class DateRecurModularSierraWidget in Recurring Date Field Modular Widgets 8
Same name and namespace in other branches
- 3.x src/Plugin/Field/FieldWidget/DateRecurModularSierraWidget.php \Drupal\date_recur_modular\Plugin\Field\FieldWidget\DateRecurModularSierraWidget
- 2.x src/Plugin/Field/FieldWidget/DateRecurModularSierraWidget.php \Drupal\date_recur_modular\Plugin\Field\FieldWidget\DateRecurModularSierraWidget
Date recur sierra widget.
This is a widget built with Drupal states, AJAX, modals, and interpreters. It is inspired by the 2019 implementation of Google Calendar/Outlook for web repeating-rule modal. It provides a single or multi-day all-day option.
Plugin annotation
@FieldWidget(
id = "date_recur_modular_sierra",
label = @Translation("Modular: Sierra"),
field_types = {
"date_recur"
}
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\Core\Field\PluginSettingsBase implements DependentPluginInterface, PluginSettingsInterface
- class \Drupal\Core\Field\WidgetBase implements WidgetInterface, ContainerFactoryPluginInterface uses AllowedTagsXssTrait
- class \Drupal\date_recur_modular\Plugin\Field\FieldWidget\DateRecurModularWidgetBase implements ContainerFactoryPluginInterface uses DateRecurModularUtilityTrait
- class \Drupal\date_recur_modular\Plugin\Field\FieldWidget\DateRecurModularSierraWidget uses DependencyTrait, DateRecurModularWidgetFieldsTrait
- class \Drupal\date_recur_modular\Plugin\Field\FieldWidget\DateRecurModularWidgetBase implements ContainerFactoryPluginInterface uses DateRecurModularUtilityTrait
- class \Drupal\Core\Field\WidgetBase implements WidgetInterface, ContainerFactoryPluginInterface uses AllowedTagsXssTrait
- class \Drupal\Core\Field\PluginSettingsBase implements DependentPluginInterface, PluginSettingsInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of DateRecurModularSierraWidget
2 files declare their use of DateRecurModularSierraWidget
File
- src/
Plugin/ Field/ FieldWidget/ DateRecurModularSierraWidget.php, line 48
Namespace
Drupal\date_recur_modular\Plugin\Field\FieldWidgetView source
class DateRecurModularSierraWidget extends DateRecurModularWidgetBase {
use DateRecurModularWidgetFieldsTrait;
use DependencyTrait;
// @todo settings: allow all day.
/**
* Name of a private tempstore collection.
*/
public const COLLECTION_MODAL_STATE = 'date_recur_modular_sierra_modal_state';
/**
* Name of a key in private tempstore collection.
*/
public const COLLECTION_MODAL_STATE_KEY = 'rrule';
/**
* Name of a key in private tempstore collection.
*/
public const COLLECTION_MODAL_STATE_DTSTART = 'dtstart';
/**
* Name of a key in private tempstore collection.
*/
public const COLLECTION_MODAL_STATE_REFRESH_BUTTON = 'refresh_button_name';
/**
* DTSTART format for COLLECTION_MODAL_STATE_DTSTART.
*/
public const COLLECTION_MODAL_STATE_DTSTART_FORMAT = 'Y-m-d\\TH:i:s e';
/**
* Stores field and delta.
*/
public const COLLECTION_MODAL_STATE_PATH = 'field_and_delta';
/**
* Stores date format to use for occurrences.
*/
public const COLLECTION_MODAL_DATE_FORMAT = 'date_format';
/**
* Form state key.
*/
protected const FORM_STATE_RRULE_KEY = 'date_recur_modular_sierra_rrule';
protected const MODE_ONCE = 'daily';
protected const MODE_WEEKLY = 'weekly';
protected const MODE_MONTHLY = 'monthly';
protected const MODE_YEARLY = 'yearly';
protected const HTML_TIME_FORMAT = 'H:i:s';
/**
* The PrivateTempStore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Provides form building and processing.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* Part grid for this list.
*
* Temporary storage for massage values.
*
* @var \Drupal\date_recur\DateRecurPartGrid
*/
protected $partGrid;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The date recur interpreter entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateRecurInterpreterStorage;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The date format entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateFormatStorage;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* {@inheritdoc}
*/
public static function defaultSettings() : array {
return [
'interpreter' => NULL,
'date_format_type' => 'medium',
'occurrences_modal' => TRUE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsSummary() : array {
$summary = parent::settingsSummary();
$interpreter = $this
->getInterpreter();
$summary[] = $interpreter ? $this
->t('Interpreter: @label', [
'@label' => $interpreter
->label() ?? $this
->t('- Missing label -'),
]) : $this
->t('No interpreter');
if ($this
->isOccurrencesModalEnabled()) {
$dateFormatId = $this
->getSetting('date_format_type');
$dateFormat = $this->dateFormatStorage
->load($dateFormatId);
$summary[] = $dateFormat ? $this
->t('Occurrence date format: @label', [
'@label' => $dateFormat
->label() ?? $dateFormat
->id(),
]) : $this
->t('Occurrence date format: missing date format');
}
$summary[] = $this
->isOccurrencesModalEnabled() ? $this
->t('Occurrences button is enabled') : $this
->t('Occurrences button is disabled');
return $summary;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) : array {
$form = parent::settingsForm($form, $form_state);
$interpreterOptions = array_map(function (DateRecurInterpreterInterface $interpreter) : string {
return $interpreter
->label() ?? (string) $this
->t('- Missing label -');
}, $this->dateRecurInterpreterStorage
->loadMultiple());
$form['interpreter'] = [
'#type' => 'select',
'#title' => $this
->t('Recurring date interpreter'),
'#description' => $this
->t('Choose a plugin for converting rules into a human readable description.'),
'#default_value' => $this
->getSetting('interpreter'),
'#options' => $interpreterOptions,
'#required' => FALSE,
'#empty_option' => $this
->t('- Do not show interpreted rule -'),
];
$dateFormatOptions = array_map(function (DateFormatInterface $dateFormat) {
$time = new DrupalDateTime();
$format = $this->dateFormatter
->format($time
->getTimestamp(), $dateFormat
->id());
return $dateFormat
->label() . ' (' . $format . ')';
}, $this->dateFormatStorage
->loadMultiple());
$form['date_format_type'] = [
'#type' => 'select',
'#title' => $this
->t('Occurrence date format'),
'#description' => $this
->t('Date format type to display occurrences and excluded occurrences.'),
'#options' => $dateFormatOptions,
'#default_value' => $this
->getSetting('date_format_type'),
];
$form['occurrences_modal'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Whether to enable occurrences button'),
'#default_value' => $this
->isOccurrencesModalEnabled(),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() : array {
$this->dependencies = parent::calculateDependencies();
$interpreter = $this
->getInterpreter();
if ($interpreter) {
$this
->addDependency('config', $interpreter
->getConfigDependencyName());
}
$id = $this
->getSetting('date_format_type');
$dateFormat = $this->dateFormatStorage
->load($id);
if ($dateFormat) {
$this
->addDependency('config', $dateFormat
->getConfigDependencyName());
}
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) : bool {
$changed = parent::onDependencyRemoval($dependencies);
$settings = $this
->getSettings();
foreach ($dependencies['config'] ?? [] as $configDependency) {
// Delete interpreter in settings if its being deleted.
if ($configDependency instanceof DateRecurInterpreterInterface) {
if ($settings['interpreter'] === $configDependency
->id()) {
unset($settings['interpreter']);
$this
->setSettings($settings);
$changed = TRUE;
}
}
}
return $changed;
}
/**
* Constructs a new DateRecurModularSierraWidget.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param array $third_party_settings
* Third party settings.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* A config factory for retrieving required config objects.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
* The PrivateTempStore factory.
* @param \Drupal\Core\Form\FormBuilderInterface $formBuilder
* Provides form building and processing.
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager.
* @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
* The date formatter service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ConfigFactoryInterface $configFactory, PrivateTempStoreFactory $tempStoreFactory, FormBuilderInterface $formBuilder, AccountInterface $currentUser, EntityTypeManagerInterface $entityTypeManager, LanguageManagerInterface $languageManager, DateFormatterInterface $dateFormatter) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $configFactory);
$this->tempStoreFactory = $tempStoreFactory;
$this->formBuilder = $formBuilder;
$this->currentUser = $currentUser;
$this->dateRecurInterpreterStorage = $entityTypeManager
->getStorage('date_recur_interpreter');
$this->dateFormatStorage = $entityTypeManager
->getStorage('date_format');
$this->languageManager = $languageManager;
$this->dateFormatter = $dateFormatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container
->get('config.factory'), $container
->get('tempstore.private'), $container
->get('form_builder'), $container
->get('current_user'), $container
->get('entity_type.manager'), $container
->get('language_manager'), $container
->get('date.formatter'));
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) : array {
/** @var \Drupal\date_recur\Plugin\Field\FieldType\DateRecurFieldItemList|\Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem[] $items */
$elementParents = [
$this->fieldDefinition
->getName(),
$delta,
];
$element['#element_validate'][] = [
static::class,
'validateModularWidget',
];
$element['#theme'] = 'date_recur_modular_sierra_widget';
$element['#attached']['library'][] = 'core/drupal.dialog.ajax';
$item = $items[$delta];
$dropdownWrapper = 'dropdown-wrapper-' . implode('-', $elementParents);
$element['buttons']['#tree'] = TRUE;
$element['buttons']['custom_recurrences'] = [
'#type' => 'button',
'#value' => $this
->t('Recurring rules'),
'#ajax' => [
'callback' => [
$this,
'openTheModal',
],
'event' => 'click',
'progress' => 'fullscreen',
],
'#attributes' => [
'class' => [
'js-hide',
'date-recur-modular-sierra-widget-recurrence-open',
],
],
'#limit_validation_errors' => [],
'#name' => Html::cleanCssIdentifier(implode('-', array_merge($elementParents, [
'custom_recurrences',
]))),
];
$element['buttons']['reload_recurrence_dropdown_custom'] = [
'#type' => 'submit',
'#value' => $this
->t('Reload dropdown and set to custom'),
'#ajax' => [
'callback' => [
get_class($this),
'reloadRecurrenceDropdownCallback',
],
'event' => 'click',
'progress' => 'fullscreen',
'wrapper' => $dropdownWrapper,
],
'#attributes' => [
'class' => [
'js-hide',
],
'data-dialog-type' => 'modal',
],
'#submit' => [
[
$this,
'transferModalToFormStateCallback',
],
],
'#limit_validation_errors' => [],
// Needs a name so triggering element works on multicardinal elements.
'#name' => Html::cleanCssIdentifier(implode('-', array_merge($elementParents, [
'reload_recurrence_dropdown_custom',
]))),
];
$element['buttons']['reload_recurrence_dropdown'] = [
'#type' => 'button',
'#value' => $this
->t('Reload dropdown'),
'#ajax' => [
'callback' => [
get_class($this),
'reloadRecurrenceDropdownCallback',
],
'event' => 'click',
'progress' => 'fullscreen',
'wrapper' => $dropdownWrapper,
],
'#attributes' => [
'class' => [
'js-hide',
'date-recur-modular-sierra-widget-reload-recurrence-options',
],
],
'#limit_validation_errors' => [],
// Needs a name so triggering element works on multicardinal elements.
'#name' => Html::cleanCssIdentifier(implode('-', array_merge($elementParents, [
'reload_recurrence_dropdown',
]))),
];
$timeZone = $this
->getDefaultTimeZone($item);
$startDateInput = NestedArray::getValue($form_state
->getUserInput(), array_merge($elementParents, [
'day_start',
]));
if ($startDateInput) {
$startDate = \DateTime::createFromFormat('Y-m-d', $startDateInput, new \DateTimeZone($timeZone));
if ($startDate) {
$startTimeInput = NestedArray::getValue($form_state
->getUserInput(), array_merge($elementParents, [
'time_start',
]));
if (is_string($startTimeInput)) {
$timeObject = $this
->parseTimeInput($startTimeInput);
if ($timeObject) {
$timeExploded = explode(':', $timeObject
->format('H:i:s'));
$startDate
->setTime((int) $timeExploded[0], (int) $timeExploded[1], (int) $timeExploded[2]);
}
else {
$startDate
->setTime(0, 0, 0);
}
}
}
}
elseif ($item->start_date instanceof DrupalDateTime) {
$startDate = $item->start_date
->getPhpDateTime();
}
if (!isset($startDate) || $startDate === FALSE) {
$startDate = new \DateTime();
}
$rrule = $item->rrule ?? '';
$element['field_path'] = [
'#type' => 'value',
'#value' => implode('/', [
$this->fieldDefinition
->getName(),
$delta,
]),
];
$element['rrule_in_storage'] = [
'#type' => 'value',
'#value' => $rrule,
];
$recurrenceOption = !empty($rrule) ? $this
->guessRecurrenceOptionFromRrule($startDate, $rrule) : NULL;
/** @var string|null $interpretation */
$interpretation = NULL;
$customRrule = $form_state
->get([
static::FORM_STATE_RRULE_KEY,
$element['field_path']['#value'],
]);
if (!empty($rrule) && $recurrenceOption === 'custom' || !empty($customRrule)) {
$customRrule = empty($customRrule) ? $rrule : $customRrule;
$helper = DateRecurHelper::create($customRrule, $startDate);
$rules = $helper
->getRules();
/** @var \Drupal\date_recur\Plugin\DateRecurInterpreterPluginInterface $plugin */
$interpreter = $this
->getInterpreter();
if ($interpreter) {
$plugin = $interpreter
->getPlugin();
$language = $this->languageManager
->getCurrentLanguage()
->getId();
$interpretation = $plugin
->interpret($rules, $language, new \DateTimeZone($timeZone));
}
else {
$interpretation = (string) $this
->t('Custom: - Missing interpreter -');
}
}
$element['day_start'] = [
'#type' => 'date',
'#title' => $this
->t('Start day'),
'#title_display' => 'invisible',
'#default_value' => $item->start_date instanceof DrupalDateTime ? $item->start_date
->format('Y-m-d') : NULL,
'#attributes' => [
'type' => 'date',
'class' => [
'date-recur-modular-sierra-widget-start-date',
],
],
'#date_date_format' => 'Y-m-d',
];
$element['day_end'] = [
'#type' => 'date',
'#title' => $this
->t('End day'),
'#title_display' => 'invisible',
'#default_value' => $item->end_date instanceof DrupalDateTime ? $item->end_date
->format('Y-m-d') : NULL,
'#attributes' => [
'type' => 'date',
'class' => [
'date-recur-modular-sierra-widget-start-end',
],
],
'#date_date_format' => 'Y-m-d',
];
$isAllDayName = $this
->getName($element, [
'is_all_day',
]);
$element['time_start'] = [
'#type' => 'date',
'#attributes' => [
'type' => 'time',
// Must specify increment else browsers default to 60, which omits
// seconds. Our validation expects seconds.
'step' => 1,
],
'#title' => $this
->t('Time'),
'#title_display' => 'invisible',
'#default_value' => $item->start_date instanceof DrupalDateTime ? $item->start_date
->format(static::HTML_TIME_FORMAT) : NULL,
// Must specify increment else browsers default to 60, which omits
// seconds. Our validation expects seconds.
'#date_increment' => 1,
];
$element['time_start']['#states']['visible'][0]['input[name="' . $isAllDayName . '"]'] = [
'checked' => FALSE,
];
$element['time_end'] = [
'#title' => $this
->t('Ending time'),
'#title_display' => 'invisible',
'#type' => 'date',
'#attributes' => [
'type' => 'time',
// Must specify increment else browsers default to 60, which omits
// seconds. Our validation expects seconds.
'step' => 1,
],
'#default_value' => $item->end_date instanceof DrupalDateTime ? $item->end_date
->format(static::HTML_TIME_FORMAT) : NULL,
];
$element['time_end']['#states']['visible'][0]['input[name="' . $isAllDayName . '"]'] = [
'checked' => FALSE,
];
$element['is_all_day'] = [
'#type' => 'checkbox',
'#title' => $this
->t('All day'),
'#default_value' => $this
->isAllDay($item),
];
$element['recurrence_option'] = [
'#type' => 'select',
'#title' => $this
->t('Recurrence option'),
'#title_display' => 'invisible',
'#default_value' => $recurrenceOption,
'#empty_option' => $this
->t('Does not repeat'),
'#prefix' => '<div id="' . $dropdownWrapper . '">',
'#suffix' => '</div>',
'#attributes' => [
'class' => [
'date-recur-modular-sierra-widget-recurrence-option',
],
],
];
$element['recurrence_option']['#options'] = $this
->getRecurrenceOptions($startDate);
if (isset($interpretation)) {
$element['recurrence_option']['#options']['custom'] = $interpretation;
}
$element['recurrence_option']['#options']['custom_open'] = $this
->t('Custom...');
$element['occurrences'] = [
'#type' => 'button',
'#value' => $this
->t('Show/exclude occurrences'),
'#ajax' => [
'callback' => [
$this,
'openOccurrencesModal',
],
'event' => 'click',
'progress' => 'fullscreen',
],
'#attributes' => [
'class' => [
'date-recur-modular-sierra-widget-occurrences-open',
],
],
'#limit_validation_errors' => [],
// Needs a unique name as formbuilder cant differentiate between deltas.
'#name' => Html::cleanCssIdentifier(implode('-', array_merge($elementParents, [
'occurrences',
]))),
'#access' => $this
->isOccurrencesModalEnabled(),
];
$element['time_zone'] = $this
->getFieldTimeZone($timeZone);
$element['time_zone']['#access'] = FALSE;
return $element;
}
/**
* Validates the widget.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function validateModularWidget(array &$element, FormStateInterface $form_state, array &$complete_form) : void {
$valueParents = $element['#parents'];
$formParents = $element['#array_parents'];
// Dont start validation until at least the start date is not empty.
/** @var string|null $start */
$startDay = $form_state
->getValue(array_merge($valueParents, [
'day_start',
]));
if (empty($startDay)) {
return;
}
/** @var string|null $timeZone */
$timeZone = $form_state
->getValue(array_merge($valueParents, [
'time_zone',
]));
if (empty($startDay)) {
$form_state
->setError($element, \t('Time zone must be set if start date is set.'));
}
$isAllDay = (bool) $form_state
->getValue(array_merge($valueParents, [
'is_all_day',
]));
if ($isAllDay) {
$form_state
->setValue(array_merge($valueParents, [
'time_start',
]), '00:00:00');
$form_state
->setValue(array_merge($valueParents, [
'time_end',
]), '23:59:59');
}
try {
$startDate = static::buildDatesFromFields(array_merge($formParents, [
'day_start',
]), array_merge($formParents, [
'time_start',
]), $timeZone, $form_state);
$form_state
->setValue(array_merge($valueParents, [
'start',
]), $startDate);
} catch (\Exception $e) {
$message = \t('Start date and time invalid.');
$form_state
->setError($element['day_start'], $message);
$form_state
->setError($element['time_start'], $message);
}
try {
$dateEnd = static::buildDatesFromFields(array_merge($formParents, [
'day_end',
]), array_merge($formParents, [
'time_end',
]), $timeZone, $form_state);
$form_state
->setValue(array_merge($valueParents, [
'end',
]), $dateEnd);
} catch (\Exception $e) {
$message = \t('End date and time invalid.');
$form_state
->setError($element['day_end'], $message);
$form_state
->setError($element['time_end'], $message);
}
if (isset($startDate) && isset($dateEnd) && $startDate > $dateEnd) {
$form_state
->setError($element['day_end'], \t('End date cannot be before the start date.'));
}
elseif (isset($startDate) && !isset($dateEnd)) {
$form_state
->setError($element['day_end'], \t('End date must be set if start date is set.'));
}
elseif (!isset($startDate) && isset($dateEnd)) {
$form_state
->setError($element['day_start'], \t('Start date must be set if end date is set.'));
}
// Process RRULE.
$rrule = '';
if (isset($startDate)) {
$recurrenceOption = $form_state
->getValue(array_merge($valueParents, [
'recurrence_option',
]));
if ($recurrenceOption === 'custom') {
$rrule = $form_state
->get([
static::FORM_STATE_RRULE_KEY,
$element['field_path']['#value'],
]);
// There wont be a value in form state if the modal wasn't interacted
// with, so fall back to value in storage.
if (!isset($rrule)) {
$rrule = $form_state
->getValue(array_merge($valueParents, [
'rrule_in_storage',
]));
}
}
else {
$rrule = static::buildRruleFromRecurrenceOption($startDate, $recurrenceOption);
}
}
$form_state
->setValue(array_merge($valueParents, [
'rrule',
]), $rrule);
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
/** @var \Drupal\date_recur\Plugin\Field\FieldType\DateRecurFieldItemList $items */
$this->partGrid = $items
->getPartGrid();
parent::extractFormValues(...func_get_args());
unset($this->partGrid);
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
$dateStorageFormat = $this->fieldDefinition
->getSetting('datetime_type') == DateRecurItem::DATETIME_TYPE_DATE ? DateRecurItem::DATE_STORAGE_FORMAT : DateRecurItem::DATETIME_STORAGE_FORMAT;
$dateStorageTimeZone = new \DateTimezone(DateRecurItem::STORAGE_TIMEZONE);
$returnValues = [];
foreach ($values as $delta => $value) {
$returnValues[$delta] = [];
$item = [];
if (!empty($value['start'])) {
$item['value'] = (clone $value['start'])
->setTimezone($dateStorageTimeZone)
->format($dateStorageFormat);
}
if (!empty($value['end'])) {
$item['end_value'] = (clone $value['end'])
->setTimezone($dateStorageTimeZone)
->format($dateStorageFormat);
}
// If no start or end date then skip.
if (count($item) === 0) {
continue;
}
assert(strlen($value['time_zone']) > 0);
$item['timezone'] = $value['time_zone'];
if (!empty($value['rrule'])) {
$item['rrule'] = $value['rrule'];
}
$returnValues[$delta] = $item;
}
return $returnValues;
}
/**
* Callback to convert RRULE data from form to modal then open modal.
*/
public function openTheModal(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
$this
->transferStateToTempstore($element, $form_state);
// Open modal.
$content = $this->formBuilder
->getForm(DateRecurModularSierraModalForm::class);
$content['#attached']['library'][] = 'core/drupal.dialog.ajax';
$dialogOptions = [
'width' => '575',
];
return (new AjaxResponse())
->setAttachments($content['#attached'])
->addCommand(new OpenModalDialogCommand($this
->t('Custom recurrence'), $content, $dialogOptions));
}
/**
* Callback to convert RRULE data from form to modal then open modal.
*/
public function openOccurrencesModal(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$this
->transferStateToTempstore($element, $form_state);
// Open modal.
$content = $this->formBuilder
->getForm(DateRecurModularSierraModalOccurrencesForm::class);
$content['#attached']['library'][] = 'core/drupal.dialog.ajax';
$dialogOptions = [
'width' => '575',
];
return (new AjaxResponse())
->setAttachments($content['#attached'])
->addCommand(new OpenModalDialogCommand($this
->t('Occurrences'), $content, $dialogOptions));
}
/**
* Transfers element state to tempstore ready for modal to consume.
*
* @param array $element
* A single form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
protected function transferStateToTempstore(array $element, FormStateInterface $form_state) : void {
$formParents = $element['#array_parents'];
$valueParents = $element['#parents'];
// Transfer RULE and Start Date to temporary storage.
$timeZone = $form_state
->getValue(array_merge($valueParents, [
'time_zone',
]));
try {
$startDate = '';
$startDate = static::buildDatesFromFields(array_merge($formParents, [
'day_start',
]), array_merge($formParents, [
'time_start',
]), $timeZone, $form_state);
} catch (\Exception $e) {
}
$startDateStr = $startDate instanceof \DateTime ? $startDate
->format(static::COLLECTION_MODAL_STATE_DTSTART_FORMAT) : '';
$path = $form_state
->getValue(array_merge($valueParents, [
'field_path',
]));
$rruleState = $form_state
->get([
static::FORM_STATE_RRULE_KEY,
$path,
]) ?? $form_state
->getValue(array_merge($valueParents, [
'rrule_in_storage',
]));
$collection = $this->tempStoreFactory
->get(static::COLLECTION_MODAL_STATE);
$collection
->set(static::COLLECTION_MODAL_STATE_KEY, $rruleState);
$collection
->set(static::COLLECTION_MODAL_STATE_DTSTART, $startDateStr);
$collection
->set(static::COLLECTION_MODAL_STATE_PATH, $path);
$collection
->set(static::COLLECTION_MODAL_DATE_FORMAT, $this
->getSetting('date_format_type'));
$collection
->set(static::COLLECTION_MODAL_STATE_REFRESH_BUTTON, $element['buttons']['reload_recurrence_dropdown_custom']['#name']);
}
/**
* Callback to convert RRULE data from modal to be consumed by form.
*/
public function transferModalToFormStateCallback(array &$form, FormStateInterface $form_state) {
$collection = $this->tempStoreFactory
->get(static::COLLECTION_MODAL_STATE);
$fieldPath = $collection
->get(static::COLLECTION_MODAL_STATE_PATH);
if ($fieldPath) {
$customRrule = $collection
->get(static::COLLECTION_MODAL_STATE_KEY);
if (isset($customRrule)) {
$form_state
->set([
static::FORM_STATE_RRULE_KEY,
$fieldPath,
], $customRrule);
$collection
->delete(static::COLLECTION_MODAL_STATE_KEY);
}
[
$fieldName,
$delta,
] = explode('/', $fieldPath);
$input =& $form_state
->getUserInput();
// After closing modal, switch dropdown to custom setting.
$input[$fieldName][$delta]['recurrence_option'] = 'custom';
}
$form_state
->setRebuild();
}
/**
* Callback to reload contents of 'recurrence_option' element.
*/
public function reloadRecurrenceDropdownCallback(array &$form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
return $element['recurrence_option'];
}
/**
* Get recurrence options for a select element based on a start date.
*
* @param \DateTime $startDate
* A date to base recurrence options.
*
* @return array
* An array of option suitable for select element.
*/
protected function getRecurrenceOptions(\DateTime $startDate) : array {
$dayOfMonth = $startDate
->format('j');
$tArgs = [
'@weekday' => $startDate
->format('l'),
'@dayofmonth' => $dayOfMonth,
'@month' => $startDate
->format('F'),
];
$monthWeekdayNth = static::getMonthWeekdayNth($startDate);
$tArgs['@monthweekdaynth'] = $monthWeekdayNth;
$tArgs['@monthweekdayordinal'] = $monthWeekdayNth == 1 ? 'st' : ($monthWeekdayNth == 2 ? 'nd' : ($monthWeekdayNth == 3 ? 'rd' : 'th'));
$options = [];
$options['daily'] = $this
->t('Daily');
$options['weekly_oneday'] = $this
->t('Weekly on @weekday', $tArgs);
$options['monthly_th_weekday'] = $this
->t('Monthly on the @monthweekdaynth@monthweekdayordinal @weekday', $tArgs);
$options['yearly_monthday'] = $this
->t('Annually on @month @dayofmonth', $tArgs);
$options['weekdayly'] = $this
->t('Every weekday (Monday to Friday)');
return $options;
}
/**
* Builds a RRULE string from a preset option given a particular start date.
*
* @param \DateTime $startDate
* A start date.
* @param string $recurrenceOption
* A recurrence option.
*
* @return string
* A RRULE string.
*/
public static function buildRruleFromRecurrenceOption(\DateTime $startDate, string $recurrenceOption) : string {
$weekdaysKeys = [
'SU',
'MO',
'TU',
'WE',
'TH',
'FR',
'SA',
];
$byDay = $weekdaysKeys[$startDate
->format('w')];
switch ($recurrenceOption) {
case 'daily':
return 'FREQ=DAILY';
case 'weekly_oneday':
return 'FREQ=WEEKLY;BYDAY=' . $byDay;
case 'monthly_th_weekday':
$monthWeekdayNth = static::getMonthWeekdayNth($startDate);
return sprintf('FREQ=MONTHLY;BYDAY=%s;BYSETPOS=%s', $byDay, $monthWeekdayNth);
case 'yearly_monthday':
return sprintf('FREQ=YEARLY;BYMONTH=%s;BYMONTHDAY=%s', $startDate
->format('n'), $startDate
->format('j'));
case 'weekdayly':
return 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR';
}
return '';
}
/**
* Attempt to guess a suitable recurrence option in getRecurrenceOptions.
*
* @param \DateTime $startDate
* A start date.
* @param string $rrule
* A rule string.
*
* @return string|null
* An option, falls back to 'custom' if no suitable recurrence could be
* determined. If a field value is non recurring then this helper shouldn't
* be called.
*/
protected function guessRecurrenceOptionFromRrule(\DateTime $startDate, string $rrule) : ?string {
try {
$helper = DateRecurHelper::create($rrule, $startDate);
/** @var \Drupal\date_recur\DateRecurRuleInterface[] $rules */
$rules = $helper
->getRules();
$rule = reset($rules);
if (!isset($rule)) {
return 'custom';
}
} catch (\Exception $e) {
return 'custom';
}
$parts = array_filter($rule
->getParts());
$frequency = $rule
->getFrequency();
$interval = $parts['INTERVAL'] ?? 1;
$count = $parts['COUNT'] ?? 1;
$byParts = array_filter($parts, function ($value, $key) : bool {
return strpos($key, 'BY', 0) === 0;
}, \ARRAY_FILTER_USE_BOTH);
$byPartCount = count($byParts);
$weekdaysKeys = [
'SU',
'MO',
'TU',
'WE',
'TH',
'FR',
'SA',
];
$byDay = explode(',', $byParts['BYDAY'] ?? '');
$byDay = array_unique(array_intersect($weekdaysKeys, $byDay));
$byDayStr = implode(',', $byDay);
$byMonth = array_unique(explode(',', $byParts['BYMONTH'] ?? ''));
sort($byMonth);
$byMonthDay = array_unique(explode(',', $byParts['BYMONTHDAY'] ?? ''));
sort($byMonthDay);
$bySetPos = array_unique(explode(',', $byParts['BYSETPOS'] ?? ''));
sort($bySetPos);
$startDayWeekday = $weekdaysKeys[$startDate
->format('w')];
if ($interval == 1 && $count == 1) {
if ($byPartCount === 0 && $frequency === 'DAILY') {
return 'daily';
}
elseif ($frequency === 'WEEKLY' && $byDayStr === 'MO,TU,WE,TH,FR' && $byPartCount === 1) {
return 'weekdayly';
}
elseif ($frequency === 'WEEKLY' && $byPartCount === 1 && count($byDay) === 1 && $byDayStr === $startDayWeekday) {
// Only if weekday is same as start day.
return 'weekly_oneday';
}
elseif ($frequency === 'MONTHLY' && $byPartCount === 2 && count($bySetPos) === 1 && count($byDay) === 1) {
return 'monthly_th_weekday';
}
elseif ($frequency === 'YEARLY' && $byPartCount === 2 && count($byMonth) === 1 && count($byMonthDay) === 1) {
return 'yearly_monthday';
}
}
return 'custom';
}
/**
* Load the interpreter to be used by this widget.
*
* @return \Drupal\date_recur\Entity\DateRecurInterpreterInterface|null
* An interpreter instance.
*/
protected function getInterpreter() : ?DateRecurInterpreterInterface {
$id = $this
->getSetting('interpreter');
if (!is_string($id) || empty($id)) {
return NULL;
}
return $this->dateRecurInterpreterStorage
->load($id);
}
/**
* Determines whether occurrences modal is enabled.
*
* @return bool
* Whether occurrences modal is enabled.
*/
protected function isOccurrencesModalEnabled() : bool {
return !empty($this
->getSetting('occurrences_modal'));
}
/**
* Parses raw input from a time field.
*
* Inspired by \Drupal\Core\Datetime\Element\Datetime::valueCallback, exists
* because plain 'time' fields do not have value callbacks.
*
* @param string $input
* Input from a time field.
*
* @return \Drupal\Core\Datetime\DrupalDateTime|null
* A date object, or NULL if input was invalid. The date portion of this
* object should be ignored.
*/
protected function parseTimeInput(string $input) : ?DrupalDateTime {
if (strlen($input) == 5) {
$input .= ':00';
}
$timeFormat = DateFormat::load('html_time')
->getPattern();
try {
return DrupalDateTime::createFromFormat($timeFormat, $input);
} catch (\Exception $e) {
return NULL;
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AllowedTagsXssTrait:: |
public | function | Returns a list of tags allowed by AllowedTagsXssTrait::fieldFilterXss(). | |
AllowedTagsXssTrait:: |
public | function | Returns a human-readable list of allowed tags for display in help texts. | |
AllowedTagsXssTrait:: |
public | function | Filters an HTML string to prevent XSS vulnerabilities. | |
DateRecurModularSierraWidget:: |
protected | property | The current user. | |
DateRecurModularSierraWidget:: |
protected | property | The date format entity storage. | |
DateRecurModularSierraWidget:: |
protected | property | The date formatter service. | |
DateRecurModularSierraWidget:: |
protected | property | The date recur interpreter entity storage. | |
DateRecurModularSierraWidget:: |
protected | property | Provides form building and processing. | |
DateRecurModularSierraWidget:: |
protected | property | The language manager. | |
DateRecurModularSierraWidget:: |
protected | property | Part grid for this list. | |
DateRecurModularSierraWidget:: |
protected | property | The PrivateTempStore factory. | |
DateRecurModularSierraWidget:: |
public static | function | Builds a RRULE string from a preset option given a particular start date. | |
DateRecurModularSierraWidget:: |
public | function |
Calculates dependencies for the configured plugin. Overrides PluginSettingsBase:: |
|
DateRecurModularSierraWidget:: |
public | constant | Stores date format to use for occurrences. | |
DateRecurModularSierraWidget:: |
public | constant | Name of a private tempstore collection. | |
DateRecurModularSierraWidget:: |
public | constant | Name of a key in private tempstore collection. | |
DateRecurModularSierraWidget:: |
public | constant | DTSTART format for COLLECTION_MODAL_STATE_DTSTART. | |
DateRecurModularSierraWidget:: |
public | constant | Name of a key in private tempstore collection. | |
DateRecurModularSierraWidget:: |
public | constant | Stores field and delta. | |
DateRecurModularSierraWidget:: |
public | constant | Name of a key in private tempstore collection. | |
DateRecurModularSierraWidget:: |
public static | function |
Creates an instance of the plugin. Overrides DateRecurModularWidgetBase:: |
|
DateRecurModularSierraWidget:: |
public static | function |
Defines the default settings for this plugin. Overrides PluginSettingsBase:: |
|
DateRecurModularSierraWidget:: |
public | function |
Extracts field values from submitted form values. Overrides WidgetBase:: |
|
DateRecurModularSierraWidget:: |
public | function |
Returns the form for a single field widget. Overrides WidgetInterface:: |
|
DateRecurModularSierraWidget:: |
protected | constant | Form state key. | |
DateRecurModularSierraWidget:: |
protected | function | Load the interpreter to be used by this widget. | |
DateRecurModularSierraWidget:: |
protected | function | Get recurrence options for a select element based on a start date. | |
DateRecurModularSierraWidget:: |
protected | function | Attempt to guess a suitable recurrence option in getRecurrenceOptions. | |
DateRecurModularSierraWidget:: |
protected | constant | ||
DateRecurModularSierraWidget:: |
protected | function | Determines whether occurrences modal is enabled. | |
DateRecurModularSierraWidget:: |
public | function |
Massages the form values into the format expected for field values. Overrides DateRecurModularWidgetBase:: |
|
DateRecurModularSierraWidget:: |
protected | constant | ||
DateRecurModularSierraWidget:: |
protected | constant | ||
DateRecurModularSierraWidget:: |
protected | constant | ||
DateRecurModularSierraWidget:: |
protected | constant | ||
DateRecurModularSierraWidget:: |
public | function |
Informs the plugin that some configuration it depends on will be deleted. Overrides PluginSettingsBase:: |
|
DateRecurModularSierraWidget:: |
public | function | Callback to convert RRULE data from form to modal then open modal. | |
DateRecurModularSierraWidget:: |
public | function | Callback to convert RRULE data from form to modal then open modal. | |
DateRecurModularSierraWidget:: |
protected | function | Parses raw input from a time field. | |
DateRecurModularSierraWidget:: |
public | function | Callback to reload contents of 'recurrence_option' element. | |
DateRecurModularSierraWidget:: |
public | function |
Returns a form to configure settings for the widget. Overrides WidgetBase:: |
|
DateRecurModularSierraWidget:: |
public | function |
Returns a short summary for the current widget settings. Overrides WidgetBase:: |
|
DateRecurModularSierraWidget:: |
public | function | Callback to convert RRULE data from modal to be consumed by form. | |
DateRecurModularSierraWidget:: |
protected | function | Transfers element state to tempstore ready for modal to consume. | |
DateRecurModularSierraWidget:: |
public static | function | Validates the widget. | |
DateRecurModularSierraWidget:: |
public | function |
Constructs a new DateRecurModularSierraWidget. Overrides DateRecurModularWidgetBase:: |
|
DateRecurModularUtilityTrait:: |
public static | function | Build a datetime object by getting the date and time from two fields. | |
DateRecurModularUtilityTrait:: |
protected | function | Builds RRULE string from an array of parts, stripping disallowed parts. | |
DateRecurModularUtilityTrait:: |
protected | function | Get the time zone associated with the current user. | |
DateRecurModularUtilityTrait:: |
protected | function | Determines a default time zone for a field item. | |
DateRecurModularUtilityTrait:: |
public static | function | Determine nth weekday into a month for a date. | |
DateRecurModularUtilityTrait:: |
protected | function | Build the name for a sub element. | |
DateRecurModularUtilityTrait:: |
protected | function | Attempts to get the first valid rule from a date recur field item. | |
DateRecurModularUtilityTrait:: |
protected | function | Get a list of time zones suitable for a select field. | |
DateRecurModularUtilityTrait:: |
protected | function | Determine whether a field item represents a full day. | |
DateRecurModularWidgetBase:: |
protected | property | The config factory service. | |
DateRecurModularWidgetBase:: |
protected | function | Determine the best suitable mode for a date recur field item. | 2 |
DateRecurModularWidgetBase:: |
protected | function | Determine the best suitable mode for a date recur field item. | 2 |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Get a BYDAY element. | |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Get an radios element for toggling between common end modes. | |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Get a select element for toggling between common modes. | |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Get a BYMONTH element. | |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Get a time zone element. | |
DateRecurModularWidgetFieldsTrait:: |
protected | function | Builds a #states array for an element dependant on mode selected. | |
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
DependencyTrait:: |
protected | property | The object's dependencies. | |
DependencyTrait:: |
protected | function | Adds multiple dependencies. | |
DependencyTrait:: |
protected | function | Adds a dependency. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginSettingsBase:: |
protected | property | Whether default settings have been merged into the current $settings. | |
PluginSettingsBase:: |
protected | property | The plugin settings injected by third party modules. | |
PluginSettingsBase:: |
public | function |
Returns the value of a setting, or its default value if absent. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Returns the array of settings, including defaults for missing settings. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets the list of third parties that store information. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets the value of a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets all third-party settings of a given module. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
protected | function | Merges default settings values into $settings. | |
PluginSettingsBase:: |
public | function |
Sets the value of a setting for the plugin. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Sets the settings for the plugin. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Sets the value of a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Unsets a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
WidgetBase:: |
protected | property | The field definition. | |
WidgetBase:: |
protected | property |
The widget settings. Overrides PluginSettingsBase:: |
|
WidgetBase:: |
public static | function | Ajax callback for the "Add another item" button. | |
WidgetBase:: |
public static | function | Submission handler for the "Add another item" button. | |
WidgetBase:: |
public static | function | After-build handler for field elements in a form. | |
WidgetBase:: |
public | function |
Assigns a field-level validation error to the right widget sub-element. Overrides WidgetInterface:: |
8 |
WidgetBase:: |
public | function |
Reports field-level validation errors against actual form elements. Overrides WidgetBaseInterface:: |
2 |
WidgetBase:: |
public | function |
Creates a form element for a field. Overrides WidgetBaseInterface:: |
3 |
WidgetBase:: |
protected | function | Special handling to create form elements for multiple values. | 1 |
WidgetBase:: |
protected | function | Generates the form element for a single copy of the widget. | |
WidgetBase:: |
protected | function | Returns the value of a field setting. | |
WidgetBase:: |
protected | function | Returns the array of field settings. | |
WidgetBase:: |
protected | function | Returns the filtered field description. | |
WidgetBase:: |
public static | function |
Retrieves processing information about the widget from $form_state. Overrides WidgetBaseInterface:: |
|
WidgetBase:: |
protected static | function | Returns the location of processing information within $form_state. | |
WidgetBase:: |
protected | function | Returns whether the widget handles multiple values. | |
WidgetBase:: |
public static | function |
Returns if the widget can be used for the provided field. Overrides WidgetInterface:: |
4 |
WidgetBase:: |
protected | function | Returns whether the widget used for default value form. | |
WidgetBase:: |
public static | function |
Stores processing information about the widget in $form_state. Overrides WidgetBaseInterface:: |