custom_field-1.0.x-dev/src/Plugin/CustomField/FieldFormatter/DateRangeDefaultFormatter.php

src/Plugin/CustomField/FieldFormatter/DateRangeDefaultFormatter.php
<?php

declare(strict_types=1);

namespace Drupal\custom_field\Plugin\CustomField\FieldFormatter;

use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\custom_field\Enum\DateTimeRangeDisplayOptions;
use Drupal\custom_field\Plugin\CustomField\FieldType\DateRangeType;
use Drupal\custom_field\Plugin\CustomField\FieldType\DateTimeType;
use Drupal\custom_field\Trait\DateRangeTrait;

/**
 * Plugin implementation of the 'Default' formatter for 'daterange' fields.
 *
 * This formatter renders the data range using <time> elements, with
 * configurable date formats (from the list of configured formats) and a
 * separator.
 */
#[FieldFormatter(
  id: 'daterange_default',
  label: new TranslatableMarkup('Default'),
  field_types: [
    'daterange',
  ],
)]
class DateRangeDefaultFormatter extends DateTimeAdvancedFormatter {

  use DateRangeTrait;

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings(): array {
    return [
      'from_to' => DateTimeRangeDisplayOptions::Both->value,
      'separator' => ' - ',
      'end_date_fallback_text' => 'TBD',
      'all_day_label' => 'All day',
      'all_day_separator' => ' | ',
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state): array {
    $elements = parent::settingsForm($form, $form_state);
    $parents = $form['#field_parents'];
    $visibility_path = $form['#visibility_path'];
    $datetime_type = $this->customFieldDefinition->getDatetimeType();
    $elements['from_to'] = [
      '#type' => 'select',
      '#title' => $this->t('Display'),
      '#options' => $this->getFromToOptions(),
      '#default_value' => $this->getSetting('from_to'),
    ];
    $elements['separator'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Date separator'),
      '#description' => $this->t('The string to separate the start and end dates'),
      '#default_value' => $this->getSetting('separator'),
      '#states' => [
        'visible' => [
          'select[name="' . $visibility_path . '[from_to]"]' => ['value' => DateTimeRangeDisplayOptions::Both->value],
        ],
      ],
    ];
    $elements['end_date_fallback_text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('End date fallback text'),
      '#description' => $this->t('Optional text to display when the end date is empty. Leave blank to only show start date.'),
      '#default_value' => $this->getSetting('end_date_fallback_text'),
      '#states' => [
        'visible' => [
          'select[name="' . $visibility_path . '[from_to]"]' => ['value' => DateTimeRangeDisplayOptions::Both->value],
        ],
      ],
    ];
    if (in_array($datetime_type, [DateTimeType::DATETIME_TYPE_DATETIME, DateRangeType::DATETIME_TYPE_ALLDAY])) {
      $elements['all_day'] = [
        '#type' => 'details',
        '#title' => $this->t('All day settings'),
        '#open' => FALSE,
        '#states' => [
          'visible' => [
            'select[name="' . $visibility_path . '[from_to]"]' => ['value' => DateTimeRangeDisplayOptions::Both->value],
          ],
        ],
      ];
      $elements['all_day']['all_day_label'] = [
        '#type' => 'textfield',
        '#title' => $this->t('All day label'),
        '#description' => $this->t('The string to output when date range has been set to run all day. Leave blank to only show the dates.'),
        '#default_value' => $this->getSetting('all_day_label'),
        '#parents' => [...$parents, 'all_day_label'],
      ];
      $elements['all_day']['all_day_separator'] = [
        '#type' => 'textfield',
        '#title' => $this->t('All day separator'),
        '#description' => $this->t('The string to separate the <em>All day label</em> from the dates.'),
        '#default_value' => $this->getSetting('all_day_separator'),
        '#parents' => [...$parents, 'all_day_separator'],
      ];
    }

    return $elements;
  }

  /**
   * Returns a list of possible values for the 'from_to' setting.
   *
   * @return array
   *   A list of 'from_to' options.
   */
  protected function getFromToOptions(): array {
    return [
      DateTimeRangeDisplayOptions::Both->value => $this->t('Display both start and end dates'),
      DateTimeRangeDisplayOptions::StartDate->value => $this->t('Display start date only'),
      DateTimeRangeDisplayOptions::EndDate->value => $this->t('Display end date only'),
    ];
  }

  /**
   * Gets whether the start date should be displayed.
   *
   * @return bool
   *   True if the start date should be displayed. False otherwise.
   */
  protected function startDateIsDisplayed(): bool {
    switch ($this->getSetting('from_to')) {
      case DateTimeRangeDisplayOptions::Both->value:
      case DateTimeRangeDisplayOptions::StartDate->value:
        return TRUE;
    }

    return FALSE;
  }

  /**
   * Gets whether the end date should be displayed.
   *
   * @return bool
   *   True if the end date should be displayed. False otherwise.
   */
  protected function endDateIsDisplayed(): bool {
    switch ($this->getSetting('from_to')) {
      case DateTimeRangeDisplayOptions::Both->value:
      case DateTimeRangeDisplayOptions::EndDate->value:
        return TRUE;
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function formatValue(FieldItemInterface $item, mixed $value): ?array {
    $separator = $this->getSetting('separator');
    $start_date = $value['start_date'];
    $end_date = $value['end_date'];
    if (!$start_date instanceof DrupalDateTime) {
      return NULL;
    }

    $end_date_fallback = $this->getSetting('end_date_fallback_text');
    $timezone = $this->getSetting('timezone_stored') && !empty($value['timezone']) ? $value['timezone'] : NULL;
    if ($this->getSetting('timezone_override')) {
      $timezone = $this->getSetting('timezone_override');
    }
    $is_all_day = FALSE;

    if ($end_date instanceof DrupalDateTime && ($start_date->getTimestamp() !== $end_date->getTimestamp())) {
      $is_all_day = $this->isAllDay($start_date, $end_date);
      $render = $this->renderStartEndWithIsoAttribute($start_date, $separator, $end_date, $timezone);
      $default = $this->renderStartEndWithIsoAttribute($start_date, $separator, $end_date);
    }
    else {
      $render = $this->buildDateWithIsoAttribute($start_date, TRUE, $timezone);
      $default = $this->buildDateWithIsoAttribute($start_date);
      $both_displayed = $this->startDateIsDisplayed() && $this->endDateIsDisplayed();
      // Render the fallback text if applicable.
      if (!$end_date instanceof DrupalDateTime && $both_displayed && !empty($end_date_fallback)) {
        $fallback_markup = [
          '#type' => 'markup',
          '#markup' => ' ' . $end_date_fallback,
        ];
        $render = [
          DateTimeRangeDisplayOptions::StartDate->value => $render,
          'separator' => ['#markup' => $separator],
          DateTimeRangeDisplayOptions::EndDate->value => $fallback_markup,
        ];
        $default = [
          DateTimeRangeDisplayOptions::StartDate->value => $default,
          'separator' => ['#markup' => $separator],
          DateTimeRangeDisplayOptions::EndDate->value => $fallback_markup,
        ];
      }
    }

    if (!$is_all_day && $this->getSetting('user_timezone') && !empty($timezone) && $timezone !== date_default_timezone_get()) {
      return [
        '#theme' => 'item_list',
        '#list_type' => 'ul',
        '#items' => [
          $render,
          $default,
        ],
      ];
    }

    return $render;
  }

  /**
   * Creates a render array with ISO attributes given start/end dates.
   *
   * @param \Drupal\Core\Datetime\DrupalDateTime $start_date
   *   The start date to be rendered.
   * @param string $separator
   *   The separator string.
   * @param \Drupal\Core\Datetime\DrupalDateTime $end_date
   *   The end date to be rendered.
   * @param string|null $timezone
   *   The stored timezone.
   *
   * @return array
   *   A renderable array for a single date time range.
   */
  protected function renderStartEndWithIsoAttribute(DrupalDateTime $start_date, string $separator, DrupalDateTime $end_date, ?string $timezone = NULL): array {
    $element = [];
    $datetime_type = $this->customFieldDefinition->getDatetimeType();
    $all_day_format = '';
    $time_format = '';
    $date_format_parts = $this->getFilteredDateParts();
    $time_format_parts = $this->getFilteredTimeParts();
    $ampm_format = $time_format_parts['am_pm']['format'] ?? NULL;

    // Build format for date.
    if (!empty($date_format_parts)) {
      foreach ($date_format_parts as $value) {
        $all_day_format .= $value['format'];
        if ($value['suffix'] != '') {
          $all_day_format .= PlainTextOutput::renderFromHtml((string) $value['suffix']);
        }
      }
    }

    // Build format for time.
    if (!empty($time_format_parts)) {
      foreach ($time_format_parts as $value) {
        $time_format .= $value['format'];
        if ($value['suffix'] != '') {
          $time_format .= PlainTextOutput::renderFromHtml((string) $value['suffix']);
        }
      }
    }

    $is_all_day = $this->startDateIsDisplayed() && $this->endDateIsDisplayed() && $this->isAllDay($start_date, $end_date);
    if ($is_all_day) {
      $timezone = 'UTC';
      $start_date->setTimezone(timezone_open('UTC'));
      $end_date->setTimezone(timezone_open('UTC'));
    }
    if ($this->startDateIsDisplayed()) {
      $element[DateTimeRangeDisplayOptions::StartDate->value] = $this->buildDateWithIsoAttribute($start_date, FALSE, $timezone);
    }
    if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) {
      $element['separator'] = [
        '#markup' => $separator,
      ];
    }
    if ($this->endDateIsDisplayed()) {
      $element[DateTimeRangeDisplayOptions::EndDate->value] = $this->buildDateWithIsoAttribute($end_date, FALSE, $timezone);
      if ($this->startDateIsDisplayed()) {
        if ($is_all_day) {
          $all_day_label = $this->getSetting('all_day_label') ?? '';
          $all_day_separator = $this->getSetting('all_day_separator') ?? '';
          // Format the start date without time.
          $element[DateTimeRangeDisplayOptions::StartDate->value]['#text'] = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', $all_day_format, $timezone);
          $end_date_text = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', $all_day_format, $timezone);
          if (!empty($all_day_label)) {
            if (!empty($all_day_separator)) {
              // Account for possible html entities as separator.
              $all_day_separator = PlainTextOutput::renderFromHtml((string) $all_day_separator);
            }
            if ($this->isSameDay($start_date, $end_date, $timezone)) {
              $element[DateTimeRangeDisplayOptions::StartDate->value]['#text'] .= $all_day_separator;
              // Replace visual end date with the all day label.
              $end_date_text = $all_day_label;
              // The separator between dates is irrelevant.
              $element['separator'] = NULL;
            }
            else {
              // Append visual end date with the all day label.
              $end_date_text .= $all_day_separator . $all_day_label;
            }
          }
          $element[DateTimeRangeDisplayOptions::EndDate->value]['#text'] = $end_date_text;
        }
        elseif ($this->isSameDay($start_date, $end_date, $timezone)) {
          if (!empty($time_format)) {
            if (!empty($ampm_format)) {
              $start_ampm = $start_date->format($ampm_format);
              $end_ampm = $end_date->format($ampm_format);
              // Simplify the start date am/pm part if same as end date.
              if ($start_ampm === $end_ampm) {
                $start_date_text = $element[DateTimeRangeDisplayOptions::StartDate->value]['#text'];
                // Strip out redundant string from start date.
                $element[DateTimeRangeDisplayOptions::StartDate->value]['#text'] = str_replace($start_ampm, '', $start_date_text);
              }
            }
            // Display just the time for the end date.
            $element[DateTimeRangeDisplayOptions::EndDate->value]['#text'] = $end_date->format($time_format);
          }
        }
      }
    }

    // Append timezone string if applicable.
    if (!$is_all_day && $datetime_type === DateTimeType::DATETIME_TYPE_DATETIME) {
      if ($this->getSetting('display_timezone')) {
        end($element);
        $last_key = key($element);
        $timezone_date = $this->startDateIsDisplayed() ? $start_date : $end_date;
        $element[(string) $last_key]['#suffix'] = ' ' . $this->formatTimezoneDisplay($timezone_date);
      }
    }

    return $element;
  }

}

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

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