date_recur-8.x-2.2/src/Plugin/Field/FieldFormatter/DateRecurBasicFormatter.php

src/Plugin/Field/FieldFormatter/DateRecurBasicFormatter.php
<?php

declare(strict_types=1);

namespace Drupal\date_recur\Plugin\Field\FieldFormatter;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\date_recur\DateRange;
use Drupal\date_recur\Entity\DateRecurInterpreterInterface;
use Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangeDefaultFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Basic recurring date formatter.
 *
 * @FieldFormatter(
 *   id = "date_recur_basic_formatter",
 *   label = @Translation("Date recur basic formatter"),
 *   field_types = {
 *     "date_recur"
 *   }
 * )
 */
class DateRecurBasicFormatter extends DateRangeDefaultFormatter {

  use DependencyTrait;

  protected const COUNT_PER_ITEM_ITEM = 'per_item';

  protected const COUNT_PER_ITEM_ALL = 'all_items';

  /**
   * Date format config ID.
   *
   * @var string|null
   */
  protected ?string $formatType;

  /**
   * Constructs a new DateRecurBasicFormatter.
   *
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the formatter is associated.
   * @param array $settings
   *   The formatter settings.
   * @param string $label
   *   The formatter label display setting.
   * @param string $view_mode
   *   The view mode.
   * @param array $third_party_settings
   *   Third party settings.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
   *   The date formatter service.
   * @param \Drupal\Core\Entity\EntityStorageInterface $dateFormatStorage
   *   The date format entity storage.
   * @param \Drupal\Core\Entity\EntityStorageInterface $dateRecurInterpreterStorage
   *   The date recur interpreter entity storage.
   */
  public function __construct(
      $plugin_id,
      $plugin_definition,
      FieldDefinitionInterface $field_definition,
      array $settings,
      $label,
      $view_mode,
      array $third_party_settings,
      DateFormatterInterface $dateFormatter,
      EntityStorageInterface $dateFormatStorage,
      protected EntityStorageInterface $dateRecurInterpreterStorage,
  ) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $dateFormatter, $dateFormatStorage);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['label'],
      $configuration['view_mode'],
      $configuration['third_party_settings'],
      $container->get('date.formatter'),
      $container->get('entity_type.manager')->getStorage('date_format'),
      $container->get('entity_type.manager')->getStorage('date_recur_interpreter'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings(): array {
    return [
      // Show number of occurrences.
      'show_next' => 5,
      // Whether number of occurrences should be per item or in total.
      'count_per_item' => TRUE,
      // Date format for occurrences.
      'occurrence_format_type' => 'medium',
      // Date format for end date, if same day as start date.
      'same_end_date_format_type' => 'medium',
      'interpreter' => NULL,
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies(): array {
    $this->dependencies = parent::calculateDependencies();

    /** @var string|null $dateFormatId */
    $interpreterId = $this->getSetting('interpreter');
    if ($interpreterId !== NULL && ($interpreter = $this->dateRecurInterpreterStorage->load($interpreterId))) {
      $this->addDependency('config', $interpreter->getConfigDependencyName());
    }

    $dateFormatDependencies = [
      'format_type',
      'occurrence_format_type',
      'same_end_date_format_type',
    ];
    foreach ($dateFormatDependencies as $dateFormatId) {
      $id = $this->getSetting($dateFormatId);
      $dateFormat = $this->dateFormatStorage->load($id);
      if (!$dateFormat) {
        continue;
      }
      $this->addDependency('config', $dateFormat->getConfigDependencyName());
    }

    return $this->dependencies;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state): array {
    $form = parent::settingsForm($form, $form_state);

    $originalFormatType = $form['format_type'];
    unset($form['format_type']);

    // Redefine format type to change the natural order of form fields.
    $form['format_type'] = $originalFormatType;
    $form['format_type']['#title'] = $this->t('Non-Repeating Date format');
    $form['format_type']['#description'] = $this->t('Date format used for field values without repeat rules.');
    $form['occurrence_format_type'] = $originalFormatType;
    $form['occurrence_format_type']['#title'] = $this->t('Start and end date format');
    $form['occurrence_format_type']['#default_value'] = $this->getSetting('occurrence_format_type');
    $form['occurrence_format_type']['#description'] = $this->t('Date format used for field values with repeat rules.');
    $form['same_end_date_format_type'] = $originalFormatType;
    $form['same_end_date_format_type']['#title'] = $this->t('Same day end date format');
    $form['same_end_date_format_type']['#description'] = $this->t('Date format used for end date if field value has repeat rule. Used only if occurs on same calendar day as start date.');
    $form['same_end_date_format_type']['#default_value'] = $this->getSetting('same_end_date_format_type');

    // Redefine separator to change the natural order of form fields.
    $originalSeparator = $form['separator'];
    unset($form['separator']);
    $form['separator'] = $originalSeparator;
    // Change the width of the field if not already set. (Not set by default)
    $form['separator']['#size'] ??= 5;

    // Redefine timezone to change the natural order of form fields.
    $originalTimezoneOverride = $form['timezone_override'];
    unset($form['timezone_override']);
    $form['timezone_override'] = $originalTimezoneOverride;
    $form['timezone_override']['#empty_option'] = $this->t('Use current user timezone');
    $form['timezone_override']['#description'] = $this->t('Change the timezone used for displaying dates (not recommended).');

    $interpreterOptions = array_map(
      fn (DateRecurInterpreterInterface $interpreter): string => $interpreter->label() ?? (string) $this->t('- Missing label -'),
      $this->dateRecurInterpreterStorage->loadMultiple(),
    );
    $form['interpreter'] = [
      '#type' => 'select',
      '#title' => $this->t('Recurring date interpreter'),
      '#description' => $this->t('Choose a plugin for converting rules into a human readable description.'),
      '#default_value' => $this->getSetting('interpreter'),
      '#options' => $interpreterOptions,
      '#required' => FALSE,
      '#empty_option' => $this->t('- Do not show interpreted rule -'),
    ];

    $form['occurrences'] = [
      '#type' => 'container',
      '#attributes' => ['class' => ['container-inline']],
      '#tree' => FALSE,
    ];

    $form['occurrences']['show_next'] = [
      '#field_prefix' => $this->t('Show maximum of'),
      '#field_suffix' => $this->t('occurrences'),
      '#type' => 'number',
      '#min' => 0,
      '#default_value' => $this->getSetting('show_next'),
      '#attributes' => ['size' => 4],
      '#element_validate' => [[static::class, 'validateSettingsShowNext']],
    ];

    $form['occurrences']['count_per_item'] = [
      '#type' => 'select',
      '#options' => [
        static::COUNT_PER_ITEM_ITEM => $this->t('per field item'),
        static::COUNT_PER_ITEM_ALL => $this->t('across all field items'),
      ],
      '#default_value' => $this->getSetting('count_per_item') ? static::COUNT_PER_ITEM_ITEM : static::COUNT_PER_ITEM_ALL,
      '#element_validate' => [[static::class, 'validateSettingsCountPerItem']],
    ];

    return $form;
  }

  /**
   * Validation callback for count_per_item.
   *
   * @param array $element
   *   The element being processed.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   */
  public static function validateSettingsCountPerItem(array &$element, FormStateInterface $form_state, array &$complete_form): void {
    $countPerItem = $element['#value'] == static::COUNT_PER_ITEM_ITEM;
    $arrayParents = array_slice($element['#array_parents'], 0, -2);
    $formatterForm = NestedArray::getValue($complete_form, $arrayParents);
    $parents = $formatterForm['#parents'];
    $parents[] = 'count_per_item';
    $form_state->setValue($parents, $countPerItem);
  }

  /**
   * Validation callback for show_next.
   *
   * @param array $element
   *   The element being processed.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   */
  public static function validateSettingsShowNext(array &$element, FormStateInterface $form_state, array &$complete_form): void {
    $arrayParents = array_slice($element['#array_parents'], 0, -2);
    $formatterForm = NestedArray::getValue($complete_form, $arrayParents);
    $parents = $formatterForm['#parents'];
    $parents[] = 'show_next';
    $form_state->setValue($parents, $element['#value']);
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary(): array {
    $this->formatType = $this->getSetting('format_type');
    $summary = parent::settingsSummary();

    $countPerItem = $this->getSetting('count_per_item');
    $showOccurrencesCount = $this->getSetting('show_next');
    if ($showOccurrencesCount > 0) {
      $summary[] = $this->formatPlural(
        $showOccurrencesCount,
        'Show maximum of @count occurrence @per',
        'Show maximum of @count occurrences @per',
        ['@per' => $countPerItem ? $this->t('per field item') : $this->t('across all field items')],
      );
    }

    $start = new DrupalDateTime('today 9am');
    $endSameDay = clone $start;
    $endSameDay->setTime(17, 0, 0);
    $summary['sample_same_day'] = [
      '#type' => 'inline_template',
      '#template' => '{{ label }}: {{ sample }}',
      '#context' => [
        'label' => $this->t('Same day range'),
        'sample' => $this->buildDateRangeValue($start, $endSameDay, TRUE),
      ],
    ];
    $endDifferentDay = clone $endSameDay;
    $endDifferentDay->modify('+1 day');
    $summary['sample_different_day'] = [
      '#type' => 'inline_template',
      '#template' => '{{ label }}: {{ sample }}',
      '#context' => [
        'label' => $this->t('Different day range'),
        'sample' => $this->buildDateRangeValue($start, $endDifferentDay, TRUE),
      ],
    ];

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode): array {
    // Whether maximum is per field item or in total.
    $isSharedMaximum = !$this->getSetting('count_per_item');
    // Maximum amount of occurrences to be displayed.
    $occurrenceQuota = (int) $this->getSetting('show_next');

    $elements = [];
    foreach ($items as $delta => $item) {
      $value = $this->viewItem($item, $occurrenceQuota);
      $occurrenceQuota -= ($isSharedMaximum ? count($value['#occurrences']) : 0);
      $elements[$delta] = $value;
      if ($occurrenceQuota <= 0) {
        break;
      }
    }

    return $elements;
  }

  /**
   * Generate the output appropriate for a field item.
   *
   * @param \Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem $item
   *   A field item.
   * @param int $maxOccurrences
   *   Maximum number of occurrences to show for this field item.
   *
   * @return array
   *   A render array for a field item.
   */
  protected function viewItem(DateRecurItem $item, $maxOccurrences): array {
    $cacheability = new CacheableMetadata();
    $build = [
      '#theme' => 'date_recur_basic_formatter',
      '#is_recurring' => $item->isRecurring(),
    ];

    $startDate = $item->start_date;
    /** @var \Drupal\Core\Datetime\DrupalDateTime|null $endDate */
    $endDate = $item->end_date ?? $startDate;
    if (!$startDate || !$endDate) {
      return $build;
    }

    $build['#date'] = $this->buildDateRangeValue($startDate, $endDate, FALSE);

    // Render the rule.
    if ($item->isRecurring() && $this->getSetting('interpreter')) {
      /** @var string|null $interpreterId */
      $interpreterId = $this->getSetting('interpreter');
      if ($interpreterId && ($interpreter = $this->dateRecurInterpreterStorage->load($interpreterId))) {
        assert($interpreter instanceof DateRecurInterpreterInterface);
        $rules = $item->getHelper()->getRules();
        $plugin = $interpreter->getPlugin();
        $cacheability->addCacheableDependency($interpreter);
        $build['#interpretation'] = $plugin->interpret($rules, 'en');
      }
    }

    // Occurrences are generated even if the item is not recurring.
    $build['#occurrences'] = array_map(
      function (DateRange $occurrence): array {
        $startDate = DrupalDateTime::createFromDateTime($occurrence->getStart());
        $endDate = DrupalDateTime::createFromDateTime($occurrence->getEnd());
        return $this->buildDateRangeValue(
          $startDate,
          $endDate,
          TRUE,
        );
      },
      $this->getOccurrences($item, $maxOccurrences),
    );

    $cacheability->applyTo($build);
    return $build;
  }

  /**
   * Builds a date range suitable for rendering.
   *
   * @param \Drupal\Core\Datetime\DrupalDateTime $startDate
   *   The start date.
   * @param \Drupal\Core\Datetime\DrupalDateTime $endDate
   *   The end date.
   * @param bool $isOccurrence
   *   Whether the range is an occurrence of a repeating value.
   *
   * @return array
   *   A render array.
   */
  protected function buildDateRangeValue(DrupalDateTime $startDate, DrupalDateTime $endDate, $isOccurrence): array {
    $this->formatType = $isOccurrence ? $this->getSetting('occurrence_format_type') : $this->getSetting('format_type');
    $startDateString = $this->buildDateWithIsoAttribute($startDate);

    // Show the range if start and end are different, otherwise only start date.
    if ($startDate->getTimestamp() === $endDate->getTimestamp()) {
      return $startDateString;
    }
    else {
      // Start date and end date are different.
      $this->formatType = $startDate->format('Ymd') == $endDate->format('Ymd') ?
        $this->getSetting('same_end_date_format_type') :
        $this->getSetting('occurrence_format_type');
      $endDateString = $this->buildDateWithIsoAttribute($endDate);
      return [
        'start_date' => $startDateString,
        'separator' => ['#plain_text' => $this->getSetting('separator')],
        'end_date' => $endDateString,
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function formatDate($date): string {
    assert($date instanceof DrupalDateTime);
    is_string($this->formatType) ?: throw new \LogicException('Date format must be set.');
    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
    return $this->dateFormatter->format($date->getTimestamp(), $this->formatType, '', $timezone);
  }

  /**
   * Get the occurrences for a field item.
   *
   * Occurrences are abstracted out to make it easier for extending formatters
   * to change.
   *
   * @param \Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem $item
   *   A field item.
   * @param int $maxOccurrences
   *   Maximum number of occurrences to render.
   *
   * @return \Drupal\date_recur\DateRange[]
   *   A render array.
   */
  protected function getOccurrences(DateRecurItem $item, $maxOccurrences): array {
    $start = new \DateTime('now');
    return $item->getHelper()
      ->getOccurrences($start, NULL, $maxOccurrences);
  }

}

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

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