fullcalendar-8.x-2.x-dev/src/Plugin/fullcalendar/type/FullCalendar.php
src/Plugin/fullcalendar/type/FullCalendar.php
<?php
namespace Drupal\fullcalendar\Plugin\fullcalendar\type;
use Drupal\Core\Datetime\DateHelper;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\fullcalendar\Plugin\FullcalendarBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The full calender type plugin.
*
* @FullcalendarOption(
* id = "fullcalendar",
* module = "fullcalendar",
* js = TRUE,
* weight = "-20"
* )
*/
class FullCalendar extends FullcalendarBase implements ContainerFactoryPluginInterface {
use OptionsFormHelperTrait;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected LanguageManagerInterface $languageManager;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfo
*/
protected $entityTypeBundleInfo;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The entity display repository.
*
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
*/
protected $entityDisplayRepository;
/**
* The default color for events in Fullcalendar.
*
* @var string
*/
protected $defaultColor = '#3788d8';
/**
* The display options available in the FullCalendar library.
*
* @var array
*/
protected $displayOptions = [
'auto' => 'Automatic',
'block' => 'Block',
'list-item' => 'List item',
'background' => 'Background',
'inverse-background' => 'Inverse background',
'none' => 'None',
];
/**
* Constructor for the full calendar type plugin.
*
* @param array $configuration
* The configuration.
* @param string $plugin_id
* The plugin ID.
* @param array $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info
* The entity type bundle info.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
* The entity display repository.
*/
final public function __construct(
array $configuration,
string $plugin_id,
array $plugin_definition,
ModuleHandlerInterface $module_handler,
LanguageManagerInterface $language_manager,
EntityTypeBundleInfo $entity_type_bundle_info,
EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager,
EntityDisplayRepositoryInterface $entity_display_repository,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->entityDisplayRepository = $entity_display_repository;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('module_handler'),
$container->get('language_manager'),
$container->get('entity_type.bundle.info'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('entity_display.repository')
);
}
/**
* {@inheritdoc}
*/
public function defineOptions(): array {
$options = $this->getDefaultOptions();
// Override Fullcalendar / base defaults.
$options['header'] = ['default' => "left:'dayGridMonth,timeGridWeek,timeGridDay', center:'title', right:'today prev,next'"];
$options['links']['contains']['showMessages'] = ['default' => TRUE];
$options['links']['contains']['navLinks'] = ['default' => TRUE];
return $options;
}
/**
* {@inheritdoc}
*/
public function process(array &$settings): void {
static $fc_dom_id = 1;
if (empty($this->style->view->dom_id)) {
$this->style->view->dom_id = 'fc-' . $fc_dom_id++;
}
$options = $this->style->options;
// We no longer need custom fields.
unset($options['fields']);
$settings += $options + [
'view_name' => $this->style->view->storage->id(),
'view_display' => $this->style->view->current_display,
];
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state): void {
/** @var \Drupal\fullcalendar\Plugin\views\style\FullCalendar $style_plugin */
$style_plugin = $this->style;
$entity_type = $this->style->view->getBaseEntityType()->id();
// All bundle types.
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
// Options list.
$bundlesList = [];
foreach ($bundles as $id => $bundle) {
$label = $bundle['label'];
$bundlesList[$id] = $label;
}
$field_options = $style_plugin->displayHandler->getFieldLabels();
$form['intro'] = [
'#markup' => $this->t('Fullcalendar defaults have been provided where appropriate. See the "more info" links for the documentation of settings.'),
];
// Get the date fields.
$date_fields = $style_plugin->parseFields();
$form['fields'] = $this->getFieldsetElement($this->t('Customize fields'), $this->t('Customize the Drupal fields to use in the Calendar view. Appropriate fields will be determined if not set explicitly.'));
$form['fields']['title'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use a custom title'),
'#default_value' => $style_plugin->options['fields']['title'],
'#data_type' => 'bool',
'#fieldset' => 'fields',
];
$form['fields']['title_field'] = [
'#type' => 'select',
'#title' => $this->t('Title field'),
'#options' => $field_options,
'#default_value' => $style_plugin->options['fields']['title_field'] ?? '',
'#empty_option' => $this->t('- Select -'),
'#description' => $this->t('Choose the field with the custom title.'),
'#process' => ['\Drupal\Core\Render\Element\Select::processSelect'],
'#states' => [
'visible' => [
':input[name="style_options[fields][title]"]' => ['checked' => TRUE],
],
],
'#fieldset' => 'fields',
];
$form['fields']['url'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use a custom URL'),
'#default_value' => $style_plugin->options['fields']['url'],
'#data_type' => 'bool',
'#fieldset' => 'fields',
];
$form['fields']['url_field'] = [
'#type' => 'select',
'#title' => $this->t('URL field'),
'#options' => $field_options,
'#default_value' => $style_plugin->options['fields']['url_field'] ?? '',
'#empty_option' => $this->t('- Select -'),
'#description' => $this->t('Choose the field with the custom link.'),
'#process' => ['\Drupal\Core\Render\Element\Select::processSelect'],
'#states' => [
'visible' => [
':input[name="style_options[fields][url]"]' => ['checked' => TRUE],
],
],
'#fieldset' => 'fields',
];
$form['fields']['date'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use a custom date field'),
'#default_value' => $style_plugin->options['fields']['date'],
'#data_type' => 'bool',
'#fieldset' => 'fields',
];
$form['fields']['date_field'] = [
'#type' => 'select',
'#title' => $this->t('Date fields'),
'#options' => $date_fields,
'#default_value' => $style_plugin->options['fields']['date_field'] ?? '',
'#description' => $this->t('Select one or more date fields.'),
'#multiple' => TRUE,
'#size' => count($date_fields),
'#process' => ['\Drupal\Core\Render\Element\Select::processSelect'],
'#states' => [
'visible' => [
':input[name="style_options[fields][date]"]' => ['checked' => TRUE],
],
],
'#fieldset' => 'fields',
];
// Disable form elements when not needed.
if (empty($field_options)) {
$form['fields']['#description'] = $this->t('All the options are hidden, you need to add fields first.');
$form['fields']['title']['#type'] = 'hidden';
$form['fields']['url']['#type'] = 'hidden';
$form['fields']['date']['#type'] = 'hidden';
$form['fields']['title_field']['#disabled'] = TRUE;
$form['fields']['url_field']['#disabled'] = TRUE;
$form['fields']['date_field']['#disabled'] = TRUE;
}
elseif (empty($date_fields)) {
$form['fields']['date']['#type'] = 'hidden';
$form['fields']['date_field']['#disabled'] = TRUE;
}
// Fieldset for interactive options including links, drag-and-drop, etc.
$form['links'] = $this->getFieldsetElement($this->t('Interactive Options'));
$form['links']['navLinks'] = [
'#type' => 'checkbox',
'#title' => t('Enable nav links'),
'#description' => $this->t(
'Determines if day names and week names are clickable. When true, day headings and weekNumbers will become clickable. Default: false. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/navLinks', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]
),
'#default_value' => $style_plugin->options['links']['navLinks'],
'#data_type' => 'bool',
'#fieldset' => 'links',
];
$form['links']['navLinkDayClick'] = [
'#type' => 'textfield',
'#title' => $this->t('Day click function'),
'#description' => $this->t(
'Determines what happens upon a day heading nav-link click. By default, the user is taken to the first day-view that appears in the header. Enter the name of a function you have written for this feature. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/navLinkDayClick', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]
),
'#default_value' => $style_plugin->options['links']['navLinkDayClick'],
'#size' => '40',
'#fieldset' => 'links',
'#states' => [
'visible' => [
':input[name="style_options[links][navLinks]"]' => ['checked' => TRUE],
],
],
];
$form['links']['navLinkWeekClick'] = [
'#type' => 'textfield',
'#title' => $this->t('Week click function'),
'#description' => $this->t(
'Determines what happens upon a week-number nav-link click. By default, the user is taken to the a the first week-view that appears in the header. Enter the name of a function you have written for this feature. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/navLinkWeekClick', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]
),
'#default_value' => $style_plugin->options['links']['navLinkWeekClick'],
'#size' => '40',
'#fieldset' => 'links',
'#states' => [
'visible' => [
':input[name="style_options[links][navLinks]"]' => ['checked' => TRUE],
],
],
];
$form['links']['bundle_type'] = [
'#title' => $this->t('Event bundle (Content) type'),
'#description' => $this->t('The bundle (content) type of a new event. Once this is set, you can create a new event by double clicking a calendar entry.'),
'#type' => 'select',
'#options' => array_merge(['' => t('None')], $bundlesList),
'#default_value' => $style_plugin->options['links']['bundle_type'] ?? '',
];
// If the Form Mode Control module is installed, expose an option to use it.
if ($this->moduleHandler->moduleExists('form_mode_control')) {
$form_modes = $this->entityDisplayRepository->getFormModeOptions($entity_type);
// Only expose the form element if our entity type has more than one
// form mode.
if ($form_modes && count($form_modes) > 1) {
$form['links']['formMode'] = [
'#title' => $this->t('Form mode'),
'#description' => $this->t('The form mode to use for adding an entity.'),
'#type' => 'select',
'#options' => $form_modes,
'#default_value' => $style_plugin->options['links']['formMode'] ?? '',
'#states' => [
'invisible' => [
':input[name="style_options[links][bundle_type]"]' => ['value' => ''],
],
],
];
}
}
$target_options = [
'' => 'Same window',
'modal' => 'Modal dialog',
];
$form['links']['createTarget'] = [
'#title' => $this->t('Where to create'),
'#description' => $this->t('Where a double-click should open the form to create a new event. Choose "Same window" to take the user away from the calendar, or "Modal dialog" to open the form in an overlay with the calendar still visible.'),
'#type' => 'select',
'#options' => $target_options,
'#default_value' => $style_plugin->options['links']['createTarget'] ?? '',
'#states' => [
'invisible' => [
':input[name="style_options[links][bundle_type]"]' => ['value' => ''],
],
],
];
$form['links']['modalWidth'] = [
'#title' => $this->t('Modal Width'),
'#description' => $this->t('How wide (in pixels) the dialog should appear.'),
'#type' => 'number',
'#min' => '100',
'#default_value' => $style_plugin->options['links']['modalWidth'] ?? '600',
'#states' => [
// Show this number field only if a dialog is chosen above.
'visible' => [
':input[name="style_options[links][createTarget]"]' => ['value' => 'modal'],
],
],
];
$form['links']['updateConfirm'] = [
'#type' => 'checkbox',
'#title' => t('Confirm before updating'),
'#description' => $this->t(
'Require confirmation before performing drag-and-drop updates.'
),
'#default_value' => $style_plugin->options['links']['updateConfirm'] ?? 0,
'#data_type' => 'bool',
'#fieldset' => 'links',
];
$form['links']['showMessages'] = [
'#type' => 'checkbox',
'#title' => t('Show message on drag-and-drop'),
'#description' => $this->t(
'Display a message on success or failure of updating the data.'
),
'#default_value' => $style_plugin->options['links']['showMessages'] ?? 0,
'#data_type' => 'bool',
'#fieldset' => 'links',
];
// Fields to override the colors in which events will display.
$form['event_format'] = $this->getFieldsetElement($this->t('Event Formatting'), $this->t('Control how events will appear.'));
$form['event_format']['eventColor'] = [
'#title' => $this->t('Default Color'),
'#description' => $this->t('The color in which events will appear, unless overridden.'),
'#type' => 'color',
'#fieldset' => 'event_format',
'#default_value' => $style_plugin->options['event_format']['eventColor'] ?? $this->defaultColor,
];
$form['event_format']['eventDisplay'] = [
'#title' => $this->t('Display Format'),
'#description' => $this->t('How events should be displayed. @more-info', [
'@more-info' => '<a href="https://fullcalendar.io/docs/eventDisplay" target="_blank">More info</a>',
]),
'#type' => 'select',
'#options' => $this->displayOptions,
'#fieldset' => 'event_format',
'#default_value' => $style_plugin->options['event_format']['eventDisplay'] ?? 'auto',
];
$form['event_format']['displayEventTime'] = [
'#title' => $this->t('Display Times'),
'#description' => $this->t("Use the calendar's time output. You might want to disable this to include a formatted output of the time in the title instead. @more-info", [
'@more-info' => '<a href="https://fullcalendar.io/docs/displayEventTime" target="_blank">More info</a>',
]),
'#type' => 'checkbox',
'#fieldset' => 'event_format',
'#default_value' => $style_plugin->options['event_format']['displayEventTime'] ?? TRUE,
];
$form['event_format']['nextDayThreshold'] = [
'#title' => $this->t('Next Day Threshold'),
'#description' => $this->t('The time at which events should be considered to have crossed into another day. @more-info', [
'@more-info' => '<a href="https://fullcalendar.io/docs/nextDayThreshold" target="_blank">More info</a>',
]),
'#type' => 'textfield',
'#fieldset' => 'event_format',
'#default_value' => $style_plugin->options['event_format']['nextDayThreshold'] ?? '00:00:00',
];
// Fields to override the colors in which events will display.
$form['colors'] = $this->getFieldsetElement($this->t('Event Color Overrides'), $this->t('Customize the colors in which events will appear.'));
$bundle_label = $this->style->view->getBaseEntityType()->getBundleLabel();
// Content type colors.
$form['colors']['color_bundle'] = [
'#type' => 'details',
'#title' => $this->t('Color by @bundle', ['@bundle' => $bundle_label]),
'#description' => $this->t('Specify colors for each bundle type. If taxonomy color is specified, this settings would be ignored.'),
'#fieldset' => 'colors',
];
// All bundle types.
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
// Options list.
foreach ($bundlesList as $id => $label) {
// Content type colors.
$defaultValues = $style_plugin->options['colors']['color_bundle'][$id] ?? [];
// Emulate new structure if old config passed.
if (!is_array($defaultValues)) {
$defaultValues = ['color' => $defaultValues];
}
$color = $defaultValues['color'] ?? $this->defaultColor;
$textcolor = $defaultValues['textColor'] ?? $this->defaultColor;
$display = $defaultValues['display'] ?? '';
$form['colors']['color_bundle'][$id] = [
'color' => [
'#title' => $this->t('Color'),
'#default_value' => $color,
'#type' => 'color',
'#prefix' => '<div class="fullcalendar--style-group">',
],
'textColor' => [
'#title' => $this->t('Text'),
'#default_value' => $textcolor,
'#type' => 'color',
],
'display' => [
'#title' => $this->t('Display Style'),
'#default_value' => $display,
'#type' => 'select',
'#options' => $this->displayOptions,
'#suffix' => '</div>',
],
'#type' => 'html_tag',
'#tag' => 'h3',
'#value' => $label,
];
}
// Get the regular fields.
$moduleHandler = $this->moduleHandler;
if ($moduleHandler->moduleExists('taxonomy')) {
// All vocabularies.
$cabNames = $this->entityTypeManager->getStorage('taxonomy_vocabulary')->getQuery()->accessCheck(TRUE)->execute();
// Taxonomy reference field.
$tax_fields = [];
// Find out all taxonomy reference fields of this View.
foreach ($field_options as $field_name => $label) {
$field_conf = FieldStorageConfig::loadByName($entity_type, $field_name) ?: FieldStorageConfig::loadByName('user', $field_name);
if (empty($field_conf)) {
continue;
}
if ($field_conf->getType() == 'entity_reference') {
$tax_fields[$field_name] = $label;
}
}
// Field name of event taxonomy.
$form['colors']['tax_field'] = [
'#title' => $this->t('Event Taxonomy Field'),
'#description' => $this->t('In order to specify colors for event taxonomies, you must select a taxonomy reference field for the View.'),
'#type' => 'select',
'#options' => $tax_fields,
'#empty_value' => '',
'#disabled' => empty($tax_fields),
'#fieldset' => 'colors',
'#default_value' => $style_plugin->options['colors']['tax_field'] ?? '',
];
// Color for vocabularies.
$vocabulary = $style_plugin->options['colors']['vocabularies'] ?? '';
$form['colors']['vocabularies'] = [
'#title' => $this->t('Vocabularies'),
'#type' => 'select',
'#options' => $cabNames,
'#empty_value' => '',
'#fieldset' => 'colors',
'#description' => $this->t('Specify which vocabulary is using for calendar event color. If the vocabulary selected is not the one that the taxonomy field belonging to, the color setting would be ignored.'),
'#default_value' => $vocabulary,
'#states' => [
// Only show this field when the 'tax_field' is selected.
'invisible' => [
[':input[name="style_options[colors][tax_field]"]' => ['value' => '']],
],
],
'#ajax' => [
// 'callback' => '::taxonomyColorCallback',
'callback' => [$this, 'taxonomyColorCallback'],
// 'callback' => [static::class, 'taxonomyColorCallback'],
'disable-refocus' => FALSE,
'event' => 'change',
'wrapper' => 'color-taxonomies-div',
'progress' => [
'type' => 'throbber',
'message' => $this->t('Verifying entry...'),
],
],
];
$taxonomies = $style_plugin->options['colors']['color_taxonomies'] ?? [];
$form['colors']['color_taxonomies'] = $this->colorInputBoxes($vocabulary, $taxonomies);
}
// Toolbar settings.
$form['toolbar'] = $this->getFieldsetElement($this->t('Toolbar'));
$form['header'] = [
'#type' => 'textfield',
'#title' => $this->t('Header'),
'#description' => $this->t("Defines the buttons and title at the top of the calendar. Enter comma-separated key:value pairs for object properties e.g. left: 'title', center: '', right: 'today prev,next' @more-info",
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/header', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['header'],
'#size' => '40',
'#fieldset' => 'toolbar',
];
$form['footer'] = [
'#type' => 'textfield',
'#title' => $this->t('Footer'),
'#description' => $this->t('Defines the controls at the bottom of the calendar. These settings accept the same exact values as the header option. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/footer', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['footer'],
'#size' => '40',
'#fieldset' => 'toolbar',
];
$form['titleFormat'] = $this->getTitleFormatElement($style_plugin->options['titleFormat'], 'toolbar');
$form['titleRangeSeparator'] = [
'#type' => 'textfield',
'#title' => $this->t('Title range separator'),
'#description' => $this->t('Determines the separator text when formatting the date range in the toolbar title. Default: \u2013 (en dash)'),
'#default_value' => $style_plugin->options['titleRangeSeparator'],
'#prefix' => '<div class="views-left-50">',
'#suffix' => '</div>',
'#size' => '40',
'#fieldset' => 'toolbar',
];
$form['buttonText'] = $this->getButtonTextElement($style_plugin->options['buttonText'], 'toolbar');
$form['buttonIcons'] = [
'#type' => 'textfield',
'#title' => $this->t('Button icons'),
'#description' => $this->t("Icons that will be displayed in buttons of the header/footer. Enter comma-separated key:value pairs for object properties e.g. prev:'left-single-arrow', next:'right-single-arrow', prevYear:'left-double-arrow', nextYear:'right-double-arrow' @more-info",
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/buttonIcons', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['buttonIcons'],
'#prefix' => '<div class="views-left-50">',
'#suffix' => '</div>',
'#size' => '40',
'#fieldset' => 'toolbar',
];
// FC Views.
$form['views'] = $this->getFieldsetElement($this->t('Views'), $this->t('Select the Fullcalendar views to enable on this calendar.'));
$form['month_view'] = [
'#type' => 'checkbox',
'#title' => t('Month View'),
'#default_value' => $style_plugin->options['month_view'],
'#data_type' => 'bool',
'#fieldset' => 'views',
'#prefix' => '<div class="views-left-25">',
'#suffix' => '</div>',
];
$form['timegrid_view'] = [
'#type' => 'checkbox',
'#title' => t('TimeGrid View'),
'#default_value' => $style_plugin->options['timegrid_view'],
'#data_type' => 'bool',
'#fieldset' => 'views',
'#prefix' => '<div class="views-left-25">',
'#suffix' => '</div>',
];
$form['list_view'] = [
'#type' => 'checkbox',
'#title' => t('List View'),
'#default_value' => $style_plugin->options['list_view'],
'#data_type' => 'bool',
'#fieldset' => 'views',
'#prefix' => '<div class="views-left-25">',
'#suffix' => '</div>',
];
$form['daygrid_view'] = [
'#type' => 'checkbox',
'#title' => t('DayGrid View'),
'#default_value' => $style_plugin->options['daygrid_view'],
'#data_type' => 'bool',
'#fieldset' => 'views',
'#prefix' => '<div class="views-left-25">',
'#suffix' => '</div>',
];
// View.
$form['view_settings'] = $this->getFieldsetElement($this->t('View settings'), '', FALSE, '', [
'visible' => [':input[name="style_options[month_view]"]' => ['checked' => TRUE]],
]);
// Month View.
$form['month_view_settings'] = $this->getFieldsetElement($this->t('Month View settings'), '', FALSE, 'view_settings', [
'visible' => [':input[name="style_options[month_view]"]' => ['checked' => TRUE]],
]);
$form['month_view_settings']['fixedWeekCount'] = [
'#type' => 'checkbox',
'#title' => t('Number of weeks'),
'#description' => $this->t('Determines the number of weeks displayed in a month view (true). If true, the calendar will always be 6 weeks tall. If false, the calendar will have either 4, 5, or 6 weeks, depending on the month.'),
'#default_value' => $style_plugin->options['month_view_settings']['fixedWeekCount'],
'#data_type' => 'bool',
'#fieldset' => 'month_view_settings',
'#prefix' => '<div class="views-left-50">',
'#suffix' => '</div>',
];
$form['month_view_settings']['showNonCurrentDates'] = [
'#type' => 'checkbox',
'#title' => t('Number of weeks'),
'#description' => $this->t('In month view, whether dates in the previous or next month should be rendered at all. (true). Days that are disabled will not render events.'),
'#default_value' => $style_plugin->options['month_view_settings']['showNonCurrentDates'],
'#data_type' => 'bool',
'#fieldset' => 'month_view_settings',
'#prefix' => '<div class="views-left-50">',
'#suffix' => '</div>',
];
// TimeGrid View.
$form['timegrid_view_settings'] = $this->getFieldsetElement($this->t('TimeGrid View settings'), '', FALSE, 'view_settings', [
'visible' => [':input[name="style_options[timegrid_view]"]' => ['checked' => TRUE]],
]);
$form['timegrid_view_settings']['allDaySlot'] = [
'#type' => 'checkbox',
'#title' => t('Display "all-day" slot'),
'#description' => $this->t('Determines the number of weeks displayed in a month view (true). When hidden with false, all-day events will not be displayed in TimeGrid views.'),
'#default_value' => $style_plugin->options['timegrid_view_settings']['allDaySlot'],
'#data_type' => 'bool',
'#fieldset' => 'timegrid_view_settings',
];
$form['timegrid_view_settings']['allDayContent'] = [
'#type' => 'textfield',
'#title' => $this->t('"All-day" content'),
'#description' => $this->t('The title of the “all-day” slot at the top of the calendar (default: all-day). Accepts HTML in a JS object notation.'),
'#default_value' => $style_plugin->options['timegrid_view_settings']['allDayContent'],
'#size' => '40',
'#fieldset' => 'timegrid_view_settings',
];
$form['timegrid_view_settings']['slotEventOverlap'] = [
'#type' => 'checkbox',
'#title' => t('Determines if timed events in TimeGrid view should visually overlap.'),
'#description' => $this->t('When set to true (the default), events will overlap each other.'),
'#default_value' => $style_plugin->options['timegrid_view_settings']['slotEventOverlap'],
'#data_type' => 'bool',
'#fieldset' => 'timegrid_view_settings',
];
$form['timegrid_view_settings']['timeGridEventMinHeight'] = [
'#type' => 'textfield',
'#title' => t('Guaranteed minimum height.'),
'#description' => $this->t('Guarantees that events within the TimeGrid views will be a minimum height. An integer pixel value can be specified to force all TimeGrid view events to be at least the given pixel height. (default: null). If not specified (the default), all events will have a height determined by their start and end times.'),
'#default_value' => $style_plugin->options['timegrid_view_settings']['timeGridEventMinHeight'],
'#size' => '40',
'#fieldset' => 'timegrid_view_settings',
];
// List View.
$form['list_view_settings'] = $this->getFieldsetElement($this->t('List View settings'), '', FALSE, 'view_settings', [
'visible' => [':input[name="style_options[list_view]"]' => ['checked' => TRUE]],
]);
$form['list_view_settings']['listDayFormat'] = [
'#type' => 'textfield',
'#title' => t('Day format (left)'),
'#description' => $this->t('A @more-info that affects the text on the left side of the day headings in list view. If false is specified, no text is displayed.',
[
'@more-info' => Link::fromTextAndUrl($this->t('Date Formatter'), Url::fromUri(self::FC_DOCS_URL . '/listDayFormat', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['list_view_settings']['listDayFormat'],
'#size' => '40',
'#fieldset' => 'list_view_settings',
];
$form['list_view_settings']['listDayAltFormat'] = [
'#type' => 'textfield',
'#title' => t('Day format (right)'),
'#description' => $this->t('A @more-info that affects the text on the right side of the day headings in list view. If false is specified, no text is displayed.',
[
'@more-info' => Link::fromTextAndUrl($this->t('Date Formatter'), Url::fromUri(self::FC_DOCS_URL . '/listDayFormat', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['list_view_settings']['listDayAltFormat'],
'#size' => '40',
'#fieldset' => 'list_view_settings',
];
$form['list_view_settings']['noEventsMessage'] = [
'#type' => 'textfield',
'#title' => t('No events message'),
'#description' => $this->t('The text that is displayed in the middle of list view, alerting the user that there are no events within the given range.'),
'#default_value' => $style_plugin->options['list_view_settings']['noEventsMessage'],
'#size' => '40',
'#fieldset' => 'list_view_settings',
];
$form['views_options'] = $this->getFieldsetElement(
$this->t('View-Specific Options'),
$this->t('Options that apply only to specific calendar views, provided as options objects in the views option, keyed by the name of the view. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/view-specific-options', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]));
$form['views_year'] = $this->getFieldsetElement($this->t('Year'), $this->t('Options that apply only to Year views'), FALSE, 'views_options');
$form['views_year']['listYear_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_year']['listYear_buttonText'], 'views_year', $this->t('Button text (Year - List)'));
$form['views_year']['listYear_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_year']['listYear_titleFormat'], 'views_year', $this->t('Title format (Year - List)'));
$form['views_month'] = $this->getFieldsetElement($this->t('Month'), $this->t('Options that apply only to Month views'), FALSE, 'views_options');
$form['views_month']['listMonth_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_month']['listMonth_buttonText'], 'views_month', $this->t('Button text (Month - List)'));
$form['views_month']['listMonth_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_month']['listMonth_titleFormat'], 'views_month', $this->t('Title format (Month - List)'));
$form['views_month']['dayGridMonth_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_month']['dayGridMonth_buttonText'], 'views_month', $this->t('Button text (Month - Day Grid)'));
$form['views_month']['dayGridMonth_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_month']['dayGridMonth_titleFormat'], 'views_month', $this->t('Title format (Month - Day Grid)'));
$form['views_month']['dayGridMonth_dayHeaderFormat'] = $this->getColumnHeaderFormatElement($style_plugin->options['views_month']['dayGridMonth_dayHeaderFormat'], 'views_month', $this->t('Column header format (Month - Day Grid)'));
$form['views_week'] = $this->getFieldsetElement($this->t('Week'), $this->t('Options that apply only to Week views'), FALSE, 'views_options');
$form['views_week']['listWeek_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_week']['listWeek_buttonText'], 'views_week', $this->t('Button text (Week - List)'));
$form['views_week']['listWeek_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_week']['listWeek_titleFormat'], 'views_week', $this->t('Title format (Week - List)'));
$form['views_week']['dayGridWeek_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_week']['dayGridWeek_buttonText'], 'views_week', $this->t('Button text (Week - Day Grid)'));
$form['views_week']['dayGridWeek_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_week']['dayGridWeek_titleFormat'], 'views_week', $this->t('Title format (Week - Day Grid)'));
$form['views_week']['dayGridWeek_dayHeaderFormat'] = $this->getColumnHeaderFormatElement($style_plugin->options['views_week']['dayGridWeek_dayHeaderFormat'], 'views_week', $this->t('Column header format (Week - Day Grid)'));
$form['views_week']['timeGridWeek_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_week']['timeGridWeek_buttonText'], 'views_week', $this->t('Button text (Week - Time Grid)'));
$form['views_week']['timeGridWeek_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_week']['timeGridWeek_titleFormat'], 'views_week', $this->t('Title format (Week - Time Grid)'));
$form['views_week']['timeGridWeek_dayHeaderFormat'] = $this->getColumnHeaderFormatElement($style_plugin->options['views_week']['timeGridWeek_dayHeaderFormat'], 'views_week', $this->t('Column header format (Week - Time Grid)'));
$form['views_day'] = $this->getFieldsetElement($this->t('Day'), $this->t('Options that apply only to Day views'), FALSE, 'views_options');
$form['views_day']['listDay_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_day']['listDay_buttonText'], 'views_day', $this->t('Button text (Day - List)'));
$form['views_day']['listDay_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_day']['listDay_titleFormat'], 'views_day', $this->t('Title format (Day - List)'));
$form['views_day']['dayGridDay_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_day']['dayGridDay_buttonText'], 'views_day', $this->t('Button text (Day - Day Grid)'));
$form['views_day']['dayGridDay_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_day']['dayGridDay_titleFormat'], 'views_day', $this->t('Title format (Day - Day Grid)'));
$form['views_day']['dayGridDay_dayHeaderFormat'] = $this->getColumnHeaderFormatElement($style_plugin->options['views_day']['dayGridDay_dayHeaderFormat'], 'views_day', $this->t('Column header format (Day - Day Grid)'));
$form['views_day']['timeGridDay_buttonText'] = $this->getButtonTextElement($style_plugin->options['views_day']['timeGridDay_buttonText'], 'views_day', $this->t('Button text (Day - Time Grid)'));
$form['views_day']['timeGridDay_titleFormat'] = $this->getTitleFormatElement($style_plugin->options['views_day']['timeGridDay_titleFormat'], 'views_day', $this->t('Title format (Day - Time Grid)'));
$form['views_day']['timeGridDay_dayHeaderFormat'] = $this->getColumnHeaderFormatElement($style_plugin->options['views_day']['timeGridDay_dayHeaderFormat'], 'views_day', $this->t('Column header format (Day - Time Grid)'));
// Display settings.
$form['display'] = $this->getFieldsetElement($this->t('Display settings'));
$form['display']['initialView'] = [
'#type' => 'select',
'#title' => $this->t('Initial display'),
'#options' => [
'dayGridMonth' => $this->t('Month'),
'timeGridWeek' => $this->t('Week (Agenda)'),
'dayGridWeek' => $this->t('Week (Basic)'),
'timeGridDay' => $this->t('Day (Agenda)'),
'dayGridDay' => $this->t('Day (Basic)'),
'listYear' => $this->t('Year (List)'),
'listMonth' => $this->t('Month (List)'),
'listWeek' => $this->t('Week (List)'),
'listDay' => $this->t('Day (List)'),
],
'#empty_option' => $this->t('- Select -'),
'#description' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/intro', [
'attributes' => [
'target' => '_blank',
],
])),
'#default_value' => $style_plugin->options['display']['initialView'],
'#prefix' => '<div class="views-left-30">',
'#suffix' => '</div>',
'#fieldset' => 'display',
];
$form['display']['firstDay'] = [
'#type' => 'select',
'#title' => $this->t('Week starts on'),
'#options' => DateHelper::weekDays(TRUE),
'#default_value' => $style_plugin->options['display']['firstDay'],
'#prefix' => '<div class="views-left-30">',
'#suffix' => '</div>',
'#fieldset' => 'display',
];
// Date/Time display.
$form['times'] = $this->getFieldsetElement(
$this->t('Date & Time Display'),
$this->t('Settings that control presence/absence of dates as well as their styling and text. These settings work across a variety of different views. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/date-display', [
'attributes' => ['target' => '_blank'],
]))->toString(),
])
);
$form['times']['convert_timezones'] = [
'#type' => 'checkbox',
'#title' => t('Convert Timezones'),
'#description' => $this->t('Convert events in other timezones so that they show accurately for the site/user timezone.'),
'#default_value' => $style_plugin->options['times']['convert_timezones'] ?? TRUE,
'#data_type' => 'bool',
'#fieldset' => 'times',
];
$form['times']['weekends'] = [
'#type' => 'checkbox',
'#title' => t('Weekends'),
'#description' => $this->t('Whether to include Saturday/Sunday columns in any of the calendar views (true).'),
'#default_value' => $style_plugin->options['times']['weekends'],
'#data_type' => 'bool',
'#fieldset' => 'times',
];
$form['times']['hiddenDays'] = [
'#type' => 'textfield',
'#title' => t('Exclude days'),
'#description' => $this->t('Exclude certain days-of-the-week from being displayed. By default, no days are hidden, unless weekends is set to false. Enter comma-separated numbers e.g. 2, 4 @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/hiddenDays', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['times']['hiddenDays'],
'#size' => '40',
'#fieldset' => 'times',
];
$form['times']['dayHeaders'] = [
'#type' => 'checkbox',
'#title' => t('Column header'),
'#description' => $this->t('Whether the day headers should appear. For the Month, TimeGrid, and DayGrid views (true).'),
'#default_value' => $style_plugin->options['times']['dayHeaders'],
'#data_type' => 'bool',
'#fieldset' => 'times',
];
$form['axis'] = $this->getFieldsetElement(
$this->t('Time-axis settings'),
$this->t('Settings that control display of times along the side of the calendar. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/date-display', [
'attributes' => ['target' => '_blank'],
]))->toString(),
])
);
$form['axis']['slotDuration'] = [
'#type' => 'textfield',
'#title' => t('Duration of slots'),
'#description' => $this->t('The frequency for displaying time slots. (default: 00:30:00) @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/slotDuration', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['slotDuration'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['axis']['slotLabelInterval'] = [
'#type' => 'textfield',
'#title' => t('Interval of slot labels'),
'#description' => $this->t('The frequency that the time slots should be labelled with text. If not specified, a reasonable value will be automatically computed based on slotDuration. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/slotLabelInterval', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['slotLabelInterval'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['axis']['slotLabelFormat'] = [
'#type' => 'textfield',
'#title' => t('Format of slot labels'),
'#description' => $this->t('Determines the text that will be displayed within a time slot. Enter comma-separated, object properties e.g. hour:numeric, minute:2-digit, omitZeroMinute:true, meridiem:short @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/slotLabelFormat', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['slotLabelFormat'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['axis']['slotMinTime'] = [
'#type' => 'textfield',
'#title' => t('First time slot'),
'#description' => $this->t('Determines the first time slot that will be displayed for each day. (default: 00:00:00) @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/slotMinTime', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['slotMinTime'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['axis']['slotMaxTime'] = [
'#type' => 'textfield',
'#title' => t('Last time slot'),
'#description' => $this->t('Determines the last time slot that will be displayed for each day. This MUST be specified as an exclusive end time. (default: 24:00:00) @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/slotMaxTime', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['slotMaxTime'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['axis']['scrollTime'] = [
'#type' => 'textfield',
'#title' => t('Scroll time'),
'#description' => $this->t('Determines how far forward the scroll pane is initially scrolled. The user will be able to scroll back to see events before this time. (default: 06:00:00) @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/scrollTime', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['axis']['scrollTime'],
'#size' => '40',
'#fieldset' => 'axis',
];
$form['nav'] = $this->getFieldsetElement($this->t('Date navigation'));
$form['nav']['initialDate'] = [
'#type' => 'textfield',
'#title' => $this->t('Default date'),
'#description' => $this->t('The initial date displayed when the calendar first loads. When not specified, this value defaults to the current date. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/initialDate', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['nav']['initialDate'],
'#size' => '40',
'#fieldset' => 'nav',
];
$form['nav']['validRange'] = [
'#type' => 'textfield',
'#title' => $this->t('Valid date range'),
'#description' => $this->t('Limits which dates the user can navigate to and where events can go. Dates outside of the valid range will be grayed-out. Enter comma-separated key:value properties e.g. start:2017-05-01, end:2017-06-01 @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/validRange', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['nav']['validRange'],
'#size' => '40',
'#fieldset' => 'nav',
];
$form['week'] = $this->getFieldsetElement($this->t('Week Numbers'));
$form['week']['weekNumbers'] = [
'#type' => 'checkbox',
'#title' => t('Display week numbers'),
'#description' => $this->t('Determines if week numbers should be displayed on the calendar. If set to true, week numbers will be displayed in a separate left column in the Month/DayGrid views as well as at the top-left corner of the TimeGrid views. Default: false. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/weekNumbers', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['week']['weekNumbers'],
'#data_type' => 'bool',
'#fieldset' => 'week',
];
$form['week']['weekNumberCalculation'] = [
'#type' => 'textfield',
'#title' => $this->t('Week label'),
'#description' => $this->t('The method for calculating week numbers that are displayed with the weekNumbers setting e.g. local, ISO, or name of function you have written for this feature. Default: "local" @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/weekNumberCalculation', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['week']['weekNumberCalculation'],
'#size' => '40',
'#fieldset' => 'week',
];
$form['week']['weekText'] = [
'#type' => 'textfield',
'#title' => $this->t('Week label'),
'#description' => $this->t('The heading text for week numbers. Also affects weeks in date formatting. Default: "W" @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/weekText', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['week']['weekText'],
'#size' => '40',
'#fieldset' => 'week',
];
$form['now'] = $this->getFieldsetElement($this->t('Now Indicator'));
$form['now']['nowIndicator'] = [
'#type' => 'checkbox',
'#title' => t('Now indicator'),
'#description' => $this->t('Whether or not to display a marker indicating the current time. Default: false. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/nowIndicator', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['now']['nowIndicator'],
'#data_type' => 'bool',
'#fieldset' => 'now',
];
$form['now']['now'] = [
'#type' => 'checkbox',
'#title' => t('Now'),
'#description' => $this->t('Explicitly sets the "today" date of the calendar - the day that is normally highlighted in yellow. Enter a @parsable-date or name of function you have written for this feature. @more-info',
[
'@parsable-date' => Link::fromTextAndUrl($this->t('parsable date'), Url::fromUri(self::FC_DOCS_URL . '/date-parsing',
[
'attributes' => ['target' => '_blank'],
]))->toString(),
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/now',
[
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['now']['now'],
'#data_type' => 'bool',
'#fieldset' => 'now',
];
$form['business'] = $this->getFieldsetElement($this->t('Business Hours'));
$form['business']['businessHours'] = [
'#type' => 'checkbox',
'#title' => $this->t('Business Hours'),
'#description' => $this->t('Emphasizes certain time slots on the calendar. By default, Monday-Friday, 9am-5pm. For better control, see below. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/businessHours', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['business']['businessHours'],
'#data_type' => 'bool',
'#fieldset' => 'business',
];
$form['business']['businessHours2'] = [
'#type' => 'textfield',
'#title' => t('Business hours format'),
'#description' => $this->t('For fine-grain control over business hours enter key:value pairs for object properties. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/businessHours', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['business']['businessHours2'],
'#size' => '40',
'#fieldset' => 'business',
'#states' => [
'visible' => [
':input[name="style_options[business][businessHours]"]' => ['checked' => TRUE],
],
],
];
$form['style'] = $this->getFieldsetElement($this->t('Calendar Appearance/Sizing'));
$form['style']['themeSystem'] = [
'#type' => 'select',
'#title' => $this->t('Theme'),
'#options' => [
'standard' => $this->t('Standard'),
'bootstrap4' => $this->t('Bootstrap 4'),
'bootstrap5' => $this->t('Bootstrap 5'),
],
'#default_value' => $style_plugin->options['style']['themeSystem'],
'#fieldset' => 'style',
];
$form['style']['height'] = [
'#type' => 'textfield',
'#title' => t('Height'),
'#description' => $this->t('Sets the height of the entire calendar, including header and footer. Enter a number, "parent", "auto" or name of function you have written for this feature. Default: This option is unset and the calendar\'s height is calculated by aspectRatio. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/sizing', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['style']['height'],
'#size' => '40',
'#fieldset' => 'style',
];
$form['style']['contentHeight'] = [
'#type' => 'textfield',
'#title' => t('View area height'),
'#description' => $this->t('Sets the height of the view area of the calendar. Enter a number, "auto" or name of function you have written for this feature. Default: This option is unset and the calendar\'s height is calculated by aspectRatio. @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/sizing', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['style']['contentHeight'],
'#size' => '40',
'#fieldset' => 'style',
];
$form['style']['aspectRatio'] = [
'#type' => 'textfield',
'#title' => t('Width-height ratio'),
'#description' => $this->t('Sets the width-to-height aspect ratio of the calendar. Enter a float. Default: 1.35 @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/sizing', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['style']['aspectRatio'],
'#size' => '40',
'#fieldset' => 'style',
];
$form['style']['handleWindowResize'] = [
'#type' => 'checkbox',
'#title' => $this->t('Resize calendar'),
'#description' => $this->t('Automatically resize the calendar when the browser window resizes. Default: true @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/sizing', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['style']['handleWindowResize'],
'#data_type' => 'bool',
'#fieldset' => 'style',
];
$form['style']['windowResizeDelay'] = [
'#type' => 'textfield',
'#title' => $this->t('Resize delay'),
'#description' => $this->t('The time the calendar will wait to adjust its size after a window resize occurs, in milliseconds. Default: 100 @more-info',
[
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/sizing', [
'attributes' => ['target' => '_blank'],
]))->toString(),
]),
'#default_value' => $style_plugin->options['style']['windowResizeDelay'],
'#size' => '40',
'#fieldset' => 'style',
];
$form['google'] = $this->getFieldsetElement(
$this->t('Google Calendar Settings'),
$this->t('Display events from a public Google Calendar you have configured. @more-info', [
'@more-info' => Link::fromTextAndUrl($this->t('More info'), Url::fromUri(self::FC_DOCS_URL . '/google-calendar', [
'attributes' => ['target' => '_blank'],
]))->toString(),
])
);
$form['google']['googleCalendarApiKey'] = [
'#type' => 'textfield',
'#title' => $this->t('Google Calendar API key'),
'#default_value' => $style_plugin->options['google']['googleCalendarApiKey'],
'#size' => '60',
'#fieldset' => 'google',
];
$form['google']['googleCalendarId'] = [
'#type' => 'textfield',
'#title' => $this->t('Google Calendar ID(s)'),
'#description' => $this->t('You can specify multiple, comma-separated Google Calendar IDs'),
'#default_value' => $style_plugin->options['google']['googleCalendarId'],
'#size' => '60',
'#fieldset' => 'google',
];
// Custom CSS.
$form['#attached']['library'][] = 'fullcalendar/drupal.fullcalendar.admin';
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(array &$form, FormStateInterface $form_state, array &$options = []): void {
$options = $form_state->getValue('style_options');
// Don't store default color values.
$style_options = $form_state->getValue('style_options');
if (!empty($style_options['colors'])) {
if (!empty($style_options['colors']['color_bundle'])) {
foreach ($style_options['colors']['color_bundle'] as $bundle_id => $styles) {
$style_options['colors']['color_bundle'][$bundle_id] = $this->removeDefaultStyles($styles);
}
}
// Clear color_taxonomies if the color vocabulary has been unset.
if (empty($style_options['colors']['vocabularies'])) {
$style_options['colors']['color_taxonomies'] = [];
}
if (!empty($style_options['colors']['color_taxonomies'])) {
foreach ($style_options['colors']['color_taxonomies'] as $term_id => $styles) {
$style_options['colors']['color_taxonomies'][$term_id] = $this->removeDefaultStyles($styles);
}
}
$form_state->setValue('style_options', $style_options);
}
// These field options have empty defaults, make sure they stay that way.
foreach (['title', 'url', 'date'] as $field) {
if (empty($options['fields'][$field]) && isset($options['fields'][$field . '_field'])) {
unset($options['fields'][$field . '_field']);
}
}
}
/**
* Helper function to omit values that match FullCalendar defaults.
*
* @param array $styles
* The array to process.
*
* @return array
* The cleansed array.
*/
protected function removeDefaultStyles(array $styles): array {
$def_values = [
'color' => $this->defaultColor,
'textColor' => $this->defaultColor,
'display' => 'auto',
];
foreach ($def_values as $key => $def_value) {
if (isset($styles[$key]) && $styles[$key] === $def_value) {
unset($styles[$key]);
}
}
return $styles;
}
/**
* {@inheritdoc}
*/
public function preView(array &$settings): void {
$options = [];
// Grab color settings before they're filtered out or flattened.
$colors = [];
if (!empty($settings['colors'])) {
$colors = $settings['colors'];
unset($settings['colors']);
}
// Grab timezone conversion setting before being filtered out or flattened.
$convert_timezones = FALSE;
if (!empty($settings['times']['convert_timezones'])) {
$convert_timezones = $settings['times']['convert_timezones'];
unset($settings['times']['convert_timezones']);
}
// Save any fields specified for the view.
$fields = $settings['fields']['date_field'] ?? [];
// Get updated settings.
$settings = $this->filterSettings($settings);
$defaultKeys = $this->getCalendarProperties();
// buttonIcons - true, false or object.
if (isset($settings['buttonIcons'])) {
$_type = in_array($settings['buttonIcons'], [
'true',
'false',
]) ? 'scalar' : 'object';
$defaultKeys[$_type][] = 'buttonIcons';
unset($_type);
}
// Prepare FC view-specific options.
$views = [];
foreach ([
'views_year',
'views_month',
'views_week',
'views_day',
] as $_views) {
if (!empty($settings[$_views])) {
foreach ($settings[$_views] as $key => $value) {
[$_view, $_option] = explode('_', $key);
$array = $this->convertKeyValuePairsToArray($value);
if ($_option === 'buttonText') {
$views[$_view][$array['key']] = $array['value'];
}
else {
$views[$_view][$_option][$array['key']] = $array['value'];
}
}
}
unset($settings[$_views]);
}
if ($views) {
$options['views'] = $views;
}
// Prepare other FC options.
$settings = $this->flattenMultidimensionalArray($settings);
$keys = array_keys($settings);
foreach ($defaultKeys as $type => $properties) {
foreach ($properties as $property) {
if (in_array($property, $keys, TRUE)) {
switch ($type) {
case 'scalar':
$value = match ($settings[$property]) {
'true' => TRUE,
'false' => FALSE,
default => $settings[$property],
};
$options[$property] = $value;
unset($value);
break;
case 'array':
$items = explode(',', $settings[$property]);
$options[$property] = array_map('trim', $items);
break;
case 'object':
$string = $this->fixCommaSeparatedValues($settings[$property]);
$values = explode(',', $string);
foreach ($values as $value) {
$value = str_replace('_COMMA_', ',', $value);
$array = $this->convertKeyValuePairsToArray($value);
$options[$property][$array['key']] = $array['value'];
}
break;
}
}
unset($settings[$property]);
}
}
// Add back any color settings.
if ($colors) {
$settings['colors'] = $colors;
}
// Add back the timezone conversion setting.
$settings['convert_timezones'] = $convert_timezones;
// If an event bundle was specified find the appropriate field.
$start_field = '';
if (!empty($settings['bundle_type'])) {
$entity_type = $this->style->view->getBaseEntityType()->id();
// Look first for a specified date field.
if (!count($fields)) {
// Find all fields defined in this view as a backup.
/** @var \Drupal\fullcalendar\Plugin\views\style\FullCalendar $style */
$style = $this->style;
$fields = $style->parseFields();
}
if (count($fields)) {
// Use the first field in the content type that aligns with settings.
$bundle_fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $settings['bundle_type']);
$date_fields = array_intersect(array_keys($fields), array_keys($bundle_fields));
$start_field = array_shift($date_fields);
}
}
if ($start_field) {
$settings['startField'] = $start_field;
}
$settings['options'] = $options;
}
/**
* Check for differences in default settings for this view.
*
* @param array $settings
* Array of view settings.
*
* @return array
* Settings that are different from the defaults.
*/
public function filterSettings(array $settings): array {
// Prepare default options - move 'default' and 'contains' keys a level up.
$defaults = [];
$_defaults = $this->getDefaultOptions();
foreach ($_defaults as $key => $value) {
if (isset($value['default'])) {
$defaults[$key] = $value['default'];
}
elseif (isset($value['contains'])) {
foreach ($value['contains'] as $key1 => $value1) {
$defaults[$key][$key1] = $value1['default'];
}
}
}
// Diff current settings against default.
return $this->arrayRecursiveDiff($settings, $defaults);
}
/**
* Check nested arrays for differences.
*
* @param array $array1
* The original array to check against.
* @param array $array2
* The array to check for in the original one.
*
* @return array
* Elements in $array1 that are different in $array2.
*/
public function arrayRecursiveDiff(array $array1, array $array2): array {
$aReturn = [];
foreach ($array1 as $mKey => $mValue) {
if (array_key_exists($mKey, $array2)) {
if (is_array($mValue)) {
$aRecursiveDiff = $this->arrayRecursiveDiff($mValue, $array2[$mKey]);
if (count($aRecursiveDiff)) {
$aReturn[$mKey] = $aRecursiveDiff;
}
}
elseif ($mValue !== $array2[$mKey]) {
$aReturn[$mKey] = $mValue;
}
}
else {
$aReturn[$mKey] = $mValue;
}
}
return $aReturn;
}
/**
* Helper function to check for empty values.
*
* @param array $array
* The array with values.
* @param mixed $value
* The value.
* @param string $depth1
* The first level key.
* @param string $depth2
* The optional second level key.
*/
public function filterEmptyValue(array &$array, mixed $value, string $depth1, string $depth2 = ''): void {
if ($value === '') {
return;
}
if ($depth2 === '') {
$array[$depth1] = $value;
}
else {
$array[$depth1][$depth2] = $value;
}
}
/**
* Split a key:value pair into array.
*
* @param string $value
* A string with key:value pairs.
*
* @return array
* Associative array of values.
*/
public function convertKeyValuePairsToArray(string $value): array {
if (!str_contains($value, self::COMMA_REPLACEMENT)) {
$value = $this->fixCommaSeparatedValues($value);
}
$items = explode(',', $value);
$items = array_map('trim', $items);
$array = [];
foreach ($items as $item) {
[$key, $value] = explode(':', $item);
$array['key'] = $key;
$array['value'] = str_replace([self::COMMA_REPLACEMENT, "'"], [
',',
'',
], $value);
}
return $array;
}
/**
* Replace commas in quoted strings with _COMMA_.
*
* This is to allow us split comma-separated values into arrays later.
*
* @param string $value
* The comma-separates value.
*
* @return string
* The string with any commas "," between quotes replaced with _COMMA_.
*/
public function fixCommaSeparatedValues(string $value): string {
// Match and replace "," commas between single quotes.
preg_match_all("/('[^',]+),([^']+')/", $value, $matches);
foreach ($matches[0] as $match) {
$_match = str_replace(',', self::COMMA_REPLACEMENT, $match);
$value = str_replace($match, $_match, $value);
}
return $value;
}
/**
* Get list of enabled FC plugins.
*
* @param array $settings
* Settings for the view.
*
* @return array
* The list of plugins.
*/
public function getEnabledFullcalendarPlugins(array $settings): array {
$plugins = [];
$form_fields = [
'month_view' => 'dayGrid',
'timegrid_view' => 'timeGrid',
'list_view' => 'list',
'daygrid_view' => 'dayGrid',
];
foreach ($form_fields as $field => $fcPlugin) {
if (isset($settings[$field]) && ((bool) $settings[$field] === TRUE)) {
$plugins[] = $fcPlugin;
}
}
if (!empty($settings['google']['googleCalendarApiKey'])) {
$plugins[] = 'googleCalendar';
}
return $plugins;
}
/**
* Taxonomy colors Ajax callback function.
*/
public function taxonomyColorCallback(array &$form, FormStateInterface $form_state): array {
$options = $form_state->getValue('style_options');
$vid = $options['colors']['vocabularies'];
if (empty($vid)) {
return ['#markup' => '<div id="color-taxonomies-div"></div>'];
}
else {
return ['#markup' => '<div id="color-taxonomies-div">Saved and reopen this form to assign colors.</div>'];
}
}
/**
* Color input box for taxonomy terms of a vocabulary.
*/
public function colorInputBoxes(string $vid, array $defaultValues, bool $open = FALSE): array {
if (empty($vid)) {
return ['#markup' => '<div id="color-taxonomies-div"></div>'];
}
// Taxonomy color details.
$elements = [
'#type' => 'details',
'#title' => $this->t('Color by Taxonomy Term'),
'#fieldset' => 'colors',
'#open' => $open,
'#prefix' => '<div id="color-taxonomies-div">',
'#suffix' => '</div>',
];
// Term IDs of the vocabulary.
$terms = $this->getTermIds($vid);
if (isset($terms[$vid])) {
// Create a color box for each term.
foreach ($terms[$vid] as $taxonomy) {
// If the term name is a valid hex color, use as initial default color.
$initial_color = preg_match('/^#[a-fA-F0-9]{6}$/', $taxonomy->name->value) ? $taxonomy->name->value : $this->defaultColor;
$defaults = $defaultValues[$taxonomy->id()] ?? NULL;
// Emulate new structure if old config passed.
if (!is_array($defaults)) {
$defaults = ['color' => $defaults];
}
$color = $defaults['color'] ?? $initial_color;
$textcolor = $defaults['textColor'] ?? $this->defaultColor;
$display = $defaults['display'] ?? '';
$elements[$taxonomy->id()] = [
'color' => [
'#title' => $this->t('Color'),
'#default_value' => $color,
'#type' => 'color',
'#prefix' => '<div class="fullcalendar--style-group">',
],
'textColor' => [
'#title' => $this->t('Text'),
'#default_value' => $textcolor,
'#type' => 'color',
],
'display' => [
'#title' => $this->t('Display Style'),
'#default_value' => $display,
'#type' => 'select',
'#options' => $this->displayOptions,
'#suffix' => '</div>',
],
'#type' => 'html_tag',
'#tag' => 'h3',
'#value' => $taxonomy->name->value,
];
}
}
return $elements;
}
/**
* Get all terms of a vocabulary.
*/
public function getTermIds(string $vid): array {
if (empty($vid)) {
return [];
}
$terms = &drupal_static(__FUNCTION__);
// Get taxonomy terms from database if they haven't been loaded.
if (!isset($terms[$vid])) {
// Get terms Ids.
$query = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery();
$query->condition('vid', $vid);
$tids = $query->accessCheck(TRUE)->execute();
$terms[$vid] = $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($tids);
}
return $terms;
}
}
