smart_date-3.1.0-beta1/modules/smart_date_recur/smart_date_recur.module

modules/smart_date_recur/smart_date_recur.module
<?php

/**
 * @file
 * Field hooks for a field that stores a start and end date as timestamps.
 */

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\smart_date_recur\Entity\SmartDateRule;

/**
 * Implements hook_help().
 */
function smart_date_recur_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.smart_date_recur':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Smart Date Recur module adds recurring date functionality to Smart Date fields. For more information, see the <a href=":datetime_do">online documentation for the Smart Date module</a>.', [
        ':datetime_do' => 'https://www.drupal.org/docs/contributed-modules/smart-date',
      ]) . '</p>';
      return $output;
  }
}

/**
 * Implements hook_theme().
 */
function smart_date_recur_theme($existing, $type, $theme, $path) {
  return [
    'smart_date_recurring_formatter' => [
      'variables' => [
        'rule_text' => NULL,
        'past_display' => NULL,
        'next_display' => NULL,
        'upcoming_display' => NULL,
      ],
    ],
    'smart_date_recurring_text_rule' => [
      'variables' => [
        'rule' => NULL,
        'repeat' => NULL,
        'repeat_separator' => NULL,
        'day' => NULL,
        'day_separator' => NULL,
        'days_array' => [],
        'month' => NULL,
        'month_separator' => NULL,
        'time' => NULL,
        'time_separator' => NULL,
        'limit' => NULL,
        'limit_separator' => NULL,
      ],
    ],
    'smart_date_recurring_comma_list' => [
      'variables' => [
        'values' => NULL,
      ],
    ],
  ];
}

/**
 * Prepares variables for recurring smart date templates.
 *
 * Default template: smart-date-recurring-text-rule.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - rule: The smart date rule object.
 *   - repeat: How often the date reoccurs.
 *   - day: The day the date occurs.
 *   - month: The month the date occurs.
 *   - time: The time the date occurs.
 *   - limit: How many times the date reoccurs.
 */
function template_preprocess_smart_date_recurring_text_rule(&$variables) {
  $has_day_or_month = !empty($variables['day']) || !empty($variables['month']);
  $has_time_or_limit = !empty($variables['time']) || !empty($variables['limit']);
  if (!isset($variables['day_separator']) && !empty($variables['repeat']) && $has_day_or_month) {
    // Add spacing if there's a day or month it repeats on.
    $variables['day_separator'] = ' ';
  }
  if (!isset($variables['time_separator']) && $has_day_or_month && $has_time_or_limit) {
    // Add spacing if there's a day or month it repeats on.
    $variables['time_separator'] = ' ';
  }
}

/**
 * Implements hook_entity_delete().
 */
function smart_date_recur_entity_delete($entity) {
  $entity_type = $entity->getEntityTypeId();
  $bundle = $entity->bundle();

  // Check for rules that apply to this entity type and bundle.
  $query = \Drupal::entityTypeManager()->getStorage('smart_date_rule');
  $query_result = $query->getQuery()
    ->condition('entity_type', $entity_type)
    ->condition('bundle', $bundle)
    ->accessCheck(TRUE)
    ->execute();

  // If none exist, nothing to do.
  if (!$query_result) {
    return;
  }

  $field_names = [];
  // Get all the relevant fields for this bundle.
  foreach ($query_result as $rule) {
    $rrule = SmartDateRule::load($rule);
    if ($rrule) {
      $field_name = $rrule->field_name->value;
      if (!in_array($field_name, $field_names)) {
        $field_names[] = $field_name;
      }
    }
  }

  // If no relevant fields for this bundle, nothing to do.
  if (!$field_names) {
    return;
  }

  // Check for relevant field values on the deleted entity.
  foreach ($field_names as $field_name) {
    if (!$values = $entity->$field_name) {
      continue;
    }
    $rules = [];
    // Collect all distinct rules defined for the deleted entity.
    foreach ($values as $value) {
      if ($value->rrule && !in_array($value->rrule, $rules)) {
        $rules[] = $value->rrule;
      }
    }

    // If no rules defined for this field, nothing to do.
    if (!$rules) {
      continue;
    }
    // Delete any rules found.
    foreach ($rules as $rule) {
      if ($rrule = SmartDateRule::load($rule)) {
        $rrule->delete();
      }
    }
  }
}

