Commit beb795dd authored by Martin Anderson-Clutz's avatar Martin Anderson-Clutz
Browse files

Issue #3267299 by mandclu: Token for "next" or "prev" event

parent 0128b44a
Loading
Loading
Loading
Loading
+212 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Provides tokens for the smart_date_recur module.
 */

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\smart_date\Plugin\Field\FieldType\SmartDateFieldItemList;
use Drupal\smart_date_recur\Entity\SmartDateRule;
use Drupal\smart_date_recur\Plugin\Field\FieldFormatter\SmartDateRecurrenceFormatter as RecurFormatter;

/**
 * Implements hook_token_info().
 */
function smart_date_recur_token_info() {

  if (!\Drupal::hasService('token.entity_mapper')) {
    return;
  }

  $types = [];
  $tokens = [];
  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
    if (!$entity_type->entityClassImplements(ContentEntityInterface::class)) {
      continue;
    }
    $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
    if (empty($token_type)) {
      continue;
    }

    // Build property tokens for all smart date fields.
    $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
    foreach ($fields as $field_name => $field) {
      if ($field->getType() != 'smartdate') {
        continue;
      }

      $tokens[$token_type . '-' . $field_name]['prev'] = [
        'name' => t('Previous Instance'),
        'description' => t('Provide the id of a specific smart date format to use it for formattng.'),
        'dynamic' => TRUE,
        'module' => 'smart_date_recur',
      ];
      $tokens[$token_type . '-' . $field_name]['next'] = [
        'name' => t('Next instance'),
        'description' => t('Provide the id of a specific smart date format to use it for formattng.'),
        'dynamic' => TRUE,
        'module' => 'smart_date_recur',
      ];
      $tokens[$token_type . '-' . $field_name]['rule_text'] = [
        'name' => t('Recurring Rule Text'),
        'description' => NULL,
        'module' => 'smart_date_recur',
      ];
      $tokens[$token_type . '-' . $field_name]['upcoming'] = [
        'name' => t('Display upcoming instances'),
        'description' => t('Specify the number of values to return, or zero to return all. Optionally provide the id of a specific smart date format to use it for formattng.'),
        'dynamic' => TRUE,
        'module' => 'smart_date_recur',
      ];
      $tokens[$token_type . '-' . $field_name]['past'] = [
        'name' => t('Display past instances'),
        'description' => t('Specify the number of values to return, or zero to return all. Optionally provide the id of a specific smart date format to use it for formattng.'),
        'dynamic' => TRUE,
        'module' => 'smart_date_recur',
      ];
    }
  }

  return [
    'types' => $types,
    'tokens' => $tokens,
  ];
}

/**
 * Implements hook_tokens().
 */
