diff --git a/config/install/core.entity_form_display.eventseries.eventseries.default.yml b/config/install/core.entity_form_display.eventseries.eventseries.default.yml index a2a8bc86e35f0a2f9738e7cd51e53d3570e3aeef..adbba6f41169eb8504e78a44cf1f562dfd1f3cd8 100644 --- a/config/install/core.entity_form_display.eventseries.eventseries.default.yml +++ b/config/install/core.entity_form_display.eventseries.eventseries.default.yml @@ -12,41 +12,67 @@ mode: default content: body: type: text_textarea - weight: -4 + weight: 1 region: content settings: rows: 5 placeholder: '' third_party_settings: { } + consecutive_recurring_date: + type: consecutive_recurring_date + weight: 3 + region: content + settings: { } + third_party_settings: { } custom_date: type: daterange_default label: above + weight: 7 + region: content + settings: { } + third_party_settings: { } + daily_recurring_date: + type: daily_recurring_date weight: 4 region: content settings: { } third_party_settings: { } + excluded_dates: + type: daterange_default + label: above + weight: 8 + settings: { } + region: content + third_party_settings: { } + included_dates: + type: daterange_default + label: above + weight: 9 + settings: { } + region: content + third_party_settings: { } monthly_recurring_date: type: monthly_recurring_date - weight: 3 + weight: 6 region: content settings: { } third_party_settings: { } recur_type: type: options_buttons settings: { } - weight: 1 + weight: 2 region: content third_party_settings: { } status: type: boolean_checkbox settings: display_label: true - weight: 120 + weight: 12 region: content third_party_settings: { } title: type: string_textfield - weight: -6 + weight: 0 region: content settings: size: 60 @@ -54,7 +80,7 @@ content: third_party_settings: { } uid: type: entity_reference_autocomplete - weight: 5 + weight: 11 settings: match_operator: CONTAINS size: 60 @@ -63,7 +89,7 @@ content: third_party_settings: { } weekly_recurring_date: type: weekly_recurring_date - weight: 2 + weight: 5 region: content settings: { } third_party_settings: { } diff --git a/config/install/recurring_events.eventseries.config.yml b/config/install/recurring_events.eventseries.config.yml index df30fd43b13c828478bcd94d3ef986ebe9549106..d596d0db663fb14940491068c5ea160aacc92708 100644 --- a/config/install/recurring_events.eventseries.config.yml +++ b/config/install/recurring_events.eventseries.config.yml @@ -7,3 +7,8 @@ days: 'monday,tuesday,wednesday,thursday,friday,saturday,sunday' limit: 10 excludes: 1 includes: 1 +enabled_fields: 'consecutive_recurring_date,daily_recurring_date,weekly_recurring_date,monthly_recurring_date,custom' +threshold_warning: 1 +threshold_count: 200 +threshold_message: 'Saving this series will create up to @total event instances. This could result in memory exhaustion or site instability.' +threshold_prevent_save: 0 diff --git a/config/schema/recurring_events.schema.yml b/config/schema/recurring_events.schema.yml index e726f21933f69932aff8fb7f7d53ef99e99409d2..036148ed0dc6f6a3fff11743c2b6545895f4d190 100644 --- a/config/schema/recurring_events.schema.yml +++ b/config/schema/recurring_events.schema.yml @@ -29,6 +29,21 @@ recurring_events.eventseries.config: includes: type: integer label: 'Enable eventseries level included dates' + enabled_fields: + type: string + label: 'Which recur type fields are available in the creation form' + threshold_warning: + type: integer + label: 'Show a warning when too many event instances are being created' + threshold_count: + type: integer + label: 'The number of event instances to trigger the warning' + threshold_message: + type: string + label: 'The warning to show when too many instances are being created' + threshold_prevent_save: + type: integer + label: 'Prevent saving a series if too many instances are being created' recurring_events.eventinstance.config: type: config_object diff --git a/recurring_events.api.php b/recurring_events.api.php index 289cff536f95495e4e15a9aac56f36f883e7db5c..798bf2a1608e05241ec96ac51d81f0c16535b6a1 100644 --- a/recurring_events.api.php +++ b/recurring_events.api.php @@ -27,6 +27,17 @@ function hook_recurring_events_durations_alter(array &$durations = []) { $durations[172800] = t('2 days'); } +/** + * Alter the unit options available when creating an event series entity. + * + * @param array $units + * An array of units. + */ +function hook_recurring_events_units_alter(array &$units = []) { + // Events can recur ever millisecond (please do not do this). + $units['millisecond'] = t('Millisecond(s)'); +} + /** * Alter the days options available when creating an event series entity. * @@ -129,6 +140,17 @@ function hook_recurring_events_event_instances_pre_create_alter(&$event_instance } } +/** + * Alter the array of recur type fields available when creating a series. + * + * @var array $fields + * The array of recur type fields available. + */ +function hook_recurring_events_recur_field_types(&$fields) { + // Do not allow weekly events. + unset($fields['weekly_recurring_date']); +} + /** * Execute custom code before event instances are deleted. * diff --git a/recurring_events.install b/recurring_events.install index 7ccb574ca240a4f4d62b07af8ee0ca030d1f1b4a..cf479472ff87ad71fb49f229ecdb9c87c4b51310 100644 --- a/recurring_events.install +++ b/recurring_events.install @@ -60,3 +60,112 @@ function recurring_events_update_8001() { \Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('included_dates', 'eventseries', 'eventseries', $included_dates); } + +/** + * Update existing series to use the new recur type values. + */ +function recurring_events_update_8002(&$sandbox) { + // This is a multipass update. + // Grab all the weekly or monthly eventseries. + if (empty($sandbox['events'])) { + $event_series = \Drupal::database()->query(' + SELECT + eventseries_field_data.id + FROM + eventseries_field_data + WHERE + recur_type = \'weekly\' + OR recur_type = \'monthly\'' + )->fetchCol(0); + + $sandbox['events'] = $event_series; + $sandbox['max'] = count($event_series); + $sandbox['limit'] = 20; + $sandbox['progress'] = 0; + } + + if (count($sandbox['events']) > 0) { + // Loop through chunks of 20 events at a time. + $events = array_splice($sandbox['events'], 0, $sandbox['limit'], []); + if (!empty($events)) { + foreach ($events as $event_id) { + // Fully load this event so we can update and save it. + $event = \Drupal::entityTypeManager()->getStorage('eventseries')->load($event_id); + if (!empty($event)) { + // If the event was weekly it needs to be weekly_recurring_date. + if ($event->getRecurType() == 'weekly') { + $event->recur_type = 'weekly_recurring_date'; + $event->save(); + } + // If the event was monthly it needs to be monthly_recurring_date. + elseif ($event->getRecurType() == 'monthly') { + $event->recur_type = 'monthly_recurring_date'; + $event->save(); + } + } + $sandbox['progress']++; + } + echo 'Updated: ' . count($events) . ' eventseries. Total: ' . $sandbox['progress'] . ' of ' . $sandbox['max'] . "\r\n"; + $sandbox['#finished'] = ($sandbox['progress'] / $sandbox['max']); + } + } + else { + $sandbox['#finished'] = 1; + } +} + +/** + * Configure the event series to have the new recur types and settings config. + */ +function recurring_events_update_8003() { + // Add the consecutive recurring date type. + $consecutive_recurring_date = BaseFieldDefinition::create('consecutive_recurring_date') + ->setLabel(t('Consecutive Event')) + ->setDescription('The consecutive recurring date configuration.') + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(FALSE) + ->setCardinality(1) + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'consecutive_recurring_date', + 'weight' => 1, + ]); + + \Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('consecutive_recurring_date', 'eventseries', 'eventseries', $consecutive_recurring_date); + + // Add the daily recurring date type. + $daily_recurring_date = BaseFieldDefinition::create('daily_recurring_date') + ->setLabel(t('Daily Event')) + ->setDescription('The daily recurring date configuration.') + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(FALSE) + ->setCardinality(1) + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'daily_recurring_date', + 'weight' => 2, + ]); + + \Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('daily_recurring_date', 'eventseries', 'eventseries', $daily_recurring_date); + + // Enable all the recur types. + $config = \Drupal::configFactory()->getEditable('recurring_events.eventseries.config'); + $config->set('enabled_fields', 'consecutive_recurring_date,daily_recurring_date,weekly_recurring_date,monthly_recurring_date,custom'); + $config->save(TRUE); +} + +/** + * Install new threshold warning config. + */ +function recurring_events_update_8004() { + $config = \Drupal::configFactory()->getEditable('recurring_events.eventseries.config'); + $config->set('threshold_warning', 1); + $config->set('threshold_count', 200); + $config->set('threshold_message', 'Saving this series will create up to @total event instances. This could result in memory exhaustion or site instability.'); + $config->set('threshold_prevent_save', 0); + $config->save(TRUE); +} diff --git a/recurring_events.module b/recurring_events.module index 16faea695623c9dea9f4e241efcf819bbac6e124..3145ca1a5df19dc215ff108fcf20aa87921a6bb6 100644 --- a/recurring_events.module +++ b/recurring_events.module @@ -14,6 +14,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\recurring_events\Entity\EventSeries; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; /** * Implements hook_help(). @@ -410,3 +412,34 @@ function recurring_events_recurring_events_event_instances_pre_create_alter(arra } } } + +/** + * Implements callback_allowed_values_function(). + */ +function recurring_events_allowed_values_function(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL) { + $values = ['custom' => t('Custom Event')]; + $fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('eventseries'); + foreach ($fields as $field_name => $field) { + $field_definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType()); + $class = new \ReflectionClass($field_definition['class']); + if ($class->implementsInterface('\Drupal\recurring_events\RecurringEventsFieldTypeInterface')) { + $values[$field->getName()] = $field->getLabel(); + } + } + return $values; +} + +/** + * Implements hook_recurring_events_recur_field_types_alter(). + */ +function recurring_events_recurring_events_recur_field_types_alter(&$fields) { + // Enable/disable the recur field types based on the eventseries settings. + $config = \Drupal::config('recurring_events.eventseries.config'); + $enabled_fields = explode(',', $config->get('enabled_fields')); + + foreach ($fields as $field_name => $field_label) { + if (array_search($field_name, $enabled_fields) === FALSE) { + unset($fields[$field_name]); + } + } +} diff --git a/recurring_events.services.yml b/recurring_events.services.yml index b0f5718fd5df7655d5008055a6b0d9a39f770cc6..7f36cbbac181ab108207793fa8504d46c7766e96 100644 --- a/recurring_events.services.yml +++ b/recurring_events.services.yml @@ -1,7 +1,7 @@ services: recurring_events.event_creation_service: class: Drupal\recurring_events\EventCreationService - arguments: ['@string_translation', '@database', '@logger.factory', '@messenger', '@date.formatter'] + arguments: ['@string_translation', '@database', '@logger.factory', '@messenger', '@plugin.manager.field.field_type', '@entity_field.manager'] plugin.manager.field_inheritance: class: Drupal\recurring_events\FieldInheritancePluginManager parent: default_plugin_manager diff --git a/recurring_events.views.inc b/recurring_events.views.inc index d1b34940018b374690280bf99f1ce15918e762c1..f64c0b95dfa95f4c180c4470454a227c36da0ecc 100644 --- a/recurring_events.views.inc +++ b/recurring_events.views.inc @@ -55,20 +55,4 @@ function recurring_events_views_data_alter(array &$data) { if (!$set) { $data['eventinstance_field_data']['table']['base']['defaults']['field'] = 'id'; } - - // @todo Remove these declarations when - // https://www.drupal.org/project/drupal/issues/2489476 is resolved. - $data['eventinstance_field_data']['date__value']['filter']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__value']['filter']['field_name'] = 'date'; - $data['eventinstance_field_data']['date__value']['sort']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__value']['sort']['field_name'] = 'date'; - $data['eventinstance_field_data']['date__value']['argument']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__value']['argument']['field_name'] = 'date'; - - $data['eventinstance_field_data']['date__end_value']['filter']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__end_value']['filter']['field_name'] = 'date'; - $data['eventinstance_field_data']['date__end_value']['sort']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__end_value']['sort']['field_name'] = 'date'; - $data['eventinstance_field_data']['date__end_value']['argument']['id'] = 'datetime'; - $data['eventinstance_field_data']['date__end_value']['argument']['field_name'] = 'date'; } diff --git a/src/Entity/EventInstance.php b/src/Entity/EventInstance.php index 55559770f69e9110eb7e3f9b985a91489faf07fa..2b93988e92512df862d25fc3e586c6ac01714afa 100644 --- a/src/Entity/EventInstance.php +++ b/src/Entity/EventInstance.php @@ -285,14 +285,6 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface return $this; } - /** - * {@inheritdoc} - */ - public function getTitle() { - // @todo Resolve this with field inheritance. - return $this->getEventSeries()->get('title')->value; - } - /** * {@inheritdoc} * diff --git a/src/Entity/EventSeries.php b/src/Entity/EventSeries.php index 7af7f54274ebfe61d0190432e54af7974cff768d..c1573172de32d88dc3d1a637062ca20c9558cf32 100644 --- a/src/Entity/EventSeries.php +++ b/src/Entity/EventSeries.php @@ -373,29 +373,46 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ->setTranslatable(FALSE) ->setRequired(TRUE) ->setCardinality(1) - ->setSetting('allowed_values', [ - 'weekly' => t('Weekly Event'), - 'monthly' => t('Monthly Event'), - 'custom' => t('Custom Event'), - ]) + ->setSetting('allowed_values_function', 'recurring_events_allowed_values_function') ->setDisplayOptions('form', [ 'type' => 'options_buttons', - 'settings' => [ - 'allowed_values' => [ - 'weekly' => t('Weekly Event'), - 'monthly' => t('Monthly Event'), - 'custom' => t('Custom Event'), - ], - ], - 'weight' => 1, + 'weight' => 0, ]) ->setDisplayOptions('view', [ 'label' => 'above', 'weight' => 10, ]); + $fields['consecutive_recurring_date'] = BaseFieldDefinition::create('consecutive_recurring_date') + ->setLabel(t('Consecutive Event')) + ->setDescription('The consecutive recurring date configuration.') + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(FALSE) + ->setCardinality(1) + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'consecutive_recurring_date', + 'weight' => 1, + ]); + + $fields['daily_recurring_date'] = BaseFieldDefinition::create('daily_recurring_date') + ->setLabel(t('Daily Event')) + ->setDescription('The daily recurring date configuration.') + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(FALSE) + ->setCardinality(1) + ->setRequired(FALSE) + ->setDisplayOptions('form', [ + 'type' => 'daily_recurring_date', + 'weight' => 2, + ]); + $fields['weekly_recurring_date'] = BaseFieldDefinition::create('weekly_recurring_date') - ->setLabel(t('Weekly Recurring Date')) + ->setLabel(t('Weekly Event')) ->setDescription('The weekly recurring date configuration.') ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) @@ -405,11 +422,11 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ->setRequired(FALSE) ->setDisplayOptions('form', [ 'type' => 'weekly_recurring_date', - 'weight' => 2, + 'weight' => 3, ]); $fields['monthly_recurring_date'] = BaseFieldDefinition::create('monthly_recurring_date') - ->setLabel(t('Monthly Recurring Date')) + ->setLabel(t('Monthly Event')) ->setDescription('The monthly recurring date configuration.') ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) @@ -419,7 +436,7 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ->setRequired(FALSE) ->setDisplayOptions('form', [ 'type' => 'monthly_recurring_date', - 'weight' => 3, + 'weight' => 4, ]); $fields['custom_date'] = BaseFieldDefinition::create('daterange') @@ -434,7 +451,7 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ->setDisplayOptions('form', [ 'type' => 'daterange_default', 'label' => 'above', - 'weight' => 4, + 'weight' => 5, ]); $fields['excluded_dates'] = BaseFieldDefinition::create('daterange') @@ -577,6 +594,130 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { return $this->get('recur_type')->value; } + /** + * Get consecutive recurring start date. + * + * @return Drupal\Core\Datetime\DrupalDateTime + * The date object for the consecutive start date. + */ + public function getConsecutiveStartDate() { + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + return $this->get('consecutive_recurring_date')->start_date->setTimezone($user_timezone)->setTime(0, 0, 0); + } + + /** + * Get consecutive recurring end date. + * + * @return Drupal\Core\Datetime\DrupalDateTime + * The date object for the consecutive end date. + */ + public function getConsecutiveEndDate() { + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + return $this->get('consecutive_recurring_date')->end_date->setTimezone($user_timezone)->setTime(0, 0, 0); + } + + /** + * Get consecutive recurring start time. + * + * @return string + * The string for the consecutive start time. + */ + public function getConsecutiveStartTime() { + return $this->get('consecutive_recurring_date')->time; + } + + /** + * Get consecutive recurring end time. + * + * @return string + * The string for the consecutive end time. + */ + public function getConsecutiveEndTime() { + return $this->get('consecutive_recurring_date')->end_time; + } + + /** + * Get consecutive recurring duration. + * + * @return int + * The integer for the consecutive duration. + */ + public function getConsecutiveDuration() { + return $this->get('consecutive_recurring_date')->duration; + } + + /** + * Get consecutive recurring duration units. + * + * @return int + * The value for the consecutive duration units. + */ + public function getConsecutiveDurationUnits() { + return $this->get('consecutive_recurring_date')->duration_units; + } + + /** + * Get consecutive recurring buffer. + * + * @return int + * The integer for the consecutive buffer. + */ + public function getConsecutiveBuffer() { + return $this->get('consecutive_recurring_date')->buffer; + } + + /** + * Get consecutive recurring duration units. + * + * @return int + * The value for the consecutive duration units. + */ + public function getConsecutiveBufferUnits() { + return $this->get('consecutive_recurring_date')->buffer_units; + } + + /** + * Get daily recurring start date. + * + * @return Drupal\Core\Datetime\DrupalDateTime + * The date object for the daily start date. + */ + public function getDailyStartDate() { + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + return $this->get('daily_recurring_date')->start_date->setTimezone($user_timezone)->setTime(0, 0, 0); + } + + /** + * Get daily recurring end date. + * + * @return Drupal\Core\Datetime\DrupalDateTime + * The date object for the daily end date. + */ + public function getDailyEndDate() { + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + return $this->get('daily_recurring_date')->end_date->setTimezone($user_timezone)->setTime(0, 0, 0); + } + + /** + * Get daily recurring start time. + * + * @return string + * The string for the daily start time. + */ + public function getDailyStartTime() { + return $this->get('daily_recurring_date')->time; + } + + /** + * Get daily recurring duration. + * + * @return int + * The integer for the daily duration. + */ + public function getDailyDuration() { + return $this->get('daily_recurring_date')->duration; + } + /** * Get weekly recurring start date. * diff --git a/src/EventCreationService.php b/src/EventCreationService.php index 4e93b3d81b52321e88741deae9188724935bcf64..9a74d6e27bb684a1ccca7e911deae4780995bdcb 100644 --- a/src/EventCreationService.php +++ b/src/EventCreationService.php @@ -10,6 +10,8 @@ use Drupal\recurring_events\Entity\EventSeries; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\Messenger; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\Core\Entity\EntityFieldManager; +use Drupal\Core\Field\FieldTypePluginManager; /** * EventCreationService class. @@ -44,6 +46,20 @@ class EventCreationService { */ protected $messenger; + /** + * The field type plugin manager. + * + * @var Drupal\Core\Field\FieldTypePluginManager + */ + protected $fieldTypePluginManager; + + /** + * The entity field manager. + * + * @var Drupal\Core\Entity\EntityFieldManager + */ + protected $entityFieldManager; + /** * Class constructor. * @@ -55,12 +71,18 @@ class EventCreationService { * The logger factory. * @param \Drupal\Core\Messenger\Messenger $messenger * The messenger service. + * @param \Drupal\Core\Field\FieldTypePluginManager $field_type_plugin_manager + * The field type plugin manager. + * @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager + * The entity field manager. */ - public function __construct(TranslationInterface $translation, Connection $database, LoggerChannelFactoryInterface $logger, Messenger $messenger) { + public function __construct(TranslationInterface $translation, Connection $database, LoggerChannelFactoryInterface $logger, Messenger $messenger, FieldTypePluginManager $field_type_plugin_manager, EntityFieldManager $entity_field_manager) { $this->translation = $translation; $this->database = $database; $this->loggerFactory = $logger->get('recurring_events'); $this->messenger = $messenger; + $this->fieldTypePluginManager = $field_type_plugin_manager; + $this->entityFieldManager = $entity_field_manager; } /** @@ -71,7 +93,9 @@ class EventCreationService { $container->get('string_translation'), $container->get('database'), $container->get('logger.factory'), - $container->get('messenger') + $container->get('messenger'), + $container->get('plugin.manager.field.field_type'), + $container->get('entity_field.manager') ); } @@ -124,37 +148,13 @@ class EventCreationService { $config['excluded_dates'] = $event->getExcludedDates(); $config['included_dates'] = $event->getIncludedDates(); - switch ($event->getRecurType()) { - case 'weekly': - $config['start_date'] = $event->getWeeklyStartDate(); - $config['end_date'] = $event->getWeeklyEndDate(); - $config['time'] = $event->getWeeklyStartTime(); - $config['duration'] = $event->getWeeklyDuration(); - $config['days'] = $event->getWeeklyDays(); - break; - - case 'monthly': - $config['start_date'] = $event->getMonthlyStartDate(); - $config['end_date'] = $event->getMonthlyEndDate(); - $config['time'] = $event->getMonthlyStartTime(); - $config['duration'] = $event->getMonthlyDuration(); - $config['monthly_type'] = $event->getMonthlyType(); - - switch ($event->getMonthlyType()) { - case 'weekday': - $config['day_occurrence'] = $event->getMonthlyDayOccurrences(); - $config['days'] = $event->getMonthlyDays(); - break; - - case 'monthday': - $config['day_of_month'] = $event->getMonthlyDayOfMonth(); - break; - } - break; - - case 'custom': - $config['custom_dates'] = $event->getCustomDates(); - break; + if ($config['type'] === 'custom') { + $config['custom_dates'] = $event->getCustomDates(); + } + else { + $field_definition = $this->fieldTypePluginManager->getDefinition($config['type']); + $field_class = $field_definition['class']; + $config += $field_class::convertEntityConfigToArray($event); } \Drupal::moduleHandler()->alter('recurring_events_entity_config_array', $config); @@ -183,95 +183,45 @@ class EventCreationService { $config['excluded_dates'] = $this->getDatesFromForm($user_input['excluded_dates']); $config['included_dates'] = $this->getDatesFromForm($user_input['included_dates']); - switch ($config['type']) { - case 'weekly': - $time = $user_input['weekly_recurring_date'][0]['time']; - $time_parts = $this->convertTimeTo24hourFormat($time); - $timestamp = implode(':', $time_parts) . ':00'; - - $start_timestamp = $user_input['weekly_recurring_date'][0]['value']['date'] . 'T' . $timestamp; - $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); - $start_date->setTime(0, 0, 0); - - $end_timestamp = $user_input['weekly_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; - $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); - $end_date->setTime(0, 0, 0); - - $config['start_date'] = $start_date; - $config['end_date'] = $end_date; - - $config['time'] = $time; - $config['duration'] = $user_input['weekly_recurring_date'][0]['duration']; - $config['days'] = array_filter(array_values($user_input['weekly_recurring_date'][0]['days'])); - break; - - case 'monthly': - $time = $user_input['monthly_recurring_date'][0]['time']; - $time_parts = $this->convertTimeTo24hourFormat($time); - $timestamp = implode(':', $time_parts) . ':00'; - - $start_timestamp = $user_input['monthly_recurring_date'][0]['value']['date'] . 'T' . $timestamp; - $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); - $start_date->setTime(0, 0, 0); - - $end_timestamp = $user_input['monthly_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; - $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); - $end_date->setTime(0, 0, 0); - - $config['start_date'] = $start_date; - $config['end_date'] = $end_date; - - $config['time'] = $time; - $config['duration'] = $user_input['monthly_recurring_date'][0]['duration']; - $config['monthly_type'] = $user_input['monthly_recurring_date'][0]['type']; - - switch ($config['monthly_type']) { - case 'weekday': - $config['day_occurrence'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['day_occurrence'])); - $config['days'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['days'])); - break; - - case 'monthday': - $config['day_of_month'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['day_of_month'])); - break; - } - break; + if ($config['type'] === 'custom') { + foreach ($user_input['custom_date'] as $custom_date) { + $start_date = $end_date = NULL; - case 'custom': - foreach ($user_input['custom_date'] as $custom_date) { - $start_date = $end_date = NULL; + if (!empty($custom_date['value']['date']) + && !empty($custom_date['value']['time']) + && !empty($custom_date['end_value']['date']) + && !empty($custom_date['end_value']['time'])) { - if (!empty($custom_date['value']['date']) - && !empty($custom_date['value']['time']) - && !empty($custom_date['end_value']['date']) - && !empty($custom_date['end_value']['time'])) { - - // For some reason, sometimes we do not receive seconds from the - // date range picker. - if (strlen($custom_date['value']['time']) == 5) { - $custom_date['value']['time'] .= ':00'; - } - if (strlen($custom_date['end_value']['time']) == 5) { - $custom_date['end_value']['time'] .= ':00'; - } + // For some reason, sometimes we do not receive seconds from the + // date range picker. + if (strlen($custom_date['value']['time']) == 5) { + $custom_date['value']['time'] .= ':00'; + } + if (strlen($custom_date['end_value']['time']) == 5) { + $custom_date['end_value']['time'] .= ':00'; + } - $start_timestamp = implode('T', $custom_date['value']); - $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); - // Convert the DateTime object back to UTC timezone. - $start_date->setTimezone($utc_timezone); + $start_timestamp = implode('T', $custom_date['value']); + $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); + // Convert the DateTime object back to UTC timezone. + $start_date->setTimezone($utc_timezone); - $end_timestamp = implode('T', $custom_date['end_value']); - $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); - // Convert the DateTime object back to UTC timezone. - $end_date->setTimezone($utc_timezone); + $end_timestamp = implode('T', $custom_date['end_value']); + $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); + // Convert the DateTime object back to UTC timezone. + $end_date->setTimezone($utc_timezone); - $config['custom_dates'][] = [ - 'start_date' => $start_date, - 'end_date' => $end_date, - ]; - } + $config['custom_dates'][] = [ + 'start_date' => $start_date, + 'end_date' => $end_date, + ]; } - break; + } + } + else { + $field_definition = $this->fieldTypePluginManager->getDefinition($config['type']); + $field_class = $field_definition['class']; + $config += $field_class::convertFormConfigToArray($form_state); } \Drupal::moduleHandler()->alter('recurring_events_form_config_array', $config); @@ -335,108 +285,34 @@ class EventCreationService { 'override' => $config_dates, ]; } - switch ($entity_config['type']) { - case 'weekly': - case 'monthly': - if ($entity_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { - $diff['start_date'] = [ - 'label' => $this->translation->translate('Start Date'), - 'stored' => $entity_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), - 'override' => $form_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), - ]; - } - if ($entity_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { - $diff['end_date'] = [ - 'label' => $this->translation->translate('End Date'), - 'stored' => $entity_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), - 'override' => $form_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), - ]; - } - if ($entity_config['time'] !== $form_config['time']) { - $diff['time'] = [ - 'label' => $this->translation->translate('Time'), - 'stored' => $entity_config['time'], - 'override' => $form_config['time'], - ]; - } - if ($entity_config['duration'] !== $form_config['duration']) { - $diff['duration'] = [ - 'label' => $this->translation->translate('Duration'), - 'stored' => $entity_config['duration'], - 'override' => $form_config['duration'], - ]; - } - if ($entity_config['type'] === 'weekly') { - if ($entity_config['days'] !== $form_config['days']) { - $diff['days'] = [ - 'label' => $this->translation->translate('Days'), - 'stored' => implode(',', $entity_config['days']), - 'override' => implode(',', $form_config['days']), - ]; - } - } + if ($entity_config['type'] === 'custom') { + if ($entity_config['custom_dates'] !== $form_config['custom_dates']) { + $stored_start_ends = $overridden_start_ends = []; - if ($entity_config['type'] === 'monthly') { - if ($entity_config['monthly_type'] !== $form_config['monthly_type']) { - $diff['monthly_type'] = [ - 'label' => $this->translation->translate('Monthly Type'), - 'stored' => $entity_config['monthly_type'], - 'override' => $form_config['monthly_type'], - ]; - } - if ($entity_config['monthly_type'] === 'weekday') { - if ($entity_config['day_occurrence'] !== $form_config['day_occurrence']) { - $diff['day_occurrence'] = [ - 'label' => $this->translation->translate('Day Occurrence'), - 'stored' => implode(',', $entity_config['day_occurrence']), - 'override' => implode(',', $form_config['day_occurrence']), - ]; - } - if ($entity_config['days'] !== $form_config['days']) { - $diff['days'] = [ - 'label' => $this->translation->translate('Days'), - 'stored' => implode(',', $entity_config['days']), - 'override' => implode(',', $form_config['days']), - ]; - } - } - else { - if ($entity_config['day_of_month'] !== $form_config['day_of_month']) { - $diff['day_of_month'] = [ - 'label' => $this->translation->translate('Day of the Month'), - 'stored' => implode(',', $entity_config['day_of_month']), - 'override' => implode(',', $form_config['day_of_month']), - ]; - } + foreach ($entity_config['custom_dates'] as $date) { + if (!empty($date['start_date']) && !empty($date['end_date'])) { + $stored_start_ends[] = $date['start_date']->format('Y-m-d h:ia') . ' - ' . $date['end_date']->format('Y-m-d h:ia'); } } - break; - - case 'custom': - if ($entity_config['custom_dates'] !== $form_config['custom_dates']) { - $stored_start_ends = $overridden_start_ends = []; - - foreach ($entity_config['custom_dates'] as $date) { - if (!empty($date['start_date']) && !empty($date['end_date'])) { - $stored_start_ends[] = $date['start_date']->format('Y-m-d h:ia') . ' - ' . $date['end_date']->format('Y-m-d h:ia'); - } - } - - foreach ($form_config['custom_dates'] as $dates) { - if (!empty($date['start_date']) && !empty($date['end_date'])) { - $overridden_start_ends[] = $date['start_date']->format('Y-m-d h:ia') . ' - ' . $date['end_date']->format('Y-m-d h:ia'); - } + foreach ($form_config['custom_dates'] as $dates) { + if (!empty($date['start_date']) && !empty($date['end_date'])) { + $overridden_start_ends[] = $date['start_date']->format('Y-m-d h:ia') . ' - ' . $date['end_date']->format('Y-m-d h:ia'); } - - $diff['custom_dates'] = [ - 'label' => $this->translation->translate('Custom Dates'), - 'stored' => implode(', ', $stored_start_ends), - 'override' => implode(', ', $overridden_start_ends), - ]; } - break; + + $diff['custom_dates'] = [ + 'label' => $this->translation->translate('Custom Dates'), + 'stored' => implode(', ', $stored_start_ends), + 'override' => implode(', ', $overridden_start_ends), + ]; + } + } + else { + $field_definition = $this->fieldTypePluginManager->getDefinition($entity_config['type']); + $field_class = $field_definition['class']; + $diff += $field_class::buildDiffArray($entity_config, $form_config); } } @@ -509,137 +385,42 @@ class EventCreationService { $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); if (!empty($form_data['type'])) { - switch ($form_data['type']) { - case 'custom': - if (!empty($form_data['custom_dates'])) { - $events_to_create = []; - foreach ($form_data['custom_dates'] as $date_range) { - // Set this event to be created. - $events_to_create[$date_range['start_date']->format('r')] = [ - 'start_date' => $date_range['start_date'], - 'end_date' => $date_range['end_date'], - ]; - } - - // Allow modules to alter the array of event instances before they - // get created. - \Drupal::moduleHandler()->alter('recurring_events_event_instances_pre_create', $events_to_create, $event); - - if (!empty($events_to_create)) { - foreach ($events_to_create as $custom_event) { - $event_instances[] = $this->createEventInstance($event, $custom_event['start_date'], $custom_event['end_date']); - } - } + if ($form_data['type'] === 'custom') { + if (!empty($form_data['custom_dates'])) { + $events_to_create = []; + foreach ($form_data['custom_dates'] as $date_range) { + // Set this event to be created. + $events_to_create[$date_range['start_date']->format('r')] = [ + 'start_date' => $date_range['start_date'], + 'end_date' => $date_range['end_date'], + ]; } - break; - case 'weekly': - if (!empty($form_data['days'])) { - $dates = []; + // Allow modules to alter the array of event instances before they + // get created. + \Drupal::moduleHandler()->alter('recurring_events_event_instances_pre_create', $events_to_create, $event); - // Loop through each weekday and find occurrences of it in the - // date range provided. - foreach ($form_data['days'] as $weekday) { - $weekday_dates = $this->findWeekdaysBetweenDates($weekday, $form_data['start_date'], $form_data['end_date']); - $dates = array_merge($dates, $weekday_dates); - } - $time_parts = $this->convertTimeTo24hourFormat($form_data['time']); - - if (!empty($dates)) { - $events_to_create = []; - foreach ($dates as $weekly_date) { - // Set the time of the start date to be the hours and minutes. - $weekly_date->setTime($time_parts[0], $time_parts[1]); - // Configure the right timezone. - $weekly_date->setTimezone($utc_timezone); - // Create a clone of this date. - $weekly_date_end = clone $weekly_date; - // Add the number of seconds specified in the duration field. - $weekly_date_end->modify('+' . $form_data['duration'] . ' seconds'); - // Set this event to be created. - $events_to_create[$weekly_date->format('r')] = [ - 'start_date' => $weekly_date, - 'end_date' => $weekly_date_end, - ]; - } - - // Allow modules to alter the array of event instances before they - // get created. - \Drupal::moduleHandler()->alter('recurring_events_event_instances_pre_create', $events_to_create, $event); - - if (!empty($events_to_create)) { - foreach ($events_to_create as $weekly_event) { - $event_instances[] = $this->createEventInstance($event, $weekly_event['start_date'], $weekly_event['end_date']); - } - } + if (!empty($events_to_create)) { + foreach ($events_to_create as $custom_event) { + $event_instances[] = $this->createEventInstance($event, $custom_event['start_date'], $custom_event['end_date']); } } - break; - - case 'monthly': - $dates = []; - $time_parts = $this->convertTimeTo24hourFormat($form_data['time']); - - if (!empty($form_data['monthly_type'])) { - $dates = []; - switch ($form_data['monthly_type']) { - case 'weekday': - // Loop through each weekday occurrence and weekday. - if (!empty($form_data['day_occurrence']) && !empty($form_data['days'])) { - foreach ($form_data['day_occurrence'] as $occurrence) { - foreach ($form_data['days'] as $weekday) { - // Find the occurrence of the specific weekdays within - // each month. - $day_occurrences = $this->findWeekdayOccurrencesBetweenDates($occurrence, $weekday, $form_data['start_date'], $form_data['end_date']); - $dates = array_merge($dates, $day_occurrences); - } - } - } - break; - - case 'monthday': - foreach ($form_data['day_of_month'] as $day_of_month) { - $days_of_month = $this->findMonthDaysBetweenDates($day_of_month, $form_data['start_date'], $form_data['end_date']); - $dates = array_merge($dates, $days_of_month); - } - break; + } + } + else { + $field_definition = $this->fieldTypePluginManager->getDefinition($form_data['type']); + $field_class = $field_definition['class']; + $events_to_create = $field_class::calculateInstances($form_data); - } + // Allow modules to alter the array of event instances before they + // get created. + \Drupal::moduleHandler()->alter('recurring_events_event_instances_pre_create', $events_to_create, $event); - // If valid recurring dates were found. - if (!empty($dates)) { - $events_to_create = []; - foreach ($dates as $monthly_date) { - // Set the time of the start date to be the hours and - // minutes. - $monthly_date->setTime($time_parts[0], $time_parts[1]); - // Configure the timezone. - $monthly_date->setTimezone($utc_timezone); - // Create a clone of this date. - $monthly_date_end = clone $monthly_date; - // Add the number of seconds specified in the duration - // field. - $monthly_date_end->modify('+' . $form_data['duration'] . ' seconds'); - // Set this event to be created. - $events_to_create[$monthly_date->format('r')] = [ - 'start_date' => $monthly_date, - 'end_date' => $monthly_date_end, - ]; - } - - // Allow modules to alter the array of event instances before they - // get created. - \Drupal::moduleHandler()->alter('recurring_events_event_instances_pre_create', $events_to_create, $event); - - if (!empty($events_to_create)) { - foreach ($events_to_create as $monthly_event) { - $event_instances[] = $this->createEventInstance($event, $monthly_event['start_date'], $monthly_event['end_date']); - } - } - } + if (!empty($events_to_create)) { + foreach ($events_to_create as $event_to_create) { + $event_instances[] = $this->createEventInstance($event, $event_to_create['start_date'], $event_to_create['end_date']); } - break; - + } } } @@ -650,239 +431,6 @@ class EventCreationService { $event->set('event_instances', $event_instances); } - /** - * Convert a time from 12 hour format to 24 hour format. - * - * @var string $time - * The time to convert to 24 hour format. - * - * @return array - * An array of time parts. - */ - public function convertTimeTo24hourFormat($time) { - $time_parts = []; - - // Split the start time up to separate out hours and minutes. - $time_parts = explode(':', $time); - // If this is PM then add 12 hours to the hours, unless the time was - // set as noon. - if (strpos($time_parts[1], 'pm') !== FALSE && $time_parts[0] != '12') { - $time_parts[0] += 12; - } - // If this is AM and the time was midnight, set hours to 00. - elseif (strpos($time_parts[1], 'am') !== FALSE && $time_parts[0] == '12') { - $time_parts[0] = '00'; - } - // Strip out AM or PM from the time. - $time_parts[1] = substr($time_parts[1], 0, -3); - - return $time_parts; - } - - /** - * Find all the weekday occurrences between two dates. - * - * @param string $weekday - * The name of the day of the week. - * @param Drupal\Core\Datetime\DrupalDateTime $start_date - * The start date. - * @param Drupal\Core\Datetime\DrupalDateTime $end_date - * The end date. - * - * @return array - * An array of matching dates. - */ - public static function findWeekdaysBetweenDates($weekday, DrupalDateTime $start_date, DrupalDateTime $end_date) { - $dates = []; - - // Clone the date as we do not want to make changes to the original object. - $start = clone $start_date; - $end = clone $end_date; - - // We want to create events up to and including the last day, so modify the - // end date to be midnight of the next day. - $end->modify('midnight next day'); - - // If the start date is after the end date then we have an invalid range so - // just return nothing. - if ($start->getTimestamp() > $end->getTimestamp()) { - return $dates; - } - - // If the start date is not the weekday we are seeking, jump to the next - // instance of that weekday. - if ($start->format('l') != ucwords($weekday)) { - $start->modify('next ' . $weekday); - } - - // Loop through a week at a time, storing the date in the array to return - // until the end date is surpassed. - while ($start->getTimestamp() <= $end->getTimestamp()) { - // If we do not clone here we end up modifying the value of start in - // the array and get some funky dates returned. - $dates[] = clone $start; - $start->modify('+1 week'); - } - - return $dates; - } - - /** - * Find all the day-of-month occurrences between two dates. - * - * @param int $day_of_month - * The day of the month. - * @param Drupal\Core\Datetime\DrupalDateTime $start_date - * The start date. - * @param Drupal\Core\Datetime\DrupalDateTime $end_date - * The end date. - * - * @return array - * An array of matching dates. - */ - public function findMonthDaysBetweenDates($day_of_month, DrupalDateTime $start_date, DrupalDateTime $end_date) { - $dates = []; - - // Clone the date as we do not want to make changes to the original object. - $start = clone $start_date; - $end = clone $end_date; - - // We want to create events up to and including the last day, so modify the - // end date to be midnight of the next day. - $end->modify('midnight next day'); - - // If the start date is after the end date then we have an invalid range so - // just return nothing. - if ($start->getTimestamp() > $end->getTimestamp()) { - return $dates; - } - - $day_to_check = $day_of_month; - - // If day of month is set to -1 that is the last day of the month, we need - // to calculate how many days a month has. - if ($day_of_month === '-1') { - $day_to_check = $start->format('t'); - } - - // If the day of the month is after the start date. - if ($start->format('d') < $day_to_check) { - $new_date = clone $start; - $curr_month = $new_date->format('m'); - $curr_year = $new_date->format('Y'); - - // Check to see if that date is a valid date. - if (!checkdate($curr_month, $day_to_check, $curr_year)) { - // If not, go find the next valid date. - $start = $this->findNextMonthDay($day_of_month, $start); - } - else { - // This is a valid date, so let us start there. - $start->setDate($curr_year, $curr_month, $day_to_check); - } - } - // If the day of the month is in the past. - elseif ($start->format('d') > $day_to_check) { - // Find the next valid start date. - $start = $this->findNextMonthDay($day_of_month, $start); - } - - // Loop through each month checking to see if the day of the month is a - // valid day, until the end date has been surpassed. - while ($start->getTimestamp() <= $end->getTimestamp()) { - // If we do not clone here we end up modifying the value of start in - // the array and get some funky dates returned. - $dates[] = clone $start; - // Find the next valid event date. - $start = $this->findNextMonthDay($day_of_month, $start); - } - - return $dates; - } - - /** - * Find the next day-of-month occurrence. - * - * @param int $day_of_month - * The day of the month. - * @param Drupal\Core\Datetime\DrupalDateTime $date - * The start date. - * - * @return Drupal\Core\Datetime\DrupalDateTime - * The next occurrence of a specific day of the month. - */ - public function findNextMonthDay($day_of_month, DrupalDateTime $date) { - $new_date = clone $date; - - $curr_month = $new_date->format('m'); - $curr_year = $new_date->format('Y'); - $next_month = $curr_month; - $next_year = $curr_year; - - do { - $next_month = ($next_month + 1) % 12 ?: 12; - $next_year = $next_month == 1 ? $next_year + 1 : $next_year; - - // If the desired day of the month is the last day, calculate what that - // day is. - if ($day_of_month === '-1') { - $new_date->setDate($next_year, $next_month, '1'); - $day_of_month = $new_date->format('t'); - } - } while (checkdate($next_month, $day_of_month, $next_year) === FALSE); - - $new_date->setDate($next_year, $next_month, $day_of_month); - return $new_date; - } - - /** - * Find all the monthly occurrences of a specific weekday between two dates. - * - * @param string $occurrence - * Which occurrence of the weekday to find. - * @param string $weekday - * The name of the day of the week. - * @param Drupal\Core\Datetime\DrupalDateTime $start_date - * The start date. - * @param Drupal\Core\Datetime\DrupalDateTime $end_date - * The end date. - * - * @return array - * An array of matching dates. - */ - public function findWeekdayOccurrencesBetweenDates($occurrence, $weekday, DrupalDateTime $start_date, DrupalDateTime $end_date) { - $dates = []; - - // Clone the date as we do not want to make changes to the original object. - $start = clone $start_date; - - // If the start date is after the end date then we have an invalid range so - // just return nothing. - if ($start->getTimestamp() > $end_date->getTimestamp()) { - return $dates; - } - - // Grab the occurrence of the weekday we want for this current month. - $start->modify($occurrence . ' ' . $weekday . ' of this month'); - - // Make sure we didn't just go back in time. - if ($start < $start_date) { - // Go straight to next month. - $start->modify($occurrence . ' ' . $weekday . ' of next month'); - } - - // Loop through a week at a time, storing the date in the array to return - // until the end date is surpassed. - while ($start->getTimestamp() <= $end_date->getTimestamp()) { - // If we do not clone here we end up modifying the value of start in - // the array and get some funky dates returned. - $dates[] = clone $start; - $start->modify($occurrence . ' ' . $weekday . ' of next month'); - } - - return $dates; - } - /** * Create an event instance from an event series. * @@ -968,4 +516,35 @@ class EventCreationService { return $string; } + /** + * Retrieve the recur field types. + * + * @param bool $allow_alter + * Allow altering of the field types. + * + * @return array + * An array of field types. + */ + public function getRecurFieldTypes($allow_alter = TRUE) { + // Build an array of recur type field options based on FieldTypes that + // implement the Drupal\recurring_events\RecurringEventsFieldTypeInterface + // interface. Allow for other modules to customize this list with an alter + // hook. + $recur_fields = []; + $fields = $this->entityFieldManager->getBaseFieldDefinitions('eventseries'); + foreach ($fields as $field_name => $field) { + $field_definition = $this->fieldTypePluginManager->getDefinition($field->getType()); + $class = new \ReflectionClass($field_definition['class']); + if ($class->implementsInterface('\Drupal\recurring_events\RecurringEventsFieldTypeInterface')) { + $recur_fields[$field->getName()] = $field->getLabel(); + } + } + + $recur_fields['custom'] = t('Custom Event'); + if ($allow_alter) { + \Drupal::moduleHandler()->alter('recurring_events_recur_field_types', $recur_fields); + } + return $recur_fields; + } + } diff --git a/src/Form/EventInstanceSettingsForm.php b/src/Form/EventInstanceSettingsForm.php index db8c0ae934c6f358c9b446dce7514841bb969047..b4572ca3c9a1b5a400abc3f6c4aa42d437b738da 100644 --- a/src/Form/EventInstanceSettingsForm.php +++ b/src/Form/EventInstanceSettingsForm.php @@ -60,7 +60,7 @@ class EventInstanceSettingsForm extends ConfigFormBase { * Form definition array. */ public function buildForm(array $form, FormStateInterface $form_state) { - $config = $this->config('recurring_events.eventinstance.config'); + $config = $this->config('recurring_events.eventseries.config'); $php_date_url = Url::fromUri('https://secure.php.net/manual/en/function.date.php'); $php_date_link = Link::fromTextAndUrl($this->t('PHP date/time format'), $php_date_url); diff --git a/src/Form/EventSeriesForm.php b/src/Form/EventSeriesForm.php index b979d24fe95bbbce2fbd4f60a2ee95661c69c63b..6e4767b08f8a1f615e0299aa3f651cb2c9e45290 100644 --- a/src/Form/EventSeriesForm.php +++ b/src/Form/EventSeriesForm.php @@ -10,6 +10,9 @@ use Drupal\recurring_events\EventCreationService; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Messenger\Messenger; use Drupal\Core\Datetime\DateFormatter; +use Drupal\Core\Entity\EntityFieldManager; +use Drupal\Core\Field\FieldTypePluginManager; +use Drupal\recurring_events\Plugin\Field\FieldWidget\ConsecutiveRecurringDateWidget; /** * Form controller for the eventseries entity create form. @@ -53,6 +56,20 @@ class EventSeriesForm extends ContentEntityForm { */ protected $dateFormatter; + /** + * The entity field manager. + * + * @var Drupal\Core\Entity\EntityFieldManager + */ + protected $entityFieldManager; + + /** + * The field type plugin manager. + * + * @var Drupal\Core\Field\FieldTypePluginManager + */ + protected $fieldTypePluginManager; + /** * {@inheritdoc} */ @@ -62,7 +79,9 @@ class EventSeriesForm extends ContentEntityForm { $container->get('entity_type.manager')->getStorage('eventseries'), $container->get('entity.manager'), $container->get('messenger'), - $container->get('date.formatter') + $container->get('date.formatter'), + $container->get('entity_field.manager'), + $container->get('plugin.manager.field.field_type') ); } @@ -79,12 +98,18 @@ class EventSeriesForm extends ContentEntityForm { * The messenger service. * @param \Drupal\Core\Datetime\DateFormatter $date_formatter * The date formatter service. + * @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager + * The entity field manager. + * @param \Drupal\Core\Field\FieldTypePluginManager $field_type_plugin_manager + * The field type plugin manager. */ - public function __construct(EventCreationService $creation_service, EntityStorageInterface $storage, EntityManagerInterface $entity_manager, Messenger $messenger, DateFormatter $date_formatter) { + public function __construct(EventCreationService $creation_service, EntityStorageInterface $storage, EntityManagerInterface $entity_manager, Messenger $messenger, DateFormatter $date_formatter, EntityFieldManager $entity_field_manager, FieldTypePluginManager $field_type_plugin_manager) { $this->creationService = $creation_service; $this->storage = $storage; $this->messenger = $messenger; $this->dateFormatter = $date_formatter; + $this->entityFieldManager = $entity_field_manager; + $this->fieldTypePluginManager = $field_type_plugin_manager; parent::__construct($entity_manager); } @@ -94,6 +119,8 @@ class EventSeriesForm extends ContentEntityForm { public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); + $config = \Drupal::config('recurring_events.eventseries.config'); + $editing = ($form_state->getBuildInfo()['form_id'] == 'eventseries_edit_form'); /* @var $entity \Drupal\recurring_events\Entity\EventSeries */ @@ -105,6 +132,23 @@ class EventSeriesForm extends ContentEntityForm { ], ]; + // Get all the available recur type fields. Suppress altering so that we can + // get a list of all the fields, so that after we alter, we can remove the + // necessary fields from the entity form. + $recur_fields = $this->creationService->getRecurFieldTypes(FALSE); + $all_recur_fields = $recur_fields; + \Drupal::moduleHandler()->alter('recurring_events_recur_field_types', $recur_fields); + + $form['recur_type']['widget']['#options'] = $recur_fields; + + // Loop through all the recurring date configuration fields and if any were + // suppressed then also suppress the fields associated with that. + foreach ($all_recur_fields as $field_name => $field_label) { + if (!isset($recur_fields[$field_name])) { + unset($form[$field_name]); + } + } + if ($editing) { $original = $this->storage->loadUnchanged($entity->id()); if ($this->step === 1) { @@ -141,6 +185,20 @@ class EventSeriesForm extends ContentEntityForm { '#rows' => $diff_array, ]; + if ($config->get('threshold_warning')) { + $total = ConsecutiveRecurringDateWidget::checkDuration($form_state); + if ($total > $config->get('threshold_count')) { + $message = $config->get('threshold_message'); + $message = str_replace('@total', $total, $message); + $form['diff']['count_warning'] = [ + '#type' => 'markup', + '#prefix' => '<p class="form-item--error-message">', + '#markup' => $message, + '#suffix' => '</p>', + ]; + } + } + $form['diff']['confirm'] = [ '#type' => 'submit', '#value' => $this->t('Confirm Date Changes'), @@ -200,7 +258,12 @@ class EventSeriesForm extends ContentEntityForm { $editing = ($form_state->getBuildInfo()['form_id'] == 'eventseries_edit_form'); $trigger = $form_state->getTriggeringElement(); - if ($trigger['#id'] !== 'edit-confirm' && $editing) { + $ignored_triggers = [ + 'consecutive_recurring_date[0][duration]', + 'consecutive_recurring_date[0][duration_units]', + ]; + + if ($trigger['#id'] !== 'edit-confirm' && array_search($trigger['#name'], $ignored_triggers) === FALSE && $editing) { $original = $this->storage->loadUnchanged($entity->id()); if ($this->creationService->checkForFormRecurConfigChanges($original, $form_state)) { $this->step = 1; diff --git a/src/Form/EventSeriesSettingsForm.php b/src/Form/EventSeriesSettingsForm.php index 450273651011d44b53b6340b8939db9e762f15fb..4e7762ac4546b2dc87d82e6875b64297d3001468 100644 --- a/src/Form/EventSeriesSettingsForm.php +++ b/src/Form/EventSeriesSettingsForm.php @@ -50,8 +50,11 @@ class EventSeriesSettingsForm extends ConfigFormBase { ->set('limit', $form_state->getValue('limit')) ->set('excludes', $form_state->getValue('excludes')) ->set('includes', $form_state->getValue('includes')) - ->set('excluded_dates', implode(',', array_filter($form_state->getValue('excluded_dates')))) - ->set('included_dates', implode(',', array_filter($form_state->getValue('included_dates')))) + ->set('enabled_fields', implode(',', array_filter($form_state->getValue('enabled_fields')))) + ->set('threshold_warning', $form_state->getValue('threshold_warning')) + ->set('threshold_count', $form_state->getValue('threshold_count')) + ->set('threshold_message', $form_state->getValue('threshold_message')) + ->set('threshold_prevent_save', $form_state->getValue('threshold_prevent_save')) ->save(); parent::submitForm($form, $form_state); @@ -161,6 +164,60 @@ class EventSeriesSettingsForm extends ConfigFormBase { '#default_value' => $config->get('includes'), ]; + $fields = \Drupal::service('recurring_events.event_creation_service')->getRecurFieldTypes(FALSE); + + $form['creation']['enabled_fields'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Enabled Recur Field Types'), + '#required' => TRUE, + '#options' => $fields, + '#description' => $this->t('Select the recur field types to enable.'), + '#default_value' => explode(',', $config->get('enabled_fields')), + ]; + + $form['creation']['threshold_warning'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show Event Instance Threshold Warning?'), + '#description' => $this->t('Display a warning when too many event instances may be created?'), + '#default_value' => $config->get('threshold_warning'), + ]; + + $form['creation']['threshold_count'] = [ + '#type' => 'number', + '#title' => $this->t('Event Instance Threshold Count'), + '#description' => $this->t('The number of event instances to trigger the warning'), + '#default_value' => $config->get('threshold_count'), + '#states' => [ + 'visible' => [ + 'input[name="threshold_warning"]' => ['checked' => TRUE], + ], + ], + ]; + + $form['creation']['threshold_message'] = [ + '#type' => 'textarea', + '#title' => $this->t('Event Instance Threshold Message'), + '#description' => $this->t('Enter the message to be displayed. Use @total as a placeholder for the amount of instances being created.'), + '#default_value' => $config->get('threshold_message'), + '#states' => [ + 'visible' => [ + 'input[name="threshold_warning"]' => ['checked' => TRUE], + ], + ], + ]; + + $form['creation']['threshold_prevent_save'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Event Instance Threshold Prevent Save'), + '#description' => $this->t('Prevent saving a series if the threshold is exceeded?'), + '#default_value' => $config->get('threshold_prevent_save'), + '#states' => [ + 'visible' => [ + 'input[name="threshold_warning"]' => ['checked' => TRUE], + ], + ], + ]; + $form['display'] = [ '#type' => 'details', '#title' => $this->t('Event Display'), diff --git a/src/Plugin/Field/FieldType/ConsecutiveRecurringDate.php b/src/Plugin/Field/FieldType/ConsecutiveRecurringDate.php new file mode 100644 index 0000000000000000000000000000000000000000..95bf5ef37fef4352d9115822a365a989eb680595 --- /dev/null +++ b/src/Plugin/Field/FieldType/ConsecutiveRecurringDate.php @@ -0,0 +1,373 @@ +<?php + +namespace Drupal\recurring_events\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\TypedData\DataDefinition; +use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; +use Drupal\recurring_events\RecurringEventsFieldTypeInterface; +use Drupal\recurring_events\Entity\EventSeries; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; + +/** + * Plugin implementation of the 'consecutive_recurring_date' field type. + * + * @FieldType ( + * id = "consecutive_recurring_date", + * label = @Translation("Consecutive Event"), + * description = @Translation("Stores a consecutive recurring date configuration"), + * default_widget = "consecutive_recurring_date", + * default_formatter = "consecutive_recurring_date" + * ) + */ +class ConsecutiveRecurringDate extends DateRangeItem implements RecurringEventsFieldTypeInterface { + + use RecurringEventsFieldTrait; + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = parent::schema($field_definition); + + $schema['columns']['time'] = [ + 'type' => 'varchar', + 'length' => 20, + ]; + + $schema['columns']['end_time'] = [ + 'type' => 'varchar', + 'length' => 20, + ]; + + $schema['columns']['duration'] = [ + 'type' => 'int', + 'unsigned' => TRUE, + ]; + + $schema['columns']['duration_units'] = [ + 'type' => 'varchar', + 'length' => 20, + ]; + + $schema['columns']['buffer'] = [ + 'type' => 'int', + 'unsigned' => TRUE, + ]; + + $schema['columns']['buffer_units'] = [ + 'type' => 'varchar', + 'length' => 20, + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $time = $this->get('time')->getValue(); + $end_time = $this->get('end_time')->getValue(); + $duration = $this->get('duration')->getValue(); + $duration_units = $this->get('duration_units')->getValue(); + $buffer = $this->get('buffer')->getValue(); + $buffer_units = $this->get('buffer_units')->getValue(); + return parent::isEmpty() && empty($time) && empty($end_time) + && empty($duration) && empty($duration_units) + && empty($buffer) && empty($buffer_units); + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties = parent::propertyDefinitions($field_definition); + + // Add our properties. + $properties['time'] = DataDefinition::create('string') + ->setLabel(t('Time')) + ->setDescription(t('The time the events begin each day')); + + $properties['end_time'] = DataDefinition::create('string') + ->setLabel(t('End Time')) + ->setDescription(t('The time the events end each day')); + + $properties['duration'] = DataDefinition::create('integer') + ->setLabel(t('Duration')) + ->setDescription(t('The duration of the events')); + + $properties['duration_units'] = DataDefinition::create('string') + ->setLabel(t('Duration Units')) + ->setDescription(t('The duration unites of the events')); + + $properties['buffer'] = DataDefinition::create('integer') + ->setLabel(t('Buffer')) + ->setDescription(t('The time between consecutive events')); + + $properties['buffer_units'] = DataDefinition::create('string') + ->setLabel(t('Buffer Units')) + ->setDescription(t('The units used for the time between consecutive events')); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function convertEntityConfigToArray(EventSeries $event) { + $config = []; + $config['start_date'] = $event->getConsecutiveStartDate(); + $config['end_date'] = $event->getConsecutiveEndDate(); + $config['time'] = $event->getConsecutiveStartTime(); + $config['end_time'] = $event->getConsecutiveEndTime(); + $config['duration'] = $event->getConsecutiveDuration(); + $config['duration_units'] = $event->getConsecutiveDurationUnits(); + $config['buffer'] = $event->getConsecutiveBuffer(); + $config['buffer_units'] = $event->getConsecutiveBufferUnits(); + return $config; + } + + /** + * {@inheritdoc} + */ + public static function convertFormConfigToArray(FormStateInterface $form_state) { + $config = []; + + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + $user_input = $form_state->getUserInput(); + + if (!empty($user_input['consecutive_recurring_date'][0]['value']['date']) + && !empty($user_input['consecutive_recurring_date'][0]['end_value']['date']) + && !empty($user_input['consecutive_recurring_date'][0]['time'])) { + $time = $user_input['consecutive_recurring_date'][0]['time']; + $time_parts = static::convertTimeTo24hourFormat($time); + $timestamp = implode(':', $time_parts) . ':00'; + + $start_timestamp = $user_input['consecutive_recurring_date'][0]['value']['date'] . 'T' . $timestamp; + $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); + $start_date->setTime(0, 0, 0); + + $end_timestamp = $user_input['consecutive_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; + $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); + $end_date->setTime(0, 0, 0); + + $config['start_date'] = $start_date; + $config['end_date'] = $end_date; + + $config['time'] = $time; + $config['end_time'] = $user_input['consecutive_recurring_date'][0]['end_time']; + $config['duration'] = $user_input['consecutive_recurring_date'][0]['duration']; + $config['duration_units'] = $user_input['consecutive_recurring_date'][0]['duration_units']; + $config['buffer'] = $user_input['consecutive_recurring_date'][0]['buffer']; + $config['buffer_units'] = $user_input['consecutive_recurring_date'][0]['buffer_units']; + } + return $config; + } + + /** + * {@inheritdoc} + */ + public static function buildDiffArray(array $entity_config, array $form_config) { + $diff = []; + + if ($entity_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['start_date'] = [ + 'label' => t('Start Date'), + 'stored' => $entity_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['end_date'] = [ + 'label' => t('End Date'), + 'stored' => $entity_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['time'] !== $form_config['time']) { + $diff['time'] = [ + 'label' => t('Time'), + 'stored' => $entity_config['time'], + 'override' => $form_config['time'], + ]; + } + if ($entity_config['end_time'] !== $form_config['end_time']) { + $diff['end_time'] = [ + 'label' => t('End Time'), + 'stored' => $entity_config['end_time'], + 'override' => $form_config['end_time'], + ]; + } + if ($entity_config['duration'] !== $form_config['duration']) { + $diff['duration'] = [ + 'label' => t('Duration'), + 'stored' => $entity_config['duration'], + 'override' => $form_config['duration'], + ]; + } + if ($entity_config['duration_units'] !== $form_config['duration_units']) { + $diff['duration_units'] = [ + 'label' => t('Duration Units'), + 'stored' => $entity_config['duration_units'], + 'override' => $form_config['duration_units'], + ]; + } + if ($entity_config['buffer'] !== $form_config['buffer']) { + $diff['buffer'] = [ + 'label' => t('Buffer'), + 'stored' => $entity_config['buffer'], + 'override' => $form_config['buffer'], + ]; + } + if ($entity_config['buffer_units'] !== $form_config['buffer_units']) { + $diff['buffer_units'] = [ + 'label' => t('Buffer Units'), + 'stored' => $entity_config['buffer_units'], + 'override' => $form_config['buffer_units'], + ]; + } + + return $diff; + } + + /** + * {@inheritdoc} + */ + public static function calculateInstances(array $form_data) { + $dates = $events_to_create = []; + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + $daily_dates = static::findDailyDatesBetweenDates($form_data['start_date'], $form_data['end_date']); + $time_parts = static::convertTimeTo24hourFormat($form_data['time']); + + if (!empty($daily_dates)) { + foreach ($daily_dates as $daily_date) { + // Set the time of the start date to be the hours and minutes. + $daily_date->setTime($time_parts[0], $time_parts[1]); + // Configure the right timezone. + $daily_date->setTimezone($utc_timezone); + $day_times = static::findSlotsBetweenTimes($daily_date, $form_data); + + if (!empty($day_times)) { + foreach ($day_times as $day_time) { + // Create a clone of this date. + $daily_date_end = clone $day_time; + // Add the number of seconds specified in the duration field. + $daily_date_end->modify('+' . $form_data['duration'] . ' ' . $form_data['duration_units']); + // Set this event to be created. + $events_to_create[$day_time->format('r')] = [ + 'start_date' => $day_time, + 'end_date' => $daily_date_end, + ]; + } + } + } + } + + return $events_to_create; + } + + /** + * Find all the daily date occurrences between two dates. + * + * @param Drupal\Core\Datetime\DrupalDateTime $start_date + * The start date. + * @param Drupal\Core\Datetime\DrupalDateTime $end_date + * The end date. + * @param bool $count_only + * Whether to only return a count. + * + * @return array|int + * An array of matching dates, or a count. + */ + public static function findDailyDatesBetweenDates(DrupalDateTime $start_date, DrupalDateTime $end_date, $count_only = FALSE) { + $dates = []; + $count = 0; + + // Clone the date as we do not want to make changes to the original object. + $start = clone $start_date; + $end = clone $end_date; + + // We want to create events up to and including the last day, so modify the + // end date to be midnight of the next day. + $end->modify('midnight next day'); + + // If the start date is after the end date then we have an invalid range so + // just return nothing. + if ($start->getTimestamp() > $end->getTimestamp()) { + if ($count_only) { + return $count; + } + return $dates; + } + + // Loop through a week at a time, storing the date in the array to return + // until the end date is surpassed. + while ($start->getTimestamp() < $end->getTimestamp()) { + // If we do not clone here we end up modifying the value of start in + // the array and get some funky dates returned. + if (!$count_only) { + $dates[] = clone $start; + } + $start->modify('+1 day'); + $count++; + } + + if ($count_only) { + return $count; + } + return $dates; + } + + /** + * Find all the time slots between two times of a specific day. + * + * @param Drupal\Core\Datetime\DrupalDateTime $date + * The date. + * @param array $form_data + * The form data used to find the time slots. + * @param bool $count_only + * Whether to only return a count. + * + * @return array|int + * An array of time slots, or a count. + */ + public static function findSlotsBetweenTimes(DrupalDateTime $date, array $form_data, $count_only = FALSE) { + $slots = []; + $count = 0; + + $max_time = clone $date; + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + $time_parts = static::convertTimeTo24hourFormat($form_data['end_time']); + $max_time->setTimezone($user_timezone); + $max_time->setTime($time_parts[0], $time_parts[1]); + $max_time->setTimezone($utc_timezone); + + while ($date->getTimestamp() <= $max_time->getTimestamp()) { + $duration = $form_data['duration']; + $duration_units = $form_data['duration_units']; + $buffer = $form_data['buffer']; + $buffer_units = $form_data['buffer_units']; + + if (!$count_only) { + $slots[] = clone $date; + } + $date->modify('+ ' . $duration . ' ' . $duration_units); + $date->modify('+ ' . $buffer . ' ' . $buffer_units); + $count++; + } + + if ($count_only) { + return $count; + } + return $slots; + } + +} diff --git a/src/Plugin/Field/FieldType/DailyRecurringDate.php b/src/Plugin/Field/FieldType/DailyRecurringDate.php new file mode 100644 index 0000000000000000000000000000000000000000..d59c68d26e3578063eb9b0c5523ef7cbb5ad2f49 --- /dev/null +++ b/src/Plugin/Field/FieldType/DailyRecurringDate.php @@ -0,0 +1,227 @@ +<?php + +namespace Drupal\recurring_events\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\TypedData\DataDefinition; +use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; +use Drupal\recurring_events\RecurringEventsFieldTypeInterface; +use Drupal\recurring_events\Entity\EventSeries; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; + +/** + * Plugin implementation of the 'daily_recurring_date' field type. + * + * @FieldType ( + * id = "daily_recurring_date", + * label = @Translation("Daily Event"), + * description = @Translation("Stores a daily recurring date configuration"), + * default_widget = "daily_recurring_date", + * default_formatter = "daily_recurring_date" + * ) + */ +class DailyRecurringDate extends DateRangeItem implements RecurringEventsFieldTypeInterface { + + use RecurringEventsFieldTrait; + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = parent::schema($field_definition); + + $schema['columns']['time'] = [ + 'type' => 'varchar', + 'length' => 20, + ]; + + $schema['columns']['duration'] = [ + 'type' => 'int', + 'unsigned' => TRUE, + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $time = $this->get('time')->getValue(); + $duration = $this->get('duration')->getValue(); + return parent::isEmpty() && empty($time) && empty($duration); + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties = parent::propertyDefinitions($field_definition); + + // Add our properties. + $properties['time'] = DataDefinition::create('string') + ->setLabel(t('Time')) + ->setDescription(t('The time the event begins')); + + $properties['duration'] = DataDefinition::create('integer') + ->setLabel(t('Duration')) + ->setDescription(t('The duration of the event in minutes')); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function convertEntityConfigToArray(EventSeries $event) { + $config = []; + $config['start_date'] = $event->getDailyStartDate(); + $config['end_date'] = $event->getDailyEndDate(); + $config['time'] = $event->getDailyStartTime(); + $config['duration'] = $event->getDailyDuration(); + return $config; + } + + /** + * {@inheritdoc} + */ + public static function convertFormConfigToArray(FormStateInterface $form_state) { + $config = []; + + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + $user_input = $form_state->getUserInput(); + + $time = $user_input['daily_recurring_date'][0]['time']; + $time_parts = static::convertTimeTo24hourFormat($time); + $timestamp = implode(':', $time_parts) . ':00'; + + $start_timestamp = $user_input['daily_recurring_date'][0]['value']['date'] . 'T' . $timestamp; + $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); + $start_date->setTime(0, 0, 0); + + $end_timestamp = $user_input['daily_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; + $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); + $end_date->setTime(0, 0, 0); + + $config['start_date'] = $start_date; + $config['end_date'] = $end_date; + + $config['time'] = $time; + $config['duration'] = $user_input['daily_recurring_date'][0]['duration']; + return $config; + } + + /** + * {@inheritdoc} + */ + public static function buildDiffArray(array $entity_config, array $form_config) { + $diff = []; + + if ($entity_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['start_date'] = [ + 'label' => t('Start Date'), + 'stored' => $entity_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['end_date'] = [ + 'label' => t('End Date'), + 'stored' => $entity_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['time'] !== $form_config['time']) { + $diff['time'] = [ + 'label' => t('Time'), + 'stored' => $entity_config['time'], + 'override' => $form_config['time'], + ]; + } + if ($entity_config['duration'] !== $form_config['duration']) { + $diff['duration'] = [ + 'label' => t('Duration'), + 'stored' => $entity_config['duration'], + 'override' => $form_config['duration'], + ]; + } + + return $diff; + } + + /** + * {@inheritdoc} + */ + public static function calculateInstances(array $form_data) { + $dates = $events_to_create = []; + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + $daily_dates = static::findDailyDatesBetweenDates($form_data['start_date'], $form_data['end_date']); + $time_parts = static::convertTimeTo24hourFormat($form_data['time']); + + if (!empty($daily_dates)) { + foreach ($daily_dates as $daily_date) { + // Set the time of the start date to be the hours and minutes. + $daily_date->setTime($time_parts[0], $time_parts[1]); + // Configure the right timezone. + $daily_date->setTimezone($utc_timezone); + // Create a clone of this date. + $daily_date_end = clone $daily_date; + // Add the number of seconds specified in the duration field. + $daily_date_end->modify('+' . $form_data['duration'] . ' seconds'); + // Set this event to be created. + $events_to_create[$daily_date->format('r')] = [ + 'start_date' => $daily_date, + 'end_date' => $daily_date_end, + ]; + } + } + + return $events_to_create; + } + + /** + * Find all the daily date occurrences between two dates. + * + * @param Drupal\Core\Datetime\DrupalDateTime $start_date + * The start date. + * @param Drupal\Core\Datetime\DrupalDateTime $end_date + * The end date. + * + * @return array + * An array of matching dates. + */ + public static function findDailyDatesBetweenDates(DrupalDateTime $start_date, DrupalDateTime $end_date) { + $dates = []; + + // Clone the date as we do not want to make changes to the original object. + $start = clone $start_date; + $end = clone $end_date; + + // We want to create events up to and including the last day, so modify the + // end date to be midnight of the next day. + $end->modify('midnight next day'); + + // If the start date is after the end date then we have an invalid range so + // just return nothing. + if ($start->getTimestamp() > $end->getTimestamp()) { + return $dates; + } + + // Loop through a week at a time, storing the date in the array to return + // until the end date is surpassed. + while ($start->getTimestamp() < $end->getTimestamp()) { + // If we do not clone here we end up modifying the value of start in + // the array and get some funky dates returned. + $dates[] = clone $start; + $start->modify('+1 day'); + } + + return $dates; + } + +} diff --git a/src/Plugin/Field/FieldType/MonthlyRecurringDate.php b/src/Plugin/Field/FieldType/MonthlyRecurringDate.php index 1f27708658ca28890b84d4474d3f49c4986014fe..21154441d854ea4004f803fec47f6d7f3363105e 100644 --- a/src/Plugin/Field/FieldType/MonthlyRecurringDate.php +++ b/src/Plugin/Field/FieldType/MonthlyRecurringDate.php @@ -4,19 +4,27 @@ namespace Drupal\recurring_events\Plugin\Field\FieldType; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; +use Drupal\recurring_events\RecurringEventsFieldTypeInterface; +use Drupal\recurring_events\Entity\EventSeries; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; /** * Plugin implementation of the 'monthly_recurring_date' field type. * * @FieldType ( * id = "monthly_recurring_date", - * label = @Translation("Monthly Recurring Date"), + * label = @Translation("Monthly Event"), * description = @Translation("Stores a monthly recurring date configuration"), * default_widget = "monthly_recurring_date", * default_formatter = "monthly_recurring_date" * ) */ -class MonthlyRecurringDate extends WeeklyRecurringDate { +class MonthlyRecurringDate extends WeeklyRecurringDate implements RecurringEventsFieldTypeInterface { + + use RecurringEventsFieldTrait; /** * {@inheritdoc} @@ -77,4 +85,329 @@ class MonthlyRecurringDate extends WeeklyRecurringDate { return $properties; } + /** + * {@inheritdoc} + */ + public static function convertEntityConfigToArray(EventSeries $event) { + $config = []; + $config['start_date'] = $event->getMonthlyStartDate(); + $config['end_date'] = $event->getMonthlyEndDate(); + $config['time'] = $event->getMonthlyStartTime(); + $config['duration'] = $event->getMonthlyDuration(); + $config['monthly_type'] = $event->getMonthlyType(); + + switch ($event->getMonthlyType()) { + case 'weekday': + $config['day_occurrence'] = $event->getMonthlyDayOccurrences(); + $config['days'] = $event->getMonthlyDays(); + break; + + case 'monthday': + $config['day_of_month'] = $event->getMonthlyDayOfMonth(); + break; + } + return $config; + } + + /** + * {@inheritdoc} + */ + public static function convertFormConfigToArray(FormStateInterface $form_state) { + $config = []; + + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + $user_input = $form_state->getUserInput(); + + $time = $user_input['monthly_recurring_date'][0]['time']; + $time_parts = static::convertTimeTo24hourFormat($time); + $timestamp = implode(':', $time_parts) . ':00'; + + $start_timestamp = $user_input['monthly_recurring_date'][0]['value']['date'] . 'T' . $timestamp; + $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); + $start_date->setTime(0, 0, 0); + + $end_timestamp = $user_input['monthly_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; + $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); + $end_date->setTime(0, 0, 0); + + $config['start_date'] = $start_date; + $config['end_date'] = $end_date; + + $config['time'] = $time; + $config['duration'] = $user_input['monthly_recurring_date'][0]['duration']; + $config['monthly_type'] = $user_input['monthly_recurring_date'][0]['type']; + + switch ($config['monthly_type']) { + case 'weekday': + $config['day_occurrence'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['day_occurrence'])); + $config['days'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['days'])); + break; + + case 'monthday': + $config['day_of_month'] = array_filter(array_values($user_input['monthly_recurring_date'][0]['day_of_month'])); + break; + } + + return $config; + } + + /** + * {@inheritdoc} + */ + public static function buildDiffArray(array $entity_config, array $form_config) { + $diff = parent::buildDiffArray($entity_config, $form_config); + + if ($entity_config['type'] === 'monthly') { + if ($entity_config['monthly_type'] !== $form_config['monthly_type']) { + $diff['monthly_type'] = [ + 'label' => t('Monthly Type'), + 'stored' => $entity_config['monthly_type'], + 'override' => $form_config['monthly_type'], + ]; + } + if ($entity_config['monthly_type'] === 'weekday') { + if ($entity_config['day_occurrence'] !== $form_config['day_occurrence']) { + $diff['day_occurrence'] = [ + 'label' => t('Day Occurrence'), + 'stored' => implode(',', $entity_config['day_occurrence']), + 'override' => implode(',', $form_config['day_occurrence']), + ]; + } + if ($entity_config['days'] !== $form_config['days']) { + $diff['days'] = [ + 'label' => t('Days'), + 'stored' => implode(',', $entity_config['days']), + 'override' => implode(',', $form_config['days']), + ]; + } + } + else { + if ($entity_config['day_of_month'] !== $form_config['day_of_month']) { + $diff['day_of_month'] = [ + 'label' => t('Day of the Month'), + 'stored' => implode(',', $entity_config['day_of_month']), + 'override' => implode(',', $form_config['day_of_month']), + ]; + } + } + } + + return $diff; + } + + /** + * {@inheritdoc} + */ + public static function calculateInstances(array $form_data) { + $dates = $events_to_create = []; + $time_parts = static::convertTimeTo24hourFormat($form_data['time']); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + if (!empty($form_data['monthly_type'])) { + switch ($form_data['monthly_type']) { + case 'weekday': + // Loop through each weekday occurrence and weekday. + if (!empty($form_data['day_occurrence']) && !empty($form_data['days'])) { + foreach ($form_data['day_occurrence'] as $occurrence) { + foreach ($form_data['days'] as $weekday) { + // Find the occurrence of the specific weekdays within + // each month. + $day_occurrences = static::findWeekdayOccurrencesBetweenDates($occurrence, $weekday, $form_data['start_date'], $form_data['end_date']); + $dates = array_merge($dates, $day_occurrences); + } + } + } + break; + + case 'monthday': + foreach ($form_data['day_of_month'] as $day_of_month) { + $days_of_month = static::findMonthDaysBetweenDates($day_of_month, $form_data['start_date'], $form_data['end_date']); + $dates = array_merge($dates, $days_of_month); + } + break; + + } + + // If valid recurring dates were found. + if (!empty($dates)) { + foreach ($dates as $monthly_date) { + // Set the time of the start date to be the hours and + // minutes. + $monthly_date->setTime($time_parts[0], $time_parts[1]); + // Configure the timezone. + $monthly_date->setTimezone($utc_timezone); + // Create a clone of this date. + $monthly_date_end = clone $monthly_date; + // Add the number of seconds specified in the duration + // field. + $monthly_date_end->modify('+' . $form_data['duration'] . ' seconds'); + // Set this event to be created. + $events_to_create[$monthly_date->format('r')] = [ + 'start_date' => $monthly_date, + 'end_date' => $monthly_date_end, + ]; + } + } + } + + return $events_to_create; + } + + /** + * Find all the day-of-month occurrences between two dates. + * + * @param int $day_of_month + * The day of the month. + * @param Drupal\Core\Datetime\DrupalDateTime $start_date + * The start date. + * @param Drupal\Core\Datetime\DrupalDateTime $end_date + * The end date. + * + * @return array + * An array of matching dates. + */ + public static function findMonthDaysBetweenDates($day_of_month, DrupalDateTime $start_date, DrupalDateTime $end_date) { + $dates = []; + + // Clone the date as we do not want to make changes to the original object. + $start = clone $start_date; + $end = clone $end_date; + + // We want to create events up to and including the last day, so modify the + // end date to be midnight of the next day. + $end->modify('midnight next day'); + + // If the start date is after the end date then we have an invalid range so + // just return nothing. + if ($start->getTimestamp() > $end->getTimestamp()) { + return $dates; + } + + $day_to_check = $day_of_month; + + // If day of month is set to -1 that is the last day of the month, we need + // to calculate how many days a month has. + if ($day_of_month === '-1') { + $day_to_check = $start->format('t'); + } + + // If the day of the month is after the start date. + if ($start->format('d') < $day_to_check) { + $new_date = clone $start; + $curr_month = $new_date->format('m'); + $curr_year = $new_date->format('Y'); + + // Check to see if that date is a valid date. + if (!checkdate($curr_month, $day_to_check, $curr_year)) { + // If not, go find the next valid date. + $start = static::findNextMonthDay($day_of_month, $start); + } + else { + // This is a valid date, so let us start there. + $start->setDate($curr_year, $curr_month, $day_to_check); + } + } + // If the day of the month is in the past. + elseif ($start->format('d') > $day_to_check) { + // Find the next valid start date. + $start = static::findNextMonthDay($day_of_month, $start); + } + + // Loop through each month checking to see if the day of the month is a + // valid day, until the end date has been surpassed. + while ($start->getTimestamp() <= $end->getTimestamp()) { + // If we do not clone here we end up modifying the value of start in + // the array and get some funky dates returned. + $dates[] = clone $start; + // Find the next valid event date. + $start = static::findNextMonthDay($day_of_month, $start); + } + + return $dates; + } + + /** + * Find the next day-of-month occurrence. + * + * @param int $day_of_month + * The day of the month. + * @param Drupal\Core\Datetime\DrupalDateTime $date + * The start date. + * + * @return Drupal\Core\Datetime\DrupalDateTime + * The next occurrence of a specific day of the month. + */ + public static function findNextMonthDay($day_of_month, DrupalDateTime $date) { + $new_date = clone $date; + + $curr_month = $new_date->format('m'); + $curr_year = $new_date->format('Y'); + $next_month = $curr_month; + $next_year = $curr_year; + + do { + $next_month = ($next_month + 1) % 12 ?: 12; + $next_year = $next_month == 1 ? $next_year + 1 : $next_year; + + // If the desired day of the month is the last day, calculate what that + // day is. + if ($day_of_month === '-1') { + $new_date->setDate($next_year, $next_month, '1'); + $day_of_month = $new_date->format('t'); + } + } while (checkdate($next_month, $day_of_month, $next_year) === FALSE); + + $new_date->setDate($next_year, $next_month, $day_of_month); + return $new_date; + } + + /** + * Find all the monthly occurrences of a specific weekday between two dates. + * + * @param string $occurrence + * Which occurrence of the weekday to find. + * @param string $weekday + * The name of the day of the week. + * @param Drupal\Core\Datetime\DrupalDateTime $start_date + * The start date. + * @param Drupal\Core\Datetime\DrupalDateTime $end_date + * The end date. + * + * @return array + * An array of matching dates. + */ + public static function findWeekdayOccurrencesBetweenDates($occurrence, $weekday, DrupalDateTime $start_date, DrupalDateTime $end_date) { + $dates = []; + + // Clone the date as we do not want to make changes to the original object. + $start = clone $start_date; + + // If the start date is after the end date then we have an invalid range so + // just return nothing. + if ($start->getTimestamp() > $end_date->getTimestamp()) { + return $dates; + } + + // Grab the occurrence of the weekday we want for this current month. + $start->modify($occurrence . ' ' . $weekday . ' of this month'); + + // Make sure we didn't just go back in time. + if ($start < $start_date) { + // Go straight to next month. + $start->modify($occurrence . ' ' . $weekday . ' of next month'); + } + + // Loop through a week at a time, storing the date in the array to return + // until the end date is surpassed. + while ($start->getTimestamp() <= $end_date->getTimestamp()) { + // If we do not clone here we end up modifying the value of start in + // the array and get some funky dates returned. + $dates[] = clone $start; + $start->modify($occurrence . ' ' . $weekday . ' of next month'); + } + + return $dates; + } + } diff --git a/src/Plugin/Field/FieldType/WeeklyRecurringDate.php b/src/Plugin/Field/FieldType/WeeklyRecurringDate.php index 02d4ec3fc0342ed97a93c756e3dd8ac9303ec6d1..28cc10494a484e40adadc078d957eb98f82df67d 100644 --- a/src/Plugin/Field/FieldType/WeeklyRecurringDate.php +++ b/src/Plugin/Field/FieldType/WeeklyRecurringDate.php @@ -4,20 +4,27 @@ namespace Drupal\recurring_events\Plugin\Field\FieldType; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; -use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; +use Drupal\recurring_events\RecurringEventsFieldTypeInterface; +use Drupal\recurring_events\Entity\EventSeries; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; /** * Plugin implementation of the 'weekly_recurring_date' field type. * * @FieldType ( * id = "weekly_recurring_date", - * label = @Translation("Weekly Recurring Date"), + * label = @Translation("Weekly Event"), * description = @Translation("Stores a weekly recurring date configuration"), * default_widget = "weekly_recurring_date", * default_formatter = "weekly_recurring_date" * ) */ -class WeeklyRecurringDate extends DateRangeItem { +class WeeklyRecurringDate extends DailyRecurringDate implements RecurringEventsFieldTypeInterface { + + use RecurringEventsFieldTrait; /** * {@inheritdoc} @@ -25,16 +32,6 @@ class WeeklyRecurringDate extends DateRangeItem { public static function schema(FieldStorageDefinitionInterface $field_definition) { $schema = parent::schema($field_definition); - $schema['columns']['time'] = [ - 'type' => 'varchar', - 'length' => 20, - ]; - - $schema['columns']['duration'] = [ - 'type' => 'int', - 'unsigned' => TRUE, - ]; - $schema['columns']['days'] = [ 'type' => 'varchar', 'length' => 255, @@ -47,10 +44,8 @@ class WeeklyRecurringDate extends DateRangeItem { * {@inheritdoc} */ public function isEmpty() { - $time = $this->get('time')->getValue(); - $duration = $this->get('duration')->getValue(); $days = $this->get('days')->getValue(); - return parent::isEmpty() && empty($time) && empty($duration) && empty($days); + return parent::isEmpty() && empty($days); } /** @@ -59,15 +54,6 @@ class WeeklyRecurringDate extends DateRangeItem { public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $properties = parent::propertyDefinitions($field_definition); - // Add our properties. - $properties['time'] = DataDefinition::create('string') - ->setLabel(t('Time')) - ->setDescription(t('The time the event begins')); - - $properties['duration'] = DataDefinition::create('integer') - ->setLabel(t('Duration')) - ->setDescription(t('The duration of the event in minutes')); - $properties['days'] = DataDefinition::create('string') ->setLabel(t('Days')) ->setDescription(t('The days of the week on which this event occurs')); @@ -75,4 +61,178 @@ class WeeklyRecurringDate extends DateRangeItem { return $properties; } + /** + * {@inheritdoc} + */ + public static function convertEntityConfigToArray(EventSeries $event) { + $config = []; + $config['start_date'] = $event->getWeeklyStartDate(); + $config['end_date'] = $event->getWeeklyEndDate(); + $config['time'] = $event->getWeeklyStartTime(); + $config['duration'] = $event->getWeeklyDuration(); + $config['days'] = $event->getWeeklyDays(); + return $config; + } + + /** + * {@inheritdoc} + */ + public static function convertFormConfigToArray(FormStateInterface $form_state) { + $config = []; + + $user_timezone = new \DateTimeZone(drupal_get_user_timezone()); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + $user_input = $form_state->getUserInput(); + + $time = $user_input['weekly_recurring_date'][0]['time']; + $time_parts = static::convertTimeTo24hourFormat($time); + $timestamp = implode(':', $time_parts) . ':00'; + + $start_timestamp = $user_input['weekly_recurring_date'][0]['value']['date'] . 'T' . $timestamp; + $start_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start_timestamp, $user_timezone); + $start_date->setTime(0, 0, 0); + + $end_timestamp = $user_input['weekly_recurring_date'][0]['end_value']['date'] . 'T' . $timestamp; + $end_date = DrupalDateTime::createFromFormat(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end_timestamp, $user_timezone); + $end_date->setTime(0, 0, 0); + + $config['start_date'] = $start_date; + $config['end_date'] = $end_date; + + $config['time'] = $time; + $config['duration'] = $user_input['weekly_recurring_date'][0]['duration']; + $config['days'] = array_filter(array_values($user_input['weekly_recurring_date'][0]['days'])); + return $config; + } + + /** + * {@inheritdoc} + */ + public static function buildDiffArray(array $entity_config, array $form_config) { + $diff = []; + + if ($entity_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['start_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['start_date'] = [ + 'label' => t('Start Date'), + 'stored' => $entity_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['start_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) !== $form_config['end_date']->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)) { + $diff['end_date'] = [ + 'label' => t('End Date'), + 'stored' => $entity_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + 'override' => $form_config['end_date']->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ]; + } + if ($entity_config['time'] !== $form_config['time']) { + $diff['time'] = [ + 'label' => t('Time'), + 'stored' => $entity_config['time'], + 'override' => $form_config['time'], + ]; + } + if ($entity_config['duration'] !== $form_config['duration']) { + $diff['duration'] = [ + 'label' => t('Duration'), + 'stored' => $entity_config['duration'], + 'override' => $form_config['duration'], + ]; + } + + if ($entity_config['days'] !== $form_config['days']) { + $diff['days'] = [ + 'label' => t('Days'), + 'stored' => implode(',', $entity_config['days']), + 'override' => implode(',', $form_config['days']), + ]; + } + + return $diff; + } + + /** + * {@inheritdoc} + */ + public static function calculateInstances(array $form_data) { + $dates = $events_to_create = []; + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + // Loop through each weekday and find occurrences of it in the + // date range provided. + foreach ($form_data['days'] as $weekday) { + $weekday_dates = static::findWeekdaysBetweenDates($weekday, $form_data['start_date'], $form_data['end_date']); + $dates = array_merge($dates, $weekday_dates); + } + $time_parts = static::convertTimeTo24hourFormat($form_data['time']); + + if (!empty($dates)) { + foreach ($dates as $weekly_date) { + // Set the time of the start date to be the hours and minutes. + $weekly_date->setTime($time_parts[0], $time_parts[1]); + // Configure the right timezone. + $weekly_date->setTimezone($utc_timezone); + // Create a clone of this date. + $weekly_date_end = clone $weekly_date; + // Add the number of seconds specified in the duration field. + $weekly_date_end->modify('+' . $form_data['duration'] . ' seconds'); + // Set this event to be created. + $events_to_create[$weekly_date->format('r')] = [ + 'start_date' => $weekly_date, + 'end_date' => $weekly_date_end, + ]; + } + } + + return $events_to_create; + } + + /** + * Find all the weekday occurrences between two dates. + * + * @param string $weekday + * The name of the day of the week. + * @param Drupal\Core\Datetime\DrupalDateTime $start_date + * The start date. + * @param Drupal\Core\Datetime\DrupalDateTime $end_date + * The end date. + * + * @return array + * An array of matching dates. + */ + public static function findWeekdaysBetweenDates($weekday, DrupalDateTime $start_date, DrupalDateTime $end_date) { + $dates = []; + + // Clone the date as we do not want to make changes to the original object. + $start = clone $start_date; + $end = clone $end_date; + + // We want to create events up to and including the last day, so modify the + // end date to be midnight of the next day. + $end->modify('midnight next day'); + + // If the start date is after the end date then we have an invalid range so + // just return nothing. + if ($start->getTimestamp() > $end->getTimestamp()) { + return $dates; + } + + // If the start date is not the weekday we are seeking, jump to the next + // instance of that weekday. + if ($start->format('l') != ucwords($weekday)) { + $start->modify('next ' . $weekday); + } + + // Loop through a week at a time, storing the date in the array to return + // until the end date is surpassed. + while ($start->getTimestamp() < $end->getTimestamp()) { + // If we do not clone here we end up modifying the value of start in + // the array and get some funky dates returned. + $dates[] = clone $start; + $start->modify('+1 week'); + } + + return $dates; + } + } diff --git a/src/Plugin/Field/FieldWidget/ConsecutiveRecurringDateWidget.php b/src/Plugin/Field/FieldWidget/ConsecutiveRecurringDateWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..e139a1731f54b5bf373df6d1e107c2878f274c61 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/ConsecutiveRecurringDateWidget.php @@ -0,0 +1,251 @@ +<?php + +namespace Drupal\recurring_events\Plugin\Field\FieldWidget; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeDefaultWidget; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\HtmlCommand; +use Drupal\recurring_events\Plugin\Field\FieldType\ConsecutiveRecurringDate; + +/** + * Plugin implementation of the 'consecutive recurring date' widget. + * + * @FieldWidget ( + * id = "consecutive_recurring_date", + * label = @Translation("Consecutive recurring date widget"), + * field_types = { + * "consecutive_recurring_date" + * } + * ) + */ +class ConsecutiveRecurringDateWidget extends DateRangeDefaultWidget { + + use RecurringEventsFieldTrait; + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + $config = \Drupal::config('recurring_events.eventseries.config'); + + $element['#type'] = 'container'; + $element['#states'] = [ + 'visible' => [ + ':input[name="recur_type"]' => ['value' => 'consecutive_recurring_date'], + ], + ]; + $element['#element_validate'][] = [$this, 'validateThreshold']; + + $element['value']['#title'] = t('Create Events Between'); + $element['value']['#weight'] = 1; + $element['value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; + $element['value']['#date_date_element'] = 'date'; + $element['value']['#date_time_format'] = ''; + $element['value']['#date_time_element'] = 'none'; + $element['value']['#ajax'] = [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ]; + + $element['end_value']['#title'] = t('And'); + $element['end_value']['#weight'] = 2; + $element['end_value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; + $element['end_value']['#date_date_element'] = 'date'; + $element['end_value']['#date_time_format'] = ''; + $element['end_value']['#date_time_element'] = 'none'; + $element['end_value']['#ajax'] = [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ]; + + $times = $this->getTimeOptions(); + $element['time'] = [ + '#type' => 'select', + '#title' => t('First Event Starts At'), + '#options' => $times, + '#default_value' => $items[$delta]->time ?: reset(array_keys($times)), + '#weight' => 3, + '#ajax' => [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ], + ]; + + $element['end_time'] = [ + '#type' => 'select', + '#title' => t('Final Event Starts At'), + '#options' => $times, + '#default_value' => $items[$delta]->end_time ?: end(array_keys($times)), + '#weight' => 4, + '#ajax' => [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ], + ]; + + $units = $this->getUnitOptions(); + + $element['duration'] = [ + '#type' => 'number', + '#title' => t('Event Duration'), + '#default_value' => $items[$delta]->duration ?: 5, + '#weight' => 5, + '#ajax' => [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ], + ]; + + $element['duration_units'] = [ + '#type' => 'select', + '#title' => t('Event Duration'), + '#options' => $units, + '#default_value' => $items[$delta]->duration_units ?: 'minute', + '#weight' => 6, + '#ajax' => [ + 'callback' => [$this, 'changeDuration'], + 'event' => 'change', + 'wrapper' => 'eventseries-edit-form', + ], + ]; + + // If the form was reloaded due to a trigger, then check the duration + // configuration to ensure we're not going to be creating too many + // instances. + if ($config->get('threshold_warning')) { + $trigger = $form_state->getTriggeringElement(); + if (!empty($trigger)) { + $total = self::checkDuration($form_state); + if ($total > $config->get('threshold_count')) { + $message = $config->get('threshold_message'); + $message = str_replace('@total', $total, $message); + $element['count_warning'] = [ + '#type' => 'markup', + '#prefix' => '<span class="form-item--error-message">', + '#markup' => $message, + '#suffix' => '</span>', + '#weight' => 6, + ]; + } + } + } + + $element['buffer'] = [ + '#type' => 'number', + '#title' => t('Event Buffer'), + '#default_value' => $items[$delta]->buffer ?: 0, + '#weight' => 7, + ]; + + $element['buffer_units'] = [ + '#type' => 'select', + '#title' => t('Event Buffer'), + '#options' => $units, + '#default_value' => $items[$delta]->buffer_units ?: 'minute', + '#weight' => 8, + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + foreach ($values as &$item) { + if (empty($item['value'])) { + $item['value'] = ''; + } + elseif (!$item['value'] instanceof DrupalDateTime) { + $item['value'] = substr($item['value'], 0, 10); + } + else { + $item['value']; + } + if (empty($item['end_value'])) { + $item['end_value'] = ''; + } + elseif (!$item['end_value'] instanceof DrupalDateTime) { + $item['end_value'] = substr($item['end_value'], 0, 10); + } + else { + $item['end_value']; + } + + } + $values = parent::massageFormValues($values, $form, $form_state); + return $values; + } + + /** + * Perform an AJAX reload of the form. + */ + public function changeDuration(array $form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $form_id = $form_state->getBuildInfo()['form_id'] == 'eventseries_edit_form' ? 'eventseries-edit-form' : 'eventseries-add-form'; + $response->addCommand(new HtmlCommand('#' . $form_id, $form)); + return $response; + } + + /** + * Check for unreasonable duration and duration unit values. + */ + public static function checkDuration(FormStateInterface $form_state) { + $day_count = $time_count = $total = 0; + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + $form_data = ConsecutiveRecurringDate::convertFormConfigToArray($form_state); + if (!empty($form_data['start_date']) && !empty($form_data['end_date'])) { + $day_count = ConsecutiveRecurringDate::findDailyDatesBetweenDates($form_data['start_date'], $form_data['end_date'], TRUE); + $time_parts = static::convertTimeTo24hourFormat($form_data['time']); + if (!empty($time_parts)) { + $form_data['start_date']->setTime($time_parts[0], $time_parts[1]); + // Configure the right timezone. + $form_data['start_date']->setTimezone($utc_timezone); + $time_count = ConsecutiveRecurringDate::findSlotsBetweenTimes($form_data['start_date'], $form_data, TRUE); + } + } + + if (!empty($day_count) && !empty($time_count)) { + $total = $day_count * $time_count; + } + + return $total; + } + + /** + * Element validate callback to ensure that the threshold is not exceeded. + * + * @param array $element + * An associative array containing the properties and children of the + * generic form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + */ + public function validateThreshold(array &$element, FormStateInterface $form_state, array &$complete_form) { + $config = \Drupal::config('recurring_events.eventseries.config'); + + if ($config->get('threshold_prevent_save')) { + $total = $this->checkDuration($form_state); + if ($total > $config->get('threshold_count')) { + $message = $config->get('threshold_message'); + $message = str_replace('@total', $total, $message); + $form_state->setError($element, $message); + } + } + } + +} diff --git a/src/Plugin/Field/FieldWidget/DailyRecurringDateWidget.php b/src/Plugin/Field/FieldWidget/DailyRecurringDateWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..69a9c0270f97afdfe38ea2acfd1d81bf29960ce5 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/DailyRecurringDateWidget.php @@ -0,0 +1,104 @@ +<?php + +namespace Drupal\recurring_events\Plugin\Field\FieldWidget; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeDefaultWidget; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\recurring_events\Plugin\RecurringEventsFieldTrait; + +/** + * Plugin implementation of the 'daily recurring date' widget. + * + * @FieldWidget ( + * id = "daily_recurring_date", + * label = @Translation("Daily recurring date widget"), + * field_types = { + * "daily_recurring_date" + * } + * ) + */ +class DailyRecurringDateWidget extends DateRangeDefaultWidget { + + use RecurringEventsFieldTrait; + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + $element['#type'] = 'container'; + $element['#states'] = [ + 'visible' => [ + ':input[name="recur_type"]' => ['value' => 'daily_recurring_date'], + ], + ]; + + $element['value']['#title'] = t('Create Events Between'); + $element['value']['#weight'] = 1; + $element['value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; + $element['value']['#date_date_element'] = 'date'; + $element['value']['#date_time_format'] = ''; + $element['value']['#date_time_element'] = 'none'; + + $element['end_value']['#title'] = t('And'); + $element['end_value']['#weight'] = 2; + $element['end_value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; + $element['end_value']['#date_date_element'] = 'date'; + $element['end_value']['#date_time_format'] = ''; + $element['end_value']['#date_time_element'] = 'none'; + + $times = $this->getTimeOptions(); + $element['time'] = [ + '#type' => 'select', + '#title' => t('Event Start Time'), + '#options' => $times, + '#default_value' => $items[$delta]->time ?: '', + '#weight' => 3, + ]; + + $durations = $this->getDurationOptions(); + $element['duration'] = [ + '#type' => 'select', + '#title' => t('Event Duration'), + '#options' => $durations, + '#default_value' => $items[$delta]->duration ?: '', + '#weight' => 4, + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + foreach ($values as &$item) { + if (empty($item['value'])) { + $item['value'] = ''; + } + elseif (!$item['value'] instanceof DrupalDateTime) { + $item['value'] = substr($item['value'], 0, 10); + } + else { + $item['value']; + } + if (empty($item['end_value'])) { + $item['end_value'] = ''; + } + elseif (!$item['end_value'] instanceof DrupalDateTime) { + $item['end_value'] = substr($item['end_value'], 0, 10); + } + else { + $item['end_value']; + } + + } + $values = parent::massageFormValues($values, $form, $form_state); + return $values; + } + +} diff --git a/src/Plugin/Field/FieldWidget/MonthlyRecurringDateWidget.php b/src/Plugin/Field/FieldWidget/MonthlyRecurringDateWidget.php index 9091e1255f78e7f8647b6e463bf4a3fcccb8c57b..22bef8aa73e7c47f8f400e2a0d2f62812a36bb6c 100644 --- a/src/Plugin/Field/FieldWidget/MonthlyRecurringDateWidget.php +++ b/src/Plugin/Field/FieldWidget/MonthlyRecurringDateWidget.php @@ -28,7 +28,7 @@ class MonthlyRecurringDateWidget extends WeeklyRecurringDateWidget { $element['#type'] = 'container'; $element['#states'] = [ 'visible' => [ - ':input[name="recur_type"]' => ['value' => 'monthly'], + ':input[name="recur_type"]' => ['value' => 'monthly_recurring_date'], ], ]; diff --git a/src/Plugin/Field/FieldWidget/WeeklyRecurringDateWidget.php b/src/Plugin/Field/FieldWidget/WeeklyRecurringDateWidget.php index 4bc34e359f1f3b26a63dda7f1a53fddc03b860ab..23dd15ffaee1a911bff6bf8d18785dfeb715f35a 100644 --- a/src/Plugin/Field/FieldWidget/WeeklyRecurringDateWidget.php +++ b/src/Plugin/Field/FieldWidget/WeeklyRecurringDateWidget.php @@ -3,10 +3,8 @@ namespace Drupal\recurring_events\Plugin\Field\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; -use Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeDefaultWidget; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Datetime\DrupalDateTime; -use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** * Plugin implementation of the 'weekly recurring date' widget. @@ -19,7 +17,7 @@ use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; * } * ) */ -class WeeklyRecurringDateWidget extends DateRangeDefaultWidget { +class WeeklyRecurringDateWidget extends DailyRecurringDateWidget { /** * {@inheritdoc} @@ -30,42 +28,10 @@ class WeeklyRecurringDateWidget extends DateRangeDefaultWidget { $element['#type'] = 'container'; $element['#states'] = [ 'visible' => [ - ':input[name="recur_type"]' => ['value' => 'weekly'], + ':input[name="recur_type"]' => ['value' => 'weekly_recurring_date'], ], ]; - $element['value']['#title'] = t('Create Events Between'); - $element['value']['#weight'] = 1; - $element['value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; - $element['value']['#date_date_element'] = 'date'; - $element['value']['#date_time_format'] = ''; - $element['value']['#date_time_element'] = 'none'; - - $element['end_value']['#title'] = t('And'); - $element['end_value']['#weight'] = 2; - $element['end_value']['#date_date_format'] = DateTimeItemInterface::DATE_STORAGE_FORMAT; - $element['end_value']['#date_date_element'] = 'date'; - $element['end_value']['#date_time_format'] = ''; - $element['end_value']['#date_time_element'] = 'none'; - - $times = $this->getTimeOptions(); - $element['time'] = [ - '#type' => 'select', - '#title' => t('Event Start Time'), - '#options' => $times, - '#default_value' => $items[$delta]->time ?: '', - '#weight' => 3, - ]; - - $durations = $this->getDurationOptions(); - $element['duration'] = [ - '#type' => 'select', - '#title' => t('Event Duration'), - '#options' => $durations, - '#default_value' => $items[$delta]->duration ?: '', - '#weight' => 4, - ]; - $days = $this->getDayOptions(); $element['days'] = [ '#type' => 'checkboxes', @@ -115,95 +81,6 @@ class WeeklyRecurringDateWidget extends DateRangeDefaultWidget { return $values; } - /** - * Generate times based on specific intervals and min/max times. - * - * @return array - * An array of times suitable for a select list. - */ - protected function getTimeOptions() { - $times = []; - - $config = \Drupal::config('recurring_events.eventseries.config'); - // Take interval in minutes, and multiply it by 60 to convert to seconds. - $interval = $config->get('interval') * 60; - $min_time = $config->get('min_time'); - $max_time = $config->get('max_time'); - $format = $config->get('time_format'); - - $min_time = DrupalDateTime::createFromFormat('h:ia', $min_time); - $max_time = DrupalDateTime::createFromFormat('h:ia', $max_time); - - // Convert the mininum time to a number of seconds after midnight. - $lower_hour = $min_time->format('H') * 60 * 60; - $lower_minute = $min_time->format('i') * 60; - $lower = $lower_hour + $lower_minute; - - // Convert the maximum time to a number of seconds after midnight. - $upper_hour = $max_time->format('H') * 60 * 60; - $upper_minute = $max_time->format('i') * 60; - $upper = $upper_hour + $upper_minute; - - $range = range($lower, $upper, $interval); - $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); - - foreach ($range as $time) { - $time_option = DrupalDateTime::createFromTimestamp($time, $utc_timezone); - $times[$time_option->format('h:i a')] = $time_option->format($format); - } - - \Drupal::moduleHandler()->alter('recurring_events_times', $times); - - return $times; - } - - /** - * Return durations for events. - * - * @return array - * An array of durations suitable for a select list. - */ - protected function getDurationOptions() { - $durations = [ - '900' => t('15 minutes'), - '1800' => t('30 minutes'), - '2700' => t('45 minutes'), - '3600' => t('1 hour'), - '5400' => t('1.5 hours'), - '7200' => t('2 hours'), - '9000' => t('2.5 hours'), - '10800' => t('3 hours'), - '12600' => t('3.5 hours'), - '14400' => t('4 hours'), - '16200' => t('4.5 hours'), - '18000' => t('5 hours'), - '19800' => t('5.5 hours'), - '21600' => t('6 hours'), - '25200' => t('7 hours'), - '28800' => t('8 hours'), - '32400' => t('9 hours'), - '36000' => t('10 hours'), - '39600' => t('11 hours'), - '43200' => t('12 hours'), - '46800' => t('13 hours'), - '50400' => t('14 hours'), - '54000' => t('15 hours'), - '57600' => t('16 hours'), - '61200' => t('17 hours'), - '64800' => t('18 hours'), - '68400' => t('19 hours'), - '72000' => t('20 hours'), - '75600' => t('21 hours'), - '79200' => t('22 hours'), - '82800' => t('23 hours'), - '86400' => t('24 hours'), - ]; - - \Drupal::moduleHandler()->alter('recurring_events_durations', $durations); - - return $durations; - } - /** * Return day options for events. * diff --git a/src/Plugin/RecurringEventsFieldTrait.php b/src/Plugin/RecurringEventsFieldTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..eabeba9131e9a0d29e03fb7957d4f643eccf9566 --- /dev/null +++ b/src/Plugin/RecurringEventsFieldTrait.php @@ -0,0 +1,149 @@ +<?php + +namespace Drupal\recurring_events\Plugin; + +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; + +/** + * A trait to provide some time related reusable static methods. + */ +trait RecurringEventsFieldTrait { + + /** + * Generate times based on specific intervals and min/max times. + * + * @return array + * An array of times suitable for a select list. + */ + protected function getTimeOptions() { + $times = []; + + $config = \Drupal::config('recurring_events.eventseries.config'); + // Take interval in minutes, and multiply it by 60 to convert to seconds. + $interval = $config->get('interval') * 60; + $min_time = $config->get('min_time'); + $max_time = $config->get('max_time'); + $format = $config->get('time_format'); + + $min_time = DrupalDateTime::createFromFormat('h:ia', $min_time); + $max_time = DrupalDateTime::createFromFormat('h:ia', $max_time); + + // Convert the mininum time to a number of seconds after midnight. + $lower_hour = $min_time->format('H') * 60 * 60; + $lower_minute = $min_time->format('i') * 60; + $lower = $lower_hour + $lower_minute; + + // Convert the maximum time to a number of seconds after midnight. + $upper_hour = $max_time->format('H') * 60 * 60; + $upper_minute = $max_time->format('i') * 60; + $upper = $upper_hour + $upper_minute; + + $range = range($lower, $upper, $interval); + $utc_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE); + + foreach ($range as $time) { + $time_option = DrupalDateTime::createFromTimestamp($time, $utc_timezone); + $times[$time_option->format('h:i a')] = $time_option->format($format); + } + + \Drupal::moduleHandler()->alter('recurring_events_times', $times); + + return $times; + } + + /** + * Return durations for events. + * + * @return array + * An array of durations suitable for a select list. + */ + protected function getDurationOptions() { + $durations = [ + '900' => t('15 minutes'), + '1800' => t('30 minutes'), + '2700' => t('45 minutes'), + '3600' => t('1 hour'), + '5400' => t('1.5 hours'), + '7200' => t('2 hours'), + '9000' => t('2.5 hours'), + '10800' => t('3 hours'), + '12600' => t('3.5 hours'), + '14400' => t('4 hours'), + '16200' => t('4.5 hours'), + '18000' => t('5 hours'), + '19800' => t('5.5 hours'), + '21600' => t('6 hours'), + '25200' => t('7 hours'), + '28800' => t('8 hours'), + '32400' => t('9 hours'), + '36000' => t('10 hours'), + '39600' => t('11 hours'), + '43200' => t('12 hours'), + '46800' => t('13 hours'), + '50400' => t('14 hours'), + '54000' => t('15 hours'), + '57600' => t('16 hours'), + '61200' => t('17 hours'), + '64800' => t('18 hours'), + '68400' => t('19 hours'), + '72000' => t('20 hours'), + '75600' => t('21 hours'), + '79200' => t('22 hours'), + '82800' => t('23 hours'), + '86400' => t('24 hours'), + ]; + + \Drupal::moduleHandler()->alter('recurring_events_durations', $durations); + + return $durations; + } + + /** + * Convert a time from 12 hour format to 24 hour format. + * + * @var string $time + * The time to convert to 24 hour format. + * + * @return array + * An array of time parts. + */ + protected static function convertTimeTo24hourFormat($time) { + $time_parts = []; + + // Split the start time up to separate out hours and minutes. + $time_parts = explode(':', $time); + // If this is PM then add 12 hours to the hours, unless the time was + // set as noon. + if (strpos($time_parts[1], 'pm') !== FALSE && $time_parts[0] != '12') { + $time_parts[0] += 12; + } + // If this is AM and the time was midnight, set hours to 00. + elseif (strpos($time_parts[1], 'am') !== FALSE && $time_parts[0] == '12') { + $time_parts[0] = '00'; + } + // Strip out AM or PM from the time. + $time_parts[1] = substr($time_parts[1], 0, -3); + + return $time_parts; + } + + /** + * Return unit options for events. + * + * @return array + * An array of unit options suitable for a select list. + */ + protected function getUnitOptions() { + $units = [ + 'second' => t('Second(s)'), + 'minute' => t('Minute(s)'), + 'hour' => t('Hour(s)'), + ]; + + \Drupal::moduleHandler()->alter('recurring_events_units', $units); + + return $units; + } + +} diff --git a/src/RecurringEventsFieldTypeInterface.php b/src/RecurringEventsFieldTypeInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..775c34291fdbd608444714fa4b2eb16c193e1e29 --- /dev/null +++ b/src/RecurringEventsFieldTypeInterface.php @@ -0,0 +1,56 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\recurring_events\Entity\EventSeries; +use Drupal\Core\Form\FormStateInterface; + +/** + * RecurringEventsFieldTypeInterface interface. + */ +interface RecurringEventsFieldTypeInterface { + + /** + * Converts an EventSeries entity's recurring configuration to an array. + * + * @param Drupal\recurring_events\Entity\EventSeries $event + * The stored event series entity. + * + * @return array + * The recurring configuration as an array. + */ + public static function convertEntityConfigToArray(EventSeries $event); + + /** + * Converts a form state object's recurring configuration to an array. + * + * @param Drupal\Core\Form\FormStateInterface $form_state + * The form state of an updated event series entity. + * + * @return array + * The recurring configuration as an array. + */ + public static function convertFormConfigToArray(FormStateInterface $form_state); + + /** + * Calculate the event instances to create for a series. + * + * @param array $form_data + * The updated event series form data. + */ + public static function calculateInstances(array $form_data); + + /** + * Build diff array between stored entity and form state. + * + * @param array $entity_config + * The stored event series configuration. + * @param array $form_config + * The form state, or original, event series configuration. + * + * @return array + * An array of differences. + */ + public static function buildDiffArray(array $entity_config, array $form_config); + +}