/**
 * Helper function to add extra fields to Smart Date widgets.
 */
function smart_date_recur_widget_extra_fields(&$element, $item, $context) {
  $default_repeat = '';
  $default_end_count = '';
  $default_end_date = '';
  $limit_type = '';
  $defaults = [
    'interval' => NULL,
    'which' => '',
    'day' => '',
    'byday' => [],
    'byhour' => [],
    'byminute' => [],
  ];
  // Get current user.
  $user = \Drupal::currentUser();
  // Check for permission.
  $recur_permitted = $user->hasPermission('make smart dates recur');
  // Get a helper service.
  $recur_manager = \Drupal::service('smart_date_recur.manager');

  if ($item->rrule) {
    $rrule = SmartDateRule::load($item->rrule);
    if ($rrule) {
      $default_repeat = $rrule->get('freq')->getString();
      if ($rrule->get('limit')->getString()) {
        [$limit_type, $limit_value] = explode('=', $rrule->get('limit')->getString());
        if ($limit_type == 'COUNT') {
          $default_end_count = $limit_value;
        }
        elseif ($limit_type == 'UNTIL') {
          $default_end_date = $limit_value;
        }
      }
      $defaults = $rrule->getParametersArray();
    }
    $element['rrule'] = [
      '#type' => 'hidden',
      '#title' => t('Existing Rule ID'),
      '#value' => $item->rrule,
    ];

    if (!$recur_permitted) {
      $element['repeat'] = [
        '#type' => 'hidden',
        '#title' => t('Existing repeat frequency'),
        '#value' => $default_repeat,
      ];
      $element['repeat-end'] = [
        '#type' => 'hidden',
        '#title' => t('Existing repeat limit type'),
        '#value' => $limit_type,
      ];
      $element['repeat-end-count'] = [
        '#type' => 'hidden',
        '#title' => t('Existing maximum instances'),
        '#value' => $default_end_count,
      ];
      $element['repeat-end-date'] = [
        '#type' => 'hidden',
        '#title' => t('Existing last instance date'),
        '#value' => $default_end_date,
      ];
      if ($rule_text = $rrule->getTextRule()) {
        if (is_array($rule_text)) {
          $rule_text = \Drupal::service('renderer')->render($rule_text);
        }
        $element['repeat-text'] = [
          '#markup' => '<span class="clearfix"></span>
            <h4 class="label">' . t('Repeats') . '</h4>
            <p class="repeat-text"> ' . $rule_text . '</p>',
        ];
      }
      return;
    }

    // Also insert a link to the interface for managing interfaces.
    $modal = $recur_manager->getThirdPartyFallback($context, 'modal', 1);
    $url = Url::fromRoute('smart_date_recur.instances', [
      'rrule' => $item->rrule,
      'modal' => $modal,
    ]);
    $instances_link = Link::fromTextAndUrl(t('Manage Instances'), $url);
    $instances_link = $instances_link->toRenderable();
    // Add some classes.
    $instances_link['#attributes'] = [
      'class' => [
        'button',
        'button--small',
        'manage-instances',
      ],
    ];
    if ($modal) {
      $instances_link['#attributes']['class'][] = 'use-ajax';
      $instances_link['#attached']['library'][] = 'core/drupal.dialog.ajax';
    }

    $instances_link['#weight'] = 100;
    $element['manage-instances'] = $instances_link;
  }
  elseif (!$recur_permitted) {
    return;
  }
  $element['#attached']['library'][] = 'smart_date_recur/smart_date_recur';

  $select_repeat = 'select[name$="[' . $element['#delta'] . '][repeat]"]';
  $select_repeat_end = 'select[name$="[' . $element['#delta'] . '][repeat-end]"]';
  $element['repeat-label'] = [
    '#type' => 'label',
    '#title' => t('Repeats') . ' ',
    '#prefix' => '<div class="clearfix"></div>',
    '#attributes' => ['class' => ['repeat--label']],
  ];
  $element['interval'] = [
    '#type' => 'number',
    '#title' => t('every'),
    '#label_attributes' => ['class' => ['field-interval--label']],
    '#attributes' => ['class' => ['field-interval']],
    '#min' => 1,
    '#step' => 1,
    '#placeholder' => t('# of'),
    '#default_value' => $defaults['interval'],
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'invisible' => [
        $select_repeat => ['value' => ''],
      ],
    ],
  ];
  $repeat_labels = [
    'MINUTELY' => t('by minute'),
    'HOURLY' => t('by hour'),
    'DAILY' => t('daily'),
    'WEEKLY' => t('weekly'),
    'MONTHLY' => t('monthly'),
    'YEARLY' => t('annually'),
  ];
  $labels = ['' => t('never')];
  $allowed_values = $recur_manager->getThirdPartyFallback($context, 'allowed_recur_freq_values', _smart_date_recur_get_freq_defaults());
  foreach ($allowed_values as $value) {
    if (isset($repeat_labels[$value])) {
      $labels[$value] = $repeat_labels[$value];
    }
  }
  $element['repeat'] = [
    '#type' => 'select',
    '#title' => t('Repeats'),
    '#title_display' => 'invisible',
    '#options' => $labels,
    '#default_value' => $default_repeat,
    '#attributes' => ['class' => ['recur-repeat']],
  ];
  $element['repeat-end'] = [
    '#type' => 'select',
    '#title' => t('Ends'),
    '#label_attributes' => ['class' => ['pad-left']],
    '#options' => [
      '' => t('Never'),
      'COUNT' => t('After'),
      'UNTIL' => t('On Date'),
    ],
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'invisible' => [
        $select_repeat => ['value' => ''],
      ],
    ],
    '#default_value' => $limit_type,
  ];
  $element['repeat-end-count'] = [
    '#type' => 'number',
    '#title' => t('Ends after'),
    '#title_display' => t('invisible'),
    '#min' => 2,
    '#step' => 1,
    '#field_suffix' => t('times'),
    '#placeholder' => t('# of'),
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'visible' => [
        $select_repeat => ['!value' => ''],
        $select_repeat_end => ['value' => 'COUNT'],
      ],
    ],
    '#default_value' => $default_end_count,
  ];
  $element['repeat-end-date'] = [
    '#type' => 'date',
    '#title' => t('Ends on date'),
    '#title_display' => t('invisible'),
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'visible' => [
        $select_repeat => ['!value' => ''],
        $select_repeat_end => ['value' => 'UNTIL'],
      ],
    ],
    '#default_value' => $default_end_date,
    '#attributes' => [
      'class' => ['repeat-end-date'],
      'type' => 'date',
    ],
  ];
  $element['repeat-advanced'] = [
    '#type' => 'details',
    '#title' => t('Advanced'),
    '#open' => $defaults['interval'] || $defaults['which'] || $defaults['day'] || $defaults['byday'],
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'invisible' => [
        [$select_repeat => ['value' => '']],
      ],
    ],
  ];
  // Checkboxes to select which weekdays for weekly repeats.
  $days = [
    'SU' => t('Sunday'),
    'MO' => t('Monday'),
    'TU' => t('Tuesday'),
    'WE' => t('Wednesday'),
    'TH' => t('Thursday'),
    'FR' => t('Friday'),
    'SA' => t('Saturday'),
  ];
  // Weekday int. 0-6 (Sun-Sat).
  $firstDayInt = \Drupal::config('system.date')
    ->get('first_day');

  // Rebuild weekday options where system first day is first option in list.
  $days_by_config = array_merge(
      array_slice($days, $firstDayInt),
      // Re-attach weekdays sliced up to the first day.
      array_slice($days, 0, $firstDayInt)
    );

  $element['repeat-advanced']['byday'] = [
    '#type' => 'checkboxes',
    '#title' => t('on days'),
    '#title_display' => t('inline'),
    '#options' => $days_by_config,
    // Populate with any existing values.
    '#default_value' => $defaults['byday'],
    '#states' => [
      // Show only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MINUTELY']],
        [$select_repeat => ['value' => 'HOURLY']],
        [$select_repeat => ['value' => 'DAILY']],
        [$select_repeat => ['value' => 'WEEKLY']],
      ],
    ],
    '#attributes' => ['class' => ['container-inline', 'byday-checkboxes']],
  ];
  $element['repeat-advanced']['which'] = [
    '#type' => 'select',
    '#title' => t('on the'),
    '#options' => [
      '' => t('- Select -'),
      '1' => t('First', [], ['context' => 'date_ordinal']),
      '2' => t('Second', [], ['context' => 'date_ordinal']),
      '3' => t('Third', [], ['context' => 'date_ordinal']),
      '4' => t('Fourth', [], ['context' => 'date_ordinal']),
      '5' => t('Fifth', [], ['context' => 'date_ordinal']),
      '-1' => t('Last', [], ['context' => 'date_ordinal']),
    ],
    '#default_value' => $defaults['which'],
    '#states' => [
      // Show this textarea only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MONTHLY']],
        [$select_repeat => ['value' => 'YEARLY']],
      ],
    ],
  ];
  $element['repeat-advanced']['weekday'] = [
    '#type' => 'select',
    '#title' => t('Weekday'),
    '#title_display' => t('invisible'),
    '#label_attributes' => ['class' => ['pad-left']],
    '#options' => [
      '' => t('- Day (any) -'),
      'SU' => t('Sunday'),
      'MO' => t('Monday'),
      'TU' => t('Tuesday'),
      'WE' => t('Wednesday'),
      'TH' => t('Thursday'),
      'FR' => t('Friday'),
      'SA' => t('Saturday'),
      'MO,TU,WE,TH,FR' => t('Weekday'),
      'SA,SU' => t('Weekend day'),
    ],
    '#default_value' => $defaults['day'],
    '#states' => [
      // Show this textarea only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MONTHLY']],
        [$select_repeat => ['value' => 'YEARLY']],
      ],
    ],
  ];
  $element['repeat-advanced']['restrict-hours'] = [
    '#type' => 'details',
    '#title' => t('Restrict to Specific Hours'),
    '#open' => $defaults['byhour'],
    '#states' => [
      // Show this textarea only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MINUTELY']],
        [$select_repeat => ['value' => 'HOURLY']],
      ],
    ],
  ];
  $hour_options = [];
  for ($i = 0; $i < 24; $i++) {
    $label = sprintf("%02d", $i);
    $hour_options[$label] = $i . ':00';
  }
  $element['repeat-advanced']['restrict-hours']['byhour'] = [
    '#type' => 'checkboxes',
    '#title' => t('Choose hours to include'),
    '#description' => t('Leave empty to allow repeating at any hour.'),
    '#options' => $hour_options,
    // Populate with any existing values.
    '#default_value' => $defaults['byhour'],
    '#states' => [
      // Show only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MINUTELY']],
        [$select_repeat => ['value' => 'HOURLY']],
      ],
    ],
    '#attributes' => ['class' => ['smart-date--hours', 'clearfix']],
  ];
  $element['repeat-advanced']['restrict-minutes'] = [
    '#type' => 'details',
    '#title' => t('Restrict to Specific Minutes on the Hour'),
    '#open' => $defaults['byminute'],
    '#states' => [
      // Show this textarea only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MINUTELY']],
      ],
    ],
  ];
  $minute_options = [];
  for ($i = 0; $i < 60; $i++) {
    $label = sprintf("%02d", $i);
    $minute_options[$label] = ':' . $label;
  }
  $element['repeat-advanced']['restrict-minutes']['byminute'] = [
    '#type' => 'checkboxes',
    '#title' => t('Choose minutes to include'),
    '#description' => t('Leave empty to allow repeating at any value.'),
    '#options' => $minute_options,
    // Populate with any existing values.
    '#default_value' => $defaults['byminute'],
    '#states' => [
      // Show only if the repeat select has an appropriate value.
      'visible' => [
        [$select_repeat => ['value' => 'MINUTELY']],
      ],
    ],
    '#attributes' => ['class' => ['smart-date--minutes', 'clearfix']],
  ];
  $element['#element_validate'][] = [
    'Drupal\smart_date_recur\Entity\SmartDateRule', 'validateRecurring',
  ];
}