function smart_date_recur_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {

  $replacements = [];

  if (empty($data['field_property'])) {
    return $replacements;
  }

  foreach ($tokens as $token => $original) {
    $list = $data[$data['field_name']];
    if (!$list instanceof SmartDateFieldItemList) {
      continue;
    }

    // Default to the first item in the list.
    $delta = NULL;
    $parts = explode(':', $token, 2);
    // Test for a delta as the first part.
    if (is_numeric($parts[0])) {
      if (count($parts) > 1) {
        // Save the delta and process the next part.
        $delta = $parts[0];
        $parts = explode(':', $parts[1], 2);
      }
      else {
        continue;
      }
    }
    $property_name = $parts[0];
    // Now parse out the pieces of the token name.
    if ($property_name == 'rule_text') {
      // If a delta specified, only use that value.
      if ($delta !== NULL) {
        $list = [$list->get($delta)];
      }

      $rule_text = [];
      $rrules_processed = [];
      foreach($list as $field) {
        $rrid = $field->get('rrule')->getValue();
        if (!$rrid || in_array($rrid, $rrules_processed)) {
          continue;
        }
        $rule = SmartDateRule::load($rrid);
        $rule_text[] = $rule->getTextRule();
        $rrules_processed[] = $rrid;
      }
      // @todo More structure or markup around mutiple values?
      $output = \Drupal::service('renderer')->render($rule_text);
      $replacements[$original] = $output;
      continue;
    } // End rule_text token processing.

    // Provide upcoming or past instances.
    // Default to show only one instance.
    $num_to_show = 1;
    if (!empty($parts[1])) {
      $parts = explode(':', $parts[1]);
    }
    else {
      $parts = [];
    }
    // Minimize additional markup in the rendered values.
    $settings = [
      'show_next' => FALSE,
      'add_classes' => FALSE,
      'time_wrapper' => FALSE,
      'show_next' => FALSE,
    ];
    switch ($property_name) {
      case 'upcoming':
      case 'past':
        // If showing multiple and no quantity specified, show 10.
        $num_to_show = (count($parts)) ? array_shift($parts) : 10;
      case 'prev':
      case 'next':
        $settings['format'] = $parts[0] ?? 'default';
    }
    // Load the Smart Date Format and merged it into the settings.
    $format = RecurFormatter::loadSmartDateFormat($settings['format']);
    if (is_array($format) && $format) {
      $settings += $format;
    }
    switch ($property_name) {
      case 'prev':
      case 'past':
        $settings['upcoming_display'] = 0;
        $settings['past_display'] = $num_to_show;
        break;

      case 'upcoming':
      case 'next':
        $settings['upcoming_display'] = $num_to_show;
        $settings['past_display'] = 0;
    }

    $elements = [];
    // Key the values by timestamp so they can be sorted.
    foreach ($list as $delta => $item) {
      // Drop any rows with invalid values.
      if (empty($item->value) || empty($item->end_value)) {
        continue;
      }
      // Save the original delta within the item.
      $item->delta = $delta;
      $elements[$item->value] = $item;
    }
    ksort($elements);
    $elements = array_values($elements);
    // Use the Recur Formatter's helper function to find the next instance.
    $next_index = RecurFormatter::findNextInstance($elements, $settings);
    // Use the Recur Formatter's helper function to splice out the instances.
    $values = RecurFormatter::subsetInstances($elements, $next_index, $settings);
    // For 'prev' and 'next' tokens, extract the value out of the list.
    if (in_array($property_name, ['prev', 'next'])) {
      // Return just the date value.
      if ($property_name == 'next') {
        $extraction_key = '#upcoming_display';
      }
      else {
        $extraction_key = '#past_display';
      }
      $set = $values[$extraction_key]['#items'];
      $values = array_pop($set);
    }

    $output = \Drupal::service('renderer')->render($values);
    $replacements[$original] = $output;
  }
  return $replacements;
}
+27 −22
Original line number Diff line number Diff line
@@ -194,7 +194,7 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
        }
        else {
          // No rule so include the item directly.
          $elements[$delta] = $this->buildOutput($delta, $item);
          $elements[$delta] = $this->buildOutput($delta, $item, $settings);
        }
      }
      else {
@@ -210,8 +210,8 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
    if ($force_chrono) {
      ksort($elements);
      $elements = array_values($elements);
      $next_index = $this->findNextInstance($elements);
      return [$this->subsetInstances($elements, $next_index)];
      $next_index = $this->findNextInstance($elements, $settings);
      return [$this->subsetInstances($elements, $next_index, $settings)];
    }
    foreach ($rrules as $rrid => $rrule_collected) {
      $instances = $rrule_collected['instances'];
@@ -252,10 +252,10 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
      else {
        // Output for other recurrences frequencies.
        // Find the 'next' instance after now.
        $next_index = $this->findNextInstance($instances);
        $next_index = $this->findNextInstance($instances, $settings);
      }

      $rrule_output = $this->subsetInstances($instances, $next_index, $within_day);
      $rrule_output = $this->subsetInstances($instances, $next_index, $settings, $within_day);

      $rrule_output['#rule_text']['rule'] = $rrule->getTextRule();
      if (!empty($augmenters['rule'])) {
@@ -287,18 +287,20 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
   *   The values to draw from.
   * @param int $next_index
   *   The value from which to calculate.
   * @param array $settings
   *   The settings used to render the instances.
   * @param bool $within_day
   *   Whether or not to format for recurring within a day.
   *
   * @return array
   *   The formatted render array.
   */
  protected function subsetInstances(array $instances, $next_index, $within_day = FALSE) {
  public static function subsetInstances(array $instances, $next_index, array $settings = [], $within_day = FALSE) {
    $periods = ['past_display', 'upcoming_display'];
    $period_instances = [];

    // Get the specified number of past instances.
    $past_display = $this->settings['past_display'];
    $past_display = $settings['past_display'];

    // Display past instances if set and at least one instances in the past.
    if ($past_display && $next_index) {
@@ -315,7 +317,7 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
      $period_instances['past_display'] = array_slice($instances, $begin, $past_display, TRUE);
    }

    $upcoming_display = $this->settings['upcoming_display'];
    $upcoming_display = $settings['upcoming_display'];
    // Display upcoming instances if set and at least one instance upcoming.
    if ($upcoming_display && $next_index < count($instances) && $next_index != -1) {
      $period_instances['upcoming_display'] = array_slice($instances, $next_index, $upcoming_display, TRUE);
@@ -341,7 +343,7 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
        foreach ($period_instances[$period] as $key => $item) {
          // Check for manual key and use, if set.
          $delta = $item->delta ?? $key;
          $items[$delta] = $this->buildOutput($delta, $item);
          $items[$delta] = static::buildOutput($delta, $item, $settings);
        }
      }
      foreach ($items as $delta => $item) {
@@ -351,7 +353,7 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
        ];
      }
    }
    if ($this->settings['show_next'] && !empty($rrule_output['#upcoming_display']['#items'])) {
    if (!empty($settings['show_next']) && !empty($rrule_output['#upcoming_display']['#items'])) {
      $rrule_output['#next_display'] = array_shift($rrule_output['#upcoming_display']['#items']);
    }
    return $rrule_output;
@@ -364,23 +366,25 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
   *   Numeric key of the output delta.
   * @param object $item
   *   Field values.
   * @param array $settings
   *   The settings used to render the instances.
   *
   * @return array
   *   Render array of the formatted output.
   */
  protected function buildOutput($key, $item) {
  protected static function buildOutput($key, $item, array $settings = []) {
    if (!$item || empty($item->value)) {
      return [];
    }
    $output = static::formatSmartDate($item->value, $item->end_value, $this->settings, $item->timezone);
    if ($this->settings['add_classes']) {
      $this->addRangeClasses($output);
    $output = static::formatSmartDate($item->value, $item->end_value, $settings, $item->timezone);
    if (!empty($settings['add_classes'])) {
      static::addRangeClasses($output);
    }
    if ($this->settings['time_wrapper']) {
      $this->addTimeWrapper($output, $item->value, $item->end_value, $item->timezone);
    if (!empty($settings['time_wrapper'])) {
      static::addTimeWrapper($output, $item->value, $item->end_value, $item->timezone);
    }
    if (!empty($this->settings['augmenters']['instances'])) {
      $this->augmentOutput($output, $this->settings['augmenters']['instances'], $item->value, $item->end_value, $item->timezone, $key, 'instances');
    if (!empty($settings['augmenters']['instances'])) {
      static::augmentOutput($output, $settings['augmenters']['instances'], $item->value, $item->end_value, $item->timezone, $key, 'instances');
    }
    return $output;
  }
@@ -388,11 +392,12 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
  /**
   * Helper function to find the next instance from now in a provided range.
   */
  protected function findNextInstance(array $instances) {
  public static function findNextInstance(array $instances, array $settings = []) {
    $next_index = -1;
    $time = $this->settings['min_date'] ?? time();
    $time = $settings['min_date'] ?? time();
    $current_upcoming = $settings['current_upcoming'] ?? TRUE;
    foreach ($instances as $index => $instance) {
      $date_compare = ($this->settings['current_upcoming']) ? $instance->end_value : $instance->value;
      $date_compare = ($current_upcoming) ? $instance->end_value : $instance->value;
      if ($date_compare > $time) {
        $next_index = $index;
        break;
@@ -404,7 +409,7 @@ class SmartDateRecurrenceFormatter extends SmartDateDefaultFormatter {
  /**
   * Helper function to find the next instance from now in a provided range.
   */
  protected function findNextInstanceByDay(array $dates, $today) {
  public static function findNextInstanceByDay(array $dates, $today) {
    $next_index = -1;
    foreach ($dates as $index => $date) {
      if ($date >= $today) {
+9 −9
Original line number Diff line number Diff line
@@ -10,9 +10,9 @@ trait SmartDateRecurTrait {
  /**
   * Helper function to massage an array for inclusion in output.
   */
  protected function massageForOutput($output, array $settings, $add_classes = NULL) {
    if (!$add_classes) {
      $add_classes = $this->getSetting('add_classes');
  protected static function massageForOutput($output, array $settings, $add_classes = NULL) {
    if ($add_classes === NULL) {
      $add_classes = $settings['add_classes'] ?? FALSE;
    }
    if ($settings['date_first']) {
      // Time should be first so reverse the array.
@@ -20,7 +20,7 @@ trait SmartDateRecurTrait {
    }
    $temp_array['start'] = $output;
    if ($add_classes) {
      $this->addRangeClasses($temp_array);
      static::addRangeClasses($temp_array);
    }
    return $temp_array['start'];
  }
@@ -28,10 +28,10 @@ trait SmartDateRecurTrait {
  /**
   * Helper function to create a collapsed display of events within a day.
   */
  protected function formatWithinDay(array $instances, array $settings) {
    $settings_notime = $this->settingsFormatNoTime($settings);
    $settings_nodate = $this->settingsFormatNoDate($settings);
    $settings_notz = $this->settingsFormatNoTz($settings_nodate);
  protected static function formatWithinDay(array $instances, array $settings) {
    $settings_notime = static::settingsFormatNoTime($settings);
    $settings_nodate = static::settingsFormatNoDate($settings);
    $settings_notz = static::settingsFormatNoTz($settings_nodate);
    $output = [];
    foreach ($instances as $time_set) {
      $this_output = [];
@@ -47,7 +47,7 @@ trait SmartDateRecurTrait {
      $this_output['date']['#markup'] = static::formatSmartDate($last_time->value, $last_time->value, $settings_notime, $last_time->timezone, 'string');
      $this_output['#attributes']['class'] = ['smart-date--daily-times'];
      $this_output['#type'] = 'container';
      $output[] = $this->massageForOutput($this_output, $settings);
      $output[] = static::massageForOutput($this_output, $settings);
    }
    return $output;
  }
+3 −3
Original line number Diff line number Diff line
@@ -180,7 +180,7 @@ trait SmartDateTrait {
   * @param array $instance
   *   The render array of the formatted date range.
   */
  protected function addRangeClasses(array &$instance) {
  protected static function addRangeClasses(array &$instance) {
    // Array to define where wrapper parts should be skipped, for a range.
    $skip = [];
    // If a time range within a day, make a single wrapper around the times.
@@ -222,7 +222,7 @@ trait SmartDateTrait {
   * @param bool $add_classes
   *   Whether or not the field is also adding class wrappers.
   */
  protected function addTimeWrapper(array &$instance, $start_ts, $end_ts, $timezone = NULL, $add_classes = FALSE) {
  protected static function addTimeWrapper(array &$instance, $start_ts, $end_ts, $timezone = NULL, $add_classes = FALSE) {
    $times = [
      'start' => $start_ts,
      'end' => $end_ts,
@@ -236,7 +236,7 @@ trait SmartDateTrait {
    }
    foreach (['start', 'end'] as $part) {
      if (isset($instance[$part])) {
        if ($this->isAllDay($start_ts, $end_ts, $timezone)) {
        if (static::isAllDay($start_ts, $end_ts, $timezone)) {
          $format = 'Y-m-d';
        }
        else {