diff --git a/office_hours.module b/office_hours.module index fde2afc9ca7bae95c2504d4a71874247c29fb650..ba1fab84aa2bf3ffd427f147928507a5e4407422 100644 --- a/office_hours.module +++ b/office_hours.module @@ -97,6 +97,13 @@ function office_hours_views_query_substitutions(ViewExecutable $view) { return OfficeHoursStatusFilter::viewsQuerySubstitutions($view); } +/** + * Implements hook_views_pre_execute(). + */ +function office_hours_views_pre_execute(ViewExecutable $view) { + return OfficeHoursStatusFilter::viewsPreExecute($view); +} + /** * Implements hook_views_post_execute(). */ diff --git a/src/Plugin/Field/FieldType/OfficeHoursItem.php b/src/Plugin/Field/FieldType/OfficeHoursItem.php index b8fa0d091d5f0b60c6def81d6cf946cabfddc16d..f28d687160168df02291cf213990adf8fc667a23 100644 --- a/src/Plugin/Field/FieldType/OfficeHoursItem.php +++ b/src/Plugin/Field/FieldType/OfficeHoursItem.php @@ -183,7 +183,7 @@ class OfficeHoursItem extends OfficeHoursItemBase { * @return int * a predefined constant with the status. */ - public function getStatus($time) { + public function getStatus($time = 0): int { $status = static::UNDEFINED; $now_weekday = OfficeHoursDateHelper::getWeekday($time); diff --git a/src/Plugin/Field/FieldType/OfficeHoursItemBase.php b/src/Plugin/Field/FieldType/OfficeHoursItemBase.php index ff06e1f49e60b20b3f19edacf3b7382acaefab1e..554ce71dce12e34e45721de3d09c12ec37085df5 100644 --- a/src/Plugin/Field/FieldType/OfficeHoursItemBase.php +++ b/src/Plugin/Field/FieldType/OfficeHoursItemBase.php @@ -71,6 +71,15 @@ class OfficeHoursItemBase extends FieldItemBase { ->addConstraint('Length', ['max' => 255]) ->setDescription("Stores the comment."); + // @todo #3501772 Convert to complex datatype, for usingkey/value formatter + //$properties['status'] = DataDefinition::create('field_item:list_string') + //$properties['status'] = DataDefinition::create('map') + $properties['status'] = DataDefinition::create('integer') + ->setLabel(t('Status')) + ->setDescription(t('Is the entity currently open, currently closed or never open.')) + ->setComputed(TRUE) + ->setClass('\Drupal\office_hours\Plugin\Field\FieldType\OfficeHoursStatus'); + return $properties; } diff --git a/src/Plugin/Field/FieldType/OfficeHoursItemList.php b/src/Plugin/Field/FieldType/OfficeHoursItemList.php index e44cd09555a4ccf3f7ee95a03b7c600b31947393..7b231b2bba7c80a0b98a783e14d5b69124b49e41 100644 --- a/src/Plugin/Field/FieldType/OfficeHoursItemList.php +++ b/src/Plugin/Field/FieldType/OfficeHoursItemList.php @@ -269,6 +269,25 @@ class OfficeHoursItemList extends FieldItemList implements OfficeHoursItemListIn return count($exception_days); } + /** + * {@inheritdoc} + */ + public function getStatus($time = 0): int { + $items = $this; + + switch (TRUE) { + case is_null($items): + case $items->isEmpty(): + $status = OfficeHoursStatus::NEVER; + break; + + default: + $status = $items->isOpen($time); + break; + } + return $status; + } + /** * {@inheritdoc} */ diff --git a/src/Plugin/Field/FieldType/OfficeHoursItemListInterface.php b/src/Plugin/Field/FieldType/OfficeHoursItemListInterface.php index 137ca17e255c580e927b0b4d683eb3778ac960ce..e2cccdd6af178adea3de17a8e309463210ebbdbf 100644 --- a/src/Plugin/Field/FieldType/OfficeHoursItemListInterface.php +++ b/src/Plugin/Field/FieldType/OfficeHoursItemListInterface.php @@ -122,6 +122,17 @@ interface OfficeHoursItemListInterface extends FieldItemListInterface { */ public function countExceptionDays(); + /** + * Returns if an entity currently open, currently closed or never open. + * + * @param int $time + * A timestamp. Might be adapted for User Timezone. + * + * @return int + * a predefined constant with the status. + */ + public function getStatus($time = 0): int; + /** * Determines if the Entity is Open or Closed. * diff --git a/src/Plugin/Field/FieldType/OfficeHoursStatus.php b/src/Plugin/Field/FieldType/OfficeHoursStatus.php new file mode 100644 index 0000000000000000000000000000000000000000..a28f49106bbc5df8fcfa0a541cb5bcaf9589db79 --- /dev/null +++ b/src/Plugin/Field/FieldType/OfficeHoursStatus.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\office_hours\Plugin\Field\FieldType; + +use Drupal\Core\TypedData\TypedData; + +/** + * A computed property for displaying the open/closed status of a field. + * + * Plugin implementation of the 'list_string' field type. + * @see OfficeHoursItemBase~propertyDefinitions() + */ +class OfficeHoursStatus extends TypedData { + // @todo #3501772 Convert to complex datatype, for usingkey/value formatter + // Mapitem, ListStringItem, Map, TypedData { + + public const string ANY = 'all'; + public const bool CLOSED = FALSE; + public const bool OPEN = TRUE; + public const int NEVER = 2; + + /** + * Cached open/closed status. + * + * @var int + */ + protected $value = FALSE; + + /** + * Implements \Drupal\Core\TypedData\TypedDataInterface::getValue(). + */ + public function getValue() { + $items = $this->getParent()->getParent(); + $status = $items->getStatus(); + $this->setValue((int) $status); + return $status; + } + +} diff --git a/src/Plugin/views/field/FieldBase.php b/src/Plugin/views/field/FieldBase.php index bbf77059e7bd5db73496c32a34ed30c04babc417..ab837a7a170b61c31f9b6a56d33b1132f0565157 100644 --- a/src/Plugin/views/field/FieldBase.php +++ b/src/Plugin/views/field/FieldBase.php @@ -26,6 +26,7 @@ class FieldBase extends FieldPluginBase { $columns = [ 'season' => 'office_hours_season', 'timeslot' => 'office_hours_timeslot', + 'status' => 'office_hours_status', ]; foreach ($data as $table_name => $table_data) { @@ -91,6 +92,30 @@ class FieldBase extends FieldPluginBase { unset($field_data['filter']); unset($field_data['sort']); + // Extend 'Status'. + $column = 'status'; + $label = $field_label . ' - Status'; + $real_field = 'delta'; + $title = t('@label (@name:@column)', + ['@label' => $label, '@name' => $field_name, '@column' => $column] + ); + $title_short = t('@label:@column', + ['@label' => $label, '@column' => $column]); + + // @todo Do not take over all field attributes. + $field_data = &$data[$table_name][$field_name . "_$column"]; + // Use ?? to avoid TypeError: Unsupported operand types: array + null. + // This may have side effects, since data should exist. @see #3421574. + $field_data += $data[$table_name][$field_name] ?? []; + $field_data['field'] += $data[$table_name][$real_field]['field'] ?? []; + $field_data['field']['real field'] = $real_field; + $field_data['field']['property'] = $real_field; + $field_data['title'] = $title; + $field_data['title short'] = $title_short; + unset($field_data['argument']); + // Filter is set in \views\filter\OfficeHoursStatusFilter. + // unset($field_data['filter']); + unset($field_data['sort']); } } @@ -157,7 +182,7 @@ class FieldBase extends FieldPluginBase { $entity = $this->getEntity($values); // Entities with no / empty office_hours will have delta = NULL. - $delta = $values->{$table . '_delta'} ?? NULL; + $delta = $values->{"{$table}_delta"} ?? NULL; // So, no need to check for $entity->hasField($field_name). if (!is_null($delta)) { $items = $entity->get($field_name); diff --git a/src/Plugin/views/field/Status.php b/src/Plugin/views/field/Status.php new file mode 100644 index 0000000000000000000000000000000000000000..658c1dfeba23970a20486dfb8de37520415bc81a --- /dev/null +++ b/src/Plugin/views/field/Status.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\office_hours\Plugin\views\field; + +use Drupal\office_hours\Plugin\Field\FieldType\OfficeHoursStatus; +use Drupal\views\ResultRow; + +/** + * Computed field to display the open/closed status. + * + * @ingroup views_field_handlers + * + * @ViewsField("office_hours_status") + * + * @see https://www.drupal.org/docs/drupal-apis/entity-api/dynamicvirtual-field-values-using-computed-field-property-classes + */ +class Status extends FieldBase { + + /** + * Called to add the field to a query. + */ + public function query() { + // Do not add the computed subfield to the query. + } + + /** + * {@inheritdoc} + */ + public function render(ResultRow $values) { + $entity = $values->_entity; + $field_name = $this->configuration['field_name']; + $property_name = 'status'; + + $result = $entity->{$field_name}->{$property_name} ?? OfficeHoursStatus::NEVER; + return $result; // $this->sanitizeValue($result); + } + +} diff --git a/src/Plugin/views/filter/OfficeHoursStatusFilter.php b/src/Plugin/views/filter/OfficeHoursStatusFilter.php index de117154185a94a0f253246b25a0d87f633d8eb8..a9fcf69ddfb24ef5d424f134042d4c87961e9885 100644 --- a/src/Plugin/views/filter/OfficeHoursStatusFilter.php +++ b/src/Plugin/views/filter/OfficeHoursStatusFilter.php @@ -3,12 +3,13 @@ namespace Drupal\office_hours\Plugin\views\filter; use Drupal\field\FieldStorageConfigInterface; +use Drupal\office_hours\Plugin\Field\FieldType\OfficeHoursStatus; use Drupal\views\Plugin\views\cache\CachePluginBase; use Drupal\views\Plugin\views\filter\ManyToOne; use Drupal\views\ViewExecutable; /** - * Filter by open/closed status. + * Views Filter by open/closed status. * * @ingroup views_filter_handlers * @@ -20,17 +21,14 @@ use Drupal\views\ViewExecutable; * @see https://www.webomelette.com/creating-custom-views-filter-drupal-8 * @see https://www.drupal.org/docs/drupal-apis/entity-api/dynamicvirtual-field-values-using-computed-field-property-classes * @see https://drupal.stackexchange.com/questions/249963/how-to-add-a-custom-views-filter-handler-for-a-specific-field + * @see https://drupal.stackexchange.com/questions/291236/creating-a-custom-field-with-dynamic-virtual-computed-property-value */ class OfficeHoursStatusFilter extends ManyToOne { /* * Duplicate of the @ViewsFilter annotation. */ - const VIEWS_FILTER_ID = "office_hours_is_open"; - const ANY = 'all'; - const CLOSED = FALSE; - const OPEN = TRUE; - const NEVER = 2; + public const string VIEWS_FILTER_ID = "office_hours_is_open"; /** * Implements hook_field_views_data(). @@ -65,10 +63,10 @@ class OfficeHoursStatusFilter extends ManyToOne { */ public function getValueOptions() { $this->valueOptions = [ - static::ANY => $this->t('Select all'), - static::OPEN => $this->t('Open now'), - static::CLOSED => $this->t('Temporarily closed'), - static::NEVER => $this->t('Permanently closed'), + OfficeHoursStatus::ANY => $this->t('Select all'), + OfficeHoursStatus::OPEN => $this->t('Open now'), + OfficeHoursStatus::CLOSED => $this->t('Temporarily closed'), + OfficeHoursStatus::NEVER => $this->t('Permanently closed'), ]; return $this->valueOptions; @@ -106,60 +104,43 @@ class OfficeHoursStatusFilter extends ManyToOne { return; } - $filterValue = $filter->value; - $filterValue = array_filter($filterValue, function ($statusValue) { - return $statusValue !== 0; - }); - - if (in_array(static::ANY, $filterValue)) { - return; - } - - $fieldName = $filter->realField; + // Remove duplicate rows from the view. $previous_id = -1; - /** @var \Drupal\views\ResultRow $value */ foreach ($view->result as $key => $value) { - // Remove duplicate rows from the view. $id = $value->_entity->id(); if ($previous_id === $id) { unset($view->result[$key]); - continue; } $previous_id = $id; + } - // Remove filtered rows from the view. - // Since this is a calculated field, it cannot be done via query(). - /** @var \Drupal\office_hours\Plugin\Field\FieldType\OfficeHoursItemList $items */ - $items = $value->_entity->$fieldName; - if (is_null($items) || $items->isEmpty()) { - if (!in_array(static::NEVER, $filterValue)) { - unset($view->result[$key]); - } - continue; - } + $filterValue = $filter->value; + if (empty($filterValue)) { + return; + } + if (in_array(OfficeHoursStatus::ANY, $filterValue)) { + return; + } - $is_open = $items->isOpen(); - if ($is_open && in_array((int) static::OPEN, $filterValue)) { - continue; - } - if ((!$is_open) && in_array((int) static::CLOSED, $filterValue)) { - continue; + // Remove filtered rows from the view. + // Since this is a calculated field, it cannot be done via query(). + $fieldName = $filter->realField; + foreach ($view->result as $key => $value) { + $id = $value->_entity->id(); + $status = $value->_entity->$fieldName->getStatus($time = 0); + if (!in_array($status, $filterValue)) { + unset($view->result[$key]); } - unset($view->result[$key]); } - } /** * {@inheritdoc} */ public function query() { + // Do not add query details for this computed field. No SQL is possible. // The views.inc file is not always loaded. Lazy load here. \Drupal::moduleHandler()->loadInclude('office_hours', 'inc', 'office_hours.views'); - - // Do not add query details, since this is a computed field, - // and no SQL is possible. - // parent::query(); } /** @@ -172,29 +153,42 @@ class OfficeHoursStatusFilter extends ManyToOne { return ['***OFFICE_HOURS_REQUEST_TIME***' => \Drupal::time()->getRequestTime()]; } + /** + * Implements hook_views_pre_execute(). + */ + public static function viewsPreExecute(ViewExecutable $view) { + // Nothing to do here. + // if (static::getFilter($view)) { + // self::filter($view); + // } + } + /** * Implements hook_views_post_execute(). */ public static function viewsPostExecute(ViewExecutable $view) { // Nothing to do here. + if (static::getFilter($view)) { + self::filter($view); + } } /** * Implements hook_field_views_pre_render(). */ public static function viewsPreRender(ViewExecutable $view) { - if (static::getFilter($view)) { - self::filter($view); - } + // if (static::getFilter($view)) { + // self::filter($view); + // } } /** * Implements hook_views_post_render(). */ public static function viewsPostRender(ViewExecutable $view, array &$output, CachePluginBase $cache) { - if (!static::getFilter($view)) { - return; - } + // if (static::getFilter($view)) { + // return; + // } // @todo Improve time-based caching (is_open/closed status), // setting $output['#cache']['max-age'] from $items->getCacheMaxAge().