/**
 * Add configuration elements for Smart Date field storage.
 */
function smart_date_recur_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) {
  // Only try to add our option to Smart Date fields.
  $field = $form_state->getFormObject()->getEntity();
  if ($field->getType() != 'smartdate') {
    return;
  }

  $messenger = \Drupal::messenger();
  $messenger->addMessage(t('Recurring values can only be used on Smart Date fields that allow unlimited values.'), 'warning');
}

/**
 * Add configuration elements for Smart Date fields.
 */
function smart_date_recur_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {
  // Only try to add our option to Smart Date fields.
  $field = $form_state->getFormObject()->getEntity();
  if ($field->getType() != 'smartdate') {
    return;
  }

  // Only provide the recurring option if unlimited values are allowed.
  $cardinality = $field->getFieldStorageDefinition()->getCardinality();
  if ($cardinality != -1) {
    $messenger = \Drupal::messenger();
    $messenger->addMessage(t('Recurring values can only be used on Smart Date fields that allow unlimited values.'), 'warning');
    return;
  }

  $entity = $form_state->getFormObject()->getEntity();
  if ($entity instanceof BaseFieldDefinition) {
    $allow_recurring = $entity->getSetting('allow_recurring');
    $months = $entity->getSetting('month_limit');
  }
  else {
    $allow_recurring = $entity->getThirdPartySetting('smart_date_recur', 'allow_recurring');
    $months = $entity->getThirdPartySetting('smart_date_recur', 'month_limit');
  }

  $form['third_party_settings']['smart_date_recur'] = [
    '#type' => 'details',
    '#title' => t('Recurring Dates'),
    '#open' => TRUE,
  ];
  $form['third_party_settings']['smart_date_recur']['allow_recurring'] = [
    '#type' => 'checkbox',
    '#title' => t('Allow recurring date values'),
    '#default_value' => $allow_recurring,
  ];
  $form['third_party_settings']['smart_date_recur']['month_limit'] = [
    '#type' => 'number',
    '#title' => t('Months to Extend'),
    '#description' => t('For recurring dates without a specified end, how many months out should instances be generated? If left empty or zero, a default value of 12 will be used.'),
    '#states' => [
      // Show this textarea only if the 'repeat' select has a value.
      'visible' => [
        'input[name="third_party_settings[smart_date_recur][allow_recurring]"]' => ['checked' => TRUE],
      ],
    ],
    '#default_value' => $months ? $months : 12,
  ];
}

/**
 * Helper function to generate additional field deltas based on user inputs.
 */
function smart_date_recur_generate_rows(&$values, $entity_type, $bundle, $field_name, $month_limit) {
  $for_cloning = [];
  foreach ($values as $index => &$item) {
    // Keep track of the original position for sorting later.
    $item['_original_delta'] = $index;
    // Skip empty or non-repeating rows.
    if (empty($item['value']) || empty($item['repeat'])) {
      if (!empty($item['rrule'])) {
        // Removed an existing reoccurrence, so delete.
        $rrule = SmartDateRule::load($item['rrule']);
        if ($rrule) {
          $rrule->delete();
        }
        $item['rrule'] = NULL;
      }
      continue;
    }

    // Format provided values to be rrule-compatible.
    $rrule_values = [
      'freq'        => $item['repeat'],
      'start'       => $item['value'],
      'end'         => $item['end_value'],
      'entity_type' => $entity_type,
      'bundle'      => $bundle,
      'field_name'  => $field_name,
      'parameters'  => '',
    ];
    $limit = '';
    if ($item['repeat-end'] == 'COUNT') {
      $limit = $item['repeat-end-count'];
    }
    elseif ($item['repeat-end'] == 'UNTIL') {
      $limit = $item['repeat-end-date'];
    }
    if ($item['repeat-end'] && $limit) {
      $limit_safe = new FormattableMarkup(':type=:limit', [
        ':type' => $item['repeat-end'],
        ':limit' => $limit,
      ]);
      $rrule_values['limit'] = $limit_safe->__toString();
      $rrule_values['unlimited'] = FALSE;
      $before = NULL;
    }
    else {
      $rrule_values['limit'] = '';
      $rrule_values['unlimited'] = TRUE;
      $before = strtotime('+' . (int) $month_limit . ' months');
    }
    if (!empty($item['interval']) || is_array($item['repeat-advanced'])) {
      $params = [];
      if (!empty($item['interval']) && $item['interval'] > 1) {
        $interval_safe = new FormattableMarkup('INTERVAL=:interval', [':interval' => $item['interval']]);
        $params['interval'] = $interval_safe->__toString();
      }
      // Only parse appropriate advanced options based on selected frequency.
      switch ($rrule_values['freq']) {
        case 'MINUTELY':
          // Use the array of day checkboxes if one of them is checked.
          if (!empty($item['repeat-advanced']['restrict-minutes']['byminute']) && is_array($item['repeat-advanced']['restrict-minutes']['byminute'])) {
            $selected = [];
            foreach ($item['repeat-advanced']['restrict-minutes']['byminute'] as $value) {
              if ($value) {
                $selected[] = $value;
              }
            }
            if ($selected) {
              $by_minute_safe = new FormattableMarkup('BYMINUTE=:byminute', [
                ':byminute' => implode(',', $selected),
              ]);
              $params['by_minute'] = $by_minute_safe->__toString();
            }
          }

        case 'HOURLY':
          // Use the array of day checkboxes if one of them is checked.
          if (!empty($item['repeat-advanced']['restrict-hours']['byhour']) && is_array($item['repeat-advanced']['restrict-hours']['byhour'])) {
            $selected = [];
            foreach ($item['repeat-advanced']['restrict-hours']['byhour'] as $value) {
              if ($value) {
                $selected[] = $value;
              }
            }
            if ($selected) {
              $by_hour_safe = new FormattableMarkup('BYHOUR=:byhour', [
                ':byhour' => implode(',', $selected),
              ]);
              $params['by_hour'] = $by_hour_safe->__toString();
            }
          }

        case 'DAILY':
        case 'WEEKLY':
          // Use the array of day checkboxes if one of them is checked.
          if (!empty($item['repeat-advanced']['byday']) && is_array($item['repeat-advanced']['byday']) && array_sum(array_map('is_string', $item['repeat-advanced']['byday']))) {
            // Remove any zero values.
            $selected = [];
            foreach ($item['repeat-advanced']['byday'] as $value) {
              if ($value) {
                $selected[] = $value;
              }
            }
            $by_day_safe = new FormattableMarkup('BYDAY=:byday', [
              ':byday' => implode(',', $selected),
            ]);
            $params['by_day'] = $by_day_safe->__toString();
          }
          break;

        case 'MONTHLY':
        case 'YEARLY':
          if (!empty($item['repeat-advanced']['which'])) {
            if (empty($item['repeat-advanced']['weekday'])) {
              $by_day_safe = new FormattableMarkup('BYMONTHDAY=:which', [
                ':which' => $item['repeat-advanced']['which'],
              ]);
              $params['by_day'] = $by_day_safe->__toString();
            }
            else {
              // Weekday(s) specified so make the condition appropriately.
              if (strpos($item['repeat-advanced']['weekday'], ',')) {
                // A comma means a special format for multiple days allowed.
                $pattern = 'BYDAY=:day;BYSETPOS=:which';
              }
              else {
                $pattern = 'BYDAY=:which:day';
              }
              $by_day_safe = new FormattableMarkup($pattern, [
                ':which' => $item['repeat-advanced']['which'],
                ':day' => $item['repeat-advanced']['weekday'],
              ]);
              $params['by_day'] = $by_day_safe->__toString();
            }
          }
          if ($rrule_values['freq'] == 'YEARLY') {
            $by_month_safe = new FormattableMarkup('BYMONTH=:which', [
              ':which' => \Drupal::service('date.formatter')->format($rrule_values['start'], 'custom', 'n'),
            ]);
            $params['by_month'] = $by_month_safe->__toString();
          }
          break;
      }
      $rrule_values['parameters'] = implode(';', $params);
    }

    if (!empty($item['rrule'])) {
      // Existing rrule, so retrieve and update values.
      $rrule = SmartDateRule::load($item['rrule']);

      $rrule->set('freq', $rrule_values['freq']);
      $rrule->set('limit', $rrule_values['limit']);
      $rrule->set('unlimited', $rrule_values['unlimited']);
      $rrule->set('start', $rrule_values['start']);
      $rrule->set('end', $rrule_values['end']);
      $rrule->set('parameters', $rrule_values['parameters']);
    }
    else {
      // New rrule, so construct object.
      $rrule = SmartDateRule::create($rrule_values);
    }
    // Ensure the rrule timezone matches the configured timezone on the field,
    // if set.
    if (isset($item['timezone'])) {
      $rrule->setTimezone($item['timezone']);
    }
    // Generate instances.
    $instances = $rrule->getRuleInstances($before);
    $rrule->set('instances', ['data' => $instances]);

    // @todo store unaltered instances instead?
    $rrule->save();
    $item['rrule'] = $rrule->id();
    // Make additional field deltas for the generated instances.
    $for_cloning[$index] = $instances;
  }
  // Now process field values that should be cloned.
  foreach ($for_cloning as $index => $instances) {
    // Now process the generated instances.
    // Use the submitted values as a template.
    $new_item = $values[$index];
    // Replace the first instance, in case there's an override.
    unset($values[$index]);

    foreach ($instances as $rrule_index => $instance) {
      $new_item['value'] = $instance['value'];
      $new_item['end_value'] = $instance['end_value'];
      $new_item['duration'] = ($instance['end_value'] - $instance['value']) / 60;
      $new_item['rrule_index'] = $rrule_index;
      $values[] = $new_item;
    }

  }
  $values = smart_date_array_orderby($values, '_original_delta', SORT_ASC, 'value', SORT_ASC);

  return $values;
}

/**
 * Implements hook_cron().
 *
 * Queues rules without defined limits to have more instances generated.
 */
function smart_date_recur_cron() {
  // Check a time variable to control how often this runs.
  $time_check = \Drupal::state()->get('smart_date_recur_check');
  if ($time_check && $time_check > time()) {
    // Wait until a week has lapsed since the last check.
    return;
  }
  /** @var QueueFactory $queue_factory */
  $queue_factory = \Drupal::service('queue');
  /** @var QueueInterface $queue */
  $queue = $queue_factory->get('smart_date_recur_rules');
  $queue->createQueue();

  $to_process = [];
  // Get the data we need, and group it by impacted entity.
  $ids = \Drupal::entityTypeManager()->getStorage('smart_date_rule')->getRuleIdsToCheck();
  foreach (SmartDateRule::loadMultiple($ids) as $rule) {
    $entity_type = $rule?->entity_type?->getString();
    if (!$entity_type) {
      // Invalid rule, might be an orphaned rule.
      // @todo Delete invalid rule?
      // @todo Log a warning.
      continue;
    }

    // Validate rule before calling getParentEntity
    // Any orphaned rules (invalid entity type, bundle or field name) will
    // prevent new instances from being generated.
    $entity_id = $rule->validateRule() ? $rule->getParentEntity(TRUE) : NULL;
    if (!$entity_id) {
      // Invalid entity, might be an orphaned rule.
      // @todo Log a warning.
      continue;
    }
    $field_name = $rule->field_name->getString();
    $rrid = $rule->id();
    // Group the collected data by impacted entity.
    $to_process[$entity_type][$entity_id][$field_name][$rrid] = $rrid;
  }

  foreach ($to_process as $entity_type => $type_items) {
    foreach ($type_items as $entity_id => $data) {
      // Create new queue item.
      $item = new \stdClass();
      $item->entity_type = $entity_type;
      $item->entity_id = $entity_id;
      $item->data = $data;
      // Add our item to the queue.
      $queue->createItem($item);
    }
  }
  // Set the time to next extend instances.
  $wait = "+7 days";
  \Drupal::state()->set('smart_date_recur_check', strtotime($wait));
}

/**
 * Implements hook_field_widget_third_party_settings_form().
 *
 * Adds extra configuration fields to Smart Date fields configured to recur.
 */
function smart_date_recur_field_widget_third_party_settings_form($plugin, $field_definition, $form_mode, $form, $form_state) {
  if ($field_definition->getType() === 'smartdate') {
    if ($field_definition instanceof BaseFieldDefinition) {
      $allow_recurring = $field_definition->getSetting('allow_recurring');
    }
    else {
      $allow_recurring = $field_definition->getThirdPartySetting('smart_date_recur', 'allow_recurring');
    }
    if ($allow_recurring) {
      $recur_manager = \Drupal::service('smart_date_recur.manager');
      $element['modal'] = [
        '#type' => 'checkbox',
        '#title' => t('Use modal for managing instances'),
        '#default_value' => $recur_manager->getThirdPartyFallback($plugin, 'modal', 1),
      ];
      $element['allowed_recur_freq_values'] = [
        '#type' => 'checkboxes',
        '#title' => t('Allowed frequency values for recurring events'),
        '#default_value' => $recur_manager->getThirdPartyFallback($plugin, 'allowed_recur_freq_values', _smart_date_recur_get_freq_defaults()),
        '#options' => [
          'MINUTELY' => t('by Minutes'),
          'HOURLY' => t('Hourly'),
          'DAILY' => t('Daily'),
          'WEEKLY' => t('Weekly'),
          'MONTHLY' => t('Monthly'),
          'YEARLY' => t('Annually'),
        ],
      ];
      return $element;
    }
  }
}

/**
 * Implements hook_field_formatter_settings_summary_alter().
 */
function smart_date_recur_field_widget_settings_summary_alter(&$summary, $context) {
  $recur_manager = \Drupal::service('smart_date_recur.manager');
  // Append messages to the summary.
  if ($context['field_definition']->getType() == 'smartdate' && $recur_manager->getThirdPartyFallback($context['field_definition'], 'allow_recurring')) {
    if ($recur_manager->getThirdPartyFallback($context['widget'], 'modal', 1)) {
      $summary[] = t('Use modal for managing instances.');
    }
    if ($freq_values = $recur_manager->getThirdPartyFallback($context['widget'], 'allowed_recur_freq_values', _smart_date_recur_get_freq_defaults())) {
      $labels = _smart_date_recur_label_freq_defaults($freq_values);
      $summary[] = t('Dates can recur:') . ' ' . implode(', ', $labels);
    }
  }
}

/**
 * Helper function to centralize default frequency values.
 */
function _smart_date_recur_label_freq_defaults($freq_values) {
  $labels = [];
  $freq_labels = \Drupal::service('smart_date_recur.manager')->getFrequencyLabels();
  foreach ($freq_values as $key => $value) {
    if ($value && isset($freq_labels[$value])) {
      $labels[$key] = $freq_labels[$value];
    }
  }
  return $labels;
}

/**
 * Helper function to centralize default frequency values.
 */
function _smart_date_recur_get_freq_defaults() {
  return ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'];
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc