View source
<?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;
class DateRecurBasicFormatter extends DateRangeDefaultFormatter {
use DependencyTrait;
protected const COUNT_PER_ITEM_ITEM = 'per_item';
protected const COUNT_PER_ITEM_ALL = 'all_items';
protected $dateRecurInterpreterStorage;
protected $formatType;
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, DateFormatterInterface $dateFormatter, EntityStorageInterface $dateFormatStorage, EntityStorageInterface $dateRecurInterpreterStorage) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $dateFormatter, $dateFormatStorage);
$this->dateRecurInterpreterStorage = $dateRecurInterpreterStorage;
}
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'));
}
public static function defaultSettings() : array {
return [
'show_next' => 5,
'count_per_item' => TRUE,
'occurrence_format_type' => 'medium',
'same_end_date_format_type' => 'medium',
'interpreter' => NULL,
] + parent::defaultSettings();
}
public function calculateDependencies() : array {
$this->dependencies = parent::calculateDependencies();
$interpreterId = $this
->getSetting('interpreter');
if ($interpreterId && ($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;
}
public function settingsForm(array $form, FormStateInterface $form_state) : array {
$form = parent::settingsForm($form, $form_state);
$originalFormatType = $form['format_type'];
unset($form['format_type']);
$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');
$originalSeparator = $form['separator'];
unset($form['separator']);
$form['separator'] = $originalSeparator;
$form['separator']['#size'] = $form['separator']['#size'] ?? 5;
$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(function (DateRecurInterpreterInterface $interpreter) : string {
return $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;
}
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);
}
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']);
}
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;
}
public function viewElements(FieldItemListInterface $items, $langcode) : array {
$isSharedMaximum = !$this
->getSetting('count_per_item');
$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;
}
protected function viewItem(DateRecurItem $item, $maxOccurrences) : array {
$cacheability = new CacheableMetadata();
$build = [
'#theme' => 'date_recur_basic_formatter',
'#is_recurring' => $item
->isRecurring(),
];
$startDate = $item->start_date;
$endDate = $item->end_date ?? $startDate;
if (!$startDate || !$endDate) {
return $build;
}
$build['#date'] = $this
->buildDateRangeValue($startDate, $endDate, FALSE);
if ($item
->isRecurring() && $this
->getSetting('interpreter')) {
$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');
}
}
$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;
}
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);
if ($startDate
->getTimestamp() === $endDate
->getTimestamp()) {
return $startDateString;
}
else {
$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,
];
}
}
protected function formatDate($date) : string {
assert($date instanceof DrupalDateTime);
if (!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);
}
protected function getOccurrences(DateRecurItem $item, $maxOccurrences) : array {
$start = new \DateTime('now');
return $item
->getHelper()
->getOccurrences($start, NULL, $maxOccurrences);
}
}