custom_field-1.0.x-dev/src/Plugin/views/filter/CustomFieldDate.php
src/Plugin/views/filter/CustomFieldDate.php
<?php
namespace Drupal\custom_field\Plugin\views\filter;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\custom_field\Plugin\CustomField\FieldType\DateTimeType;
use Drupal\custom_field\Plugin\CustomField\FieldType\DateTimeTypeInterface;
use Drupal\views\Attribute\ViewsFilter;
use Drupal\views\Plugin\views\filter\Date as NumericDate;
use Drupal\views\Plugin\views\query\Sql;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Date/time views filter.
*
* Even though dates are stored as strings, the numeric filter is extended
* because it provides more sensible operators.
*
* @ingroup views_filter_handlers
*/
#[ViewsFilter("custom_field_datetime")]
class CustomFieldDate extends NumericDate implements ContainerFactoryPluginInterface {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected DateFormatterInterface $dateFormatter;
/**
* Date format for SQL conversion.
*
* @var string
*
* @see \Drupal\views\Plugin\views\query\Sql::getDateFormat()
*/
protected string $dateFormat = DateTimeTypeInterface::DATETIME_STORAGE_FORMAT;
/**
* The date type.
*
* @var string
*/
protected string $dateType = DateTimeType::DATETIME_TYPE_DATETIME;
/**
* Determines if the timezone offset is calculated.
*
* @var bool
*/
protected bool $calculateOffset = TRUE;
/**
* Constructs a new Date handler.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
final public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatterInterface $date_formatter, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->dateFormatter = $date_formatter;
$this->entityTypeManager = $entity_type_manager;
if ($configuration['datetime_type'] === DateTimeType::DATETIME_TYPE_DATE) {
// Date format depends on field storage format.
$this->dateFormat = DateTimeTypeInterface::DATE_STORAGE_FORMAT;
// Timezone offset calculation is not applicable to dates that are stored
// as date-only.
$this->calculateOffset = FALSE;
$this->dateType = DateTimeType::DATETIME_TYPE_DATE;
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('date.formatter'),
$container->get('entity_type.manager'),
);
}
/**
* {@inheritdoc}
*/
protected function defineOptions(): array {
$options = parent::defineOptions();
$options['expose']['contains']['filter_type'] = ['default' => ''];
return $options;
}
/**
* {@inheritdoc}
*/
public function defaultExposeOptions(): void {
parent::defaultExposeOptions();
$this->options['expose']['filter_type'] = $this->dateType;
}
/**
* Add a type selector to the value form.
*/
protected function valueForm(&$form, FormStateInterface $form_state): void {
$form['value']['#tree'] = TRUE;
$user_input = $form_state->getUserInput();
$type = NestedArray::getValue($user_input, ['options', 'value', 'type']) ?? $this->value['type'];
$selected_operator = NestedArray::getValue($user_input, ['options', 'operator']) ?? $this->operator;
$between_operator = in_array($selected_operator, ['between', 'not between']);
$identifier = $this->options['expose']['identifier'];
if (!$form_state->get('exposed')) {
$form['value']['type'] = [
'#type' => 'radios',
'#title' => $this->t('Value type'),
'#options' => [
'date' => $this->t('A date in any machine readable format. CCYY-MM-DD HH:MM:SS is preferred.'),
'offset' => $this->t('An offset from the current time such as "@example1" or "@example2"', [
'@example1' => '+1 day',
'@example2' => '-2 hours -30 minutes',
]),
],
'#default_value' => !empty($this->value['type']) ? $this->value['type'] : 'date',
];
// This doesn't quite work right with groups.
if (!$this->isAGroup()) {
$form['value']['type']['#options']['date'] = $this->t('Date');
$form['value']['type']['#ajax'] = [
// @phpstan-ignore-next-line
'url' => views_ui_build_form_url($form_state),
];
}
}
// We have to make some choices when creating this as an exposed
// filter form. For example, if the operator is locked and thus
// not rendered, we can't render dependencies; instead we only
// render the form items we need.
$which = 'all';
$source = '';
if (!empty($form['operator'])) {
$source = ':input[name="options[operator]"]';
}
if ($exposed = $form_state->get('exposed')) {
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// Exposed and locked.
$which = in_array($this->operator, $this->operatorValues(2)) ? 'minmax' : 'value';
}
else {
$source = ':input[name="' . $this->options['expose']['operator_id'] . '"]';
}
}
if ($which == 'all') {
$value_title = $type === 'date' ? 'Date' : 'Offset';
$form['value']['value'] = $this->buildFormField($form_state, $type, 'value', $value_title, !$between_operator);
// Setup #states for all operators with one value.
foreach ($this->operatorValues(1) as $operator) {
$form['value']['value']['#states']['visible'][] = [
$source => ['value' => $operator],
];
}
if ($exposed && !isset($user_input[$identifier]['value'])) {
$user_input[$identifier]['value'] = $this->value['value'];
$form_state->setUserInput($user_input);
}
}
elseif ($which == 'value') {
// When exposed we drop the value-value and just do value if
// the operator is locked.
$form['value'] = $this->buildFormField($form_state, $type, 'value', 'Date');
if ($exposed && !isset($user_input[$identifier])) {
$user_input[$identifier] = $this->value['value'];
$form_state->setUserInput($user_input);
}
}
// Minimum and maximum form fields are associated to some specific operators
// like 'between'. Ensure that min and max fields are only visible if
// the associated operator is not excluded from the operator list.
$two_value_operators_available = ($which == 'all' || $which == 'minmax');
if (!empty($this->options['expose']['operator_limit_selection']) &&
!empty($this->options['expose']['operator_list'])) {
$two_value_operators_available = FALSE;
foreach ($this->options['expose']['operator_list'] as $operator) {
if (in_array($operator, $this->operatorValues(2), TRUE)) {
$two_value_operators_available = TRUE;
break;
}
}
}
if ($two_value_operators_available) {
$form['value']['min'] = $this->buildFormField($form_state, $type, 'min', 'From', $between_operator);
$form['value']['max'] = $this->buildFormField($form_state, $type, 'max', 'To');
if ($which == 'all') {
$states = [];
// Setup #states for all operators with two values.
foreach ($this->operatorValues(2) as $operator) {
$states['#states']['visible'][] = [
$source => ['value' => $operator],
];
}
$form['value']['min'] += $states;
$form['value']['max'] += $states;
}
if ($exposed && !isset($user_input[$identifier]['min'])) {
$user_input[$identifier]['min'] = $this->value['min'];
$form_state->setUserInput($user_input);
}
if ($exposed && !isset($user_input[$identifier]['max'])) {
$user_input[$identifier]['max'] = $this->value['max'];
$form_state->setUserInput($user_input);
}
if (!isset($form['value'])) {
// Ensure there is something in the 'value'.
$form['value'] = [
'#type' => 'value',
'#value' => NULL,
];
}
}
}
/**
* {@inheritdoc}
*/
public function valueSubmit($form, FormStateInterface $form_state): void {
$value = $form_state->getValue(['options', 'value']);
foreach (['value', 'min', 'max'] as $value_key) {
if (!\array_key_exists($value_key, $value)) {
continue;
}
if (!empty($value[$value_key])) {
// Convert date objects to string.
if ($value[$value_key] instanceof DrupalDateTime) {
$value[$value_key] = $value[$value_key]->format('Y-m-d\TH:i:s');
}
}
}
$form_state->setValue(['options', 'value'], $value);
parent::valueSubmit($form, $form_state);
}
/**
* {@inheritdoc}
*
* @param array<string, mixed> $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function buildGroupSubmit($form, FormStateInterface $form_state): void {
$group_items = $form_state->getValue(['options', 'group_info', 'group_items']);
foreach ($group_items as $key => $group_item) {
$value = $group_item['value']['value'];
if ($value instanceof DrupalDateTime) {
$group_items[$key]['value']['value'] = $value->format('Y-m-d H:i:s');
}
}
$form_state->setValue(['options', 'group_info', 'group_items'], $group_items);
parent::buildGroupSubmit($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function acceptExposedInput($input): bool {
if (empty($this->options['exposed'])) {
return TRUE;
}
// Rewrite the input value so that it's in the correct format so that
// the parent gets the right data.
$key = $this->isAGroup() ? 'group_info' : 'expose';
if (empty($this->options[$key]['identifier'])) {
// Invalid identifier configuration. Value can't be resolved.
return FALSE;
}
$value = &$input[$this->options[$key]['identifier']];
if (!is_array($value)) {
$value = [
'value' => $value,
];
}
$rc = parent::acceptExposedInput($input);
if (empty($this->options['expose']['required'])) {
// We have to do some of our own checking for non-required filters.
$info = $this->operators();
$values = $info[$this->operator]['values'] ?? '';
if (!empty($values)) {
switch ($values) {
case 1:
if ($value['value'] === '') {
return FALSE;
}
break;
case 2:
if ($value['min'] === '' && $value['max'] === '') {
return FALSE;
}
break;
}
}
}
return $rc;
}
/**
* Validate that the time values convert to something usable.
*
* @param array<string, mixed> $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param string $operator
* The operator type.
* @param mixed $value
* The value.
*/
public function validateValidTime(&$form, FormStateInterface $form_state, $operator, $value): void {
$operators = $this->operators();
$user_input = $form_state->getUserInput();
$type = NestedArray::getValue($user_input, ['options', 'value', 'type']) ?? $this->value['type'];
$values = $operators[$operator]['values'] ?? NULL;
if ($values == 1) {
if ($type === 'date') {
$date = $value['value'] instanceof DrupalDateTime;
if (!$date) {
$form_state->setError($form['value'], $this->t('Invalid date format.'));
}
}
if ($type === 'offset' && empty($value['value'])) {
$form_state->setError($form['value'], $this->t('Invalid offset date format.'));
}
}
elseif ($values == 2) {
if ($type === 'date') {
$min_date = $value['min'] instanceof DrupalDateTime;
if (!$min_date) {
$form_state->setError($form['min'], $this->t('Invalid from date format.'));
}
$max_date = $value['max'] instanceof DrupalDateTime;
if (!$max_date) {
$form_state->setError($form['max'], $this->t('Invalid to date format.'));
}
}
if ($type === 'offset') {
if (empty($value['min'])) {
$form_state->setError($form['min'], $this->t('Invalid min offset date format.'));
}
if (empty($value['max'])) {
$form_state->setError($form['max'], $this->t('Invalid max offset date format.'));
}
}
}
}
/**
* Builds a group form.
*
* The form contains a group of operator or values to apply as a single
* filter. Implementing our own version to get around bugs.
*
* @param array<string, mixed> $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @link https://www.drupal.org/project/drupal/issues/3523482#comment-16143268
* Related issue discussion.
*/
public function groupForm(&$form, FormStateInterface $form_state): void {
$groups = [];
if (!empty($this->options['group_info']['optional']) && !$this->multipleExposedInput()) {
$groups = ['All' => $this->t('- Any -')];
}
foreach ($this->options['group_info']['group_items'] as $id => $group) {
if (!empty($group['title'])) {
$groups[$id] = $group['title'];
}
}
if (count($groups)) {
$value = (string) $this->options['group_info']['identifier'];
$form[$value] = [
'#title' => $this->options['group_info']['label'],
'#type' => $this->options['group_info']['widget'],
'#default_value' => $this->options['group_info']['default_group'],
'#options' => $groups,
];
$user_input = $form_state->getUserInput();
if (!empty($user_input[$value]) && (!is_scalar($user_input[$value]) || !array_key_exists($user_input[$value], $groups))) {
$user_input[$value] = $this->options['group_info']['default_group'];
$form_state->setUserInput($user_input);
}
if (!empty($this->options['group_info']['multiple'])) {
if (count($groups) < 5) {
$form[$value]['#type'] = 'checkboxes';
}
else {
$form[$value]['#type'] = 'select';
$form[$value]['#size'] = 5;
$form[$value]['#multiple'] = TRUE;
}
unset($form[$value]['#default_value']);
$user_input = $form_state->getUserInput();
if (empty($user_input[$value])) {
$user_input[$value] = array_filter($this->options['group_info']['default_group_multiple']);
$form_state->setUserInput($user_input);
}
}
$this->options['expose']['label'] = '';
}
}
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state): void {
parent::buildExposeForm($form, $form_state);
$user_input = $form_state->getUserInput();
$type = NestedArray::getValue($user_input, ['options', 'value', 'type']) ?? $this->options['value']['type'];
// Add a filter type to determine exposed form element.
$form['expose']['filter_type'] = [
'#type' => 'radios',
'#title' => $this->t('Filter type'),
'#description' => $this->t('Choose the form element type for the exposed filter.'),
'#description_display' => 'before',
'#default_value' => $this->options['expose']['filter_type'],
'#options' => [
'date' => $this->t('Date'),
'datetime' => $this->t('Date and time (separate elements)'),
'datetime_local' => $this->t('Date and time (combined elements)'),
],
'#access' => $type === 'date' && $this->dateType !== DateTimeType::DATETIME_TYPE_DATE,
];
if ($type === 'date') {
// Placeholders are only applicable when a value type is offset.
foreach (['placeholder', 'min_placeholder', 'max_placeholder'] as $key) {
$form['expose'][$key]['#access'] = FALSE;
}
}
}
/**
* Override parent method to change input type.
*/
public function buildExposedForm(&$form, FormStateInterface $form_state): void {
parent::buildExposedForm($form, $form_state);
if ($this->value['type'] === 'date') {
$filter_type = $this->options['expose']['filter_type'];
$field_identifier = $this->options['expose']['identifier'];
// The form elements might be encased in a wrapper, so check that first.
$field_wrapper = $field_identifier . '_wrapper';
if (isset($form[$field_wrapper])) {
foreach (Element::children($form[$field_wrapper][$field_identifier]) as $child) {
$label = '';
if ($child === 'min') {
$label = $this->t('From');
}
elseif ($child === 'max') {
$label = $this->t('To');
}
if (!empty($label)) {
$form[$field_wrapper][$field_identifier][$child]['#title'] = $label;
}
}
}
elseif (isset($form[$field_identifier])) {
$label = $this->exposedInfo()['label'];
if (!empty($label)) {
$form[$field_identifier]['#title'] = $label;
}
}
if (in_array($this->operator, ['between', 'not between'], TRUE)) {
// Check the element input matches the form structure.
$input = $form_state->getUserInput();
if (isset($input[$field_identifier], $input[$field_identifier]['min']) && !is_array($input[$field_identifier]['min']) && $value = $input[$field_identifier]['min']) {
$date = new DrupalDateTime($value);
if ($filter_type === 'datetime') {
$input[$field_identifier]['min'] = [
'date' => $date->format('Y-m-d'),
'time' => $date->format('H:i:s'),
];
}
else {
$input[$field_identifier]['min'] = [
'date' => $date->format($this->dateFormat),
];
}
}
if (isset($input[$field_identifier], $input[$field_identifier]['max']) && !is_array($input[$field_identifier]['max']) && $value = $input[$field_identifier]['max']) {
$date = new DrupalDateTime($value);
if ($filter_type === 'datetime') {
$input[$field_identifier]['max'] = [
'date' => $date->format('Y-m-d'),
'time' => $date->format('H:i:s'),
];
}
else {
$input[$field_identifier]['max'] = [
'date' => $date->format($this->dateFormat),
];
}
}
$form_state->setUserInput($input);
}
else {
// Check the element input matches the form structure.
$input = $form_state->getUserInput();
if (isset($input[$field_identifier]) && !is_array($input[$field_identifier]) && $value = $input[$field_identifier]) {
$date = new DrupalDateTime($value);
if ($filter_type === 'datetime') {
$input[$field_identifier] = [
'date' => $date->format('Y-m-d'),
'time' => $date->format('H:i:s'),
];
}
else {
$input[$field_identifier] = [
'date' => $date->format($this->dateFormat),
];
}
}
$form_state->setUserInput($input);
}
}
}
/**
* Override parent method, which deals with dates as integers.
*/
protected function opBetween($field): void {
$origin_offset = 0;
if ($this->value['type'] === 'date' && !$this->isAGroup()) {
$min_date = $this->value['min'];
$max_date = $this->value['max'];
if (!$min_date instanceof DrupalDateTime || !$max_date instanceof DrupalDateTime) {
return;
}
}
else {
$timezone = $this->getTimezone();
$origin_offset = $this->getOffset($this->value['min'], $timezone);
// Although both 'min' and 'max' values are required, default empty 'min'
// value as UNIX timestamp 0.
$min = (!empty($this->value['min'])) ? $this->value['min'] : '@0';
$min_date = new DateTimePlus($min, new \DateTimeZone($timezone));
$max_date = new DateTimePlus($this->value['max'], new \DateTimeZone($timezone));
}
// Convert to ISO format and format for query. UTC timezone is used since
// dates are stored in UTC.
$a = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($min_date->getTimestamp() + $origin_offset, 'custom', DateTimeTypeInterface::DATETIME_STORAGE_FORMAT, DateTimeTypeInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
$b = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($max_date->getTimestamp() + $origin_offset, 'custom', DateTimeTypeInterface::DATETIME_STORAGE_FORMAT, DateTimeTypeInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the values.
$operator = strtoupper($this->operator);
// The parent class defines $field as an object for some reason but
// getDateField() expects a string.
// @phpstan-ignore argument.type
$field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
if ($this->query instanceof Sql) {
$this->query->addWhereExpression($this->options['group'], "$field $operator $a AND $b");
}
}
/**
* Override parent method, which deals with dates as integers.
*
* @param string $field
* The field.
*
* @throws \DateInvalidTimeZoneException
* @throws \DateMalformedStringException
*/
protected function opSimple($field): void {
$origin_offset = 0;
if ($this->value['type'] === 'date' && !$this->isAGroup()) {
if (empty($this->value['value'])) {
return;
}
if ($this->value['value'] instanceof DrupalDateTime) {
$date = $this->value['value'];
}
else {
$date = new DrupalDateTime($this->value['value']);
}
}
else {
$timezone = $this->getTimezone();
$origin_offset = $this->getOffset($this->value['value'], $timezone);
// Convert to ISO. UTC timezone is used since dates are stored in UTC.
$date = new DateTimePlus($this->value['value'], new \DateTimeZone($timezone));
}
// Convert to ISO. UTC timezone is used since dates are stored in UTC.
$value = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($date->getTimestamp() + $origin_offset, 'custom', DateTimeTypeInterface::DATETIME_STORAGE_FORMAT, DateTimeTypeInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the value.
$field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
if ($this->query instanceof Sql) {
$this->query->addWhereExpression($this->options['group'], "$field $this->operator $value");
}
}
/**
* Get the proper time zone to use in computations.
*
* Date-only fields do not have a time zone associated with them, so the
* filter input needs to use UTC for reference. Otherwise, use the time zone
* for the current user.
*
* @return string
* The time zone name.
*/
protected function getTimezone(): string {
return $this->dateFormat === DateTimeTypeInterface::DATE_STORAGE_FORMAT
? DateTimeTypeInterface::STORAGE_TIMEZONE
: date_default_timezone_get();
}
/**
* Get the proper offset from UTC to use in computations.
*
* @param string $time
* A date/time string compatible with \DateTime. It is used as the
* reference for computing the offset, which can vary based on the time
* zone rules.
* @param string $timezone
* The time zone that $time is in.
*
* @return int
* The computed offset in seconds.
*
* @throws \DateInvalidTimeZoneException
* @throws \DateMalformedStringException
*/
protected function getOffset(string $time, string $timezone): int {
// Date-only fields do not have a time zone or offset from UTC associated
// with them. For relative (i.e. 'offset') comparisons, we need to compute
// the user's offset from UTC for use in the query.
$origin_offset = 0;
if ($this->dateFormat === DateTimeTypeInterface::DATE_STORAGE_FORMAT && $this->value['type'] === 'offset') {
$origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(date_default_timezone_get()), new \DateTime($time, new \DateTimeZone($timezone)));
}
return $origin_offset;
}
/**
* Helper function to build a form field.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param string $type
* The value type selected from filter settings.
* @param string $key
* The form element key.
* @param string $title
* The title of the form element.
* @param bool $auto_focus
* A boolean value to set autofocus when not exposed.
*
* @return array<string, mixed>
* The field array.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function buildFormField(FormStateInterface $form_state, string $type, string $key, string $title, bool $auto_focus = FALSE): array {
$filter_type = $this->options['expose']['filter_type'];
$exposed = (bool) $form_state->get('exposed');
if ($type === 'date' && !$this->isAGroup()) {
$value_date = NULL;
if (!empty($this->value[$key])) {
$value_date = new DrupalDateTime($this->value[$key]);
}
$date_storage = $this->entityTypeManager->getStorage('date_format');
$element_type = 'custom_field_datetime_date';
$date_element = $exposed ? 'date' : 'datetime-local';
$time_element = 'none';
$date_format = DateTimeTypeInterface::DATE_STORAGE_FORMAT;
$time_format = '';
$date_timezone = date_default_timezone_get();
if ($exposed) {
switch ($filter_type) {
case 'datetime':
$element_type = 'custom_field_datetime';
$time_element = 'time';
$time_format = $date_storage->load('html_time')->getPattern();
break;
case 'date':
$date_timezone = DateTimeTypeInterface::STORAGE_TIMEZONE;
break;
case 'datetime_local':
$date_element = 'datetime-local';
$date_format = DateTimeTypeInterface::DATETIME_STORAGE_FORMAT;
break;
}
}
$element = [
'#type' => $element_type,
'#date_increment' => 1,
'#date_date_element' => $date_element,
'#date_date_format' => $date_format,
'#date_date_callbacks' => [],
'#date_time_element' => $time_element,
'#date_time_format' => $time_format,
'#date_time_callbacks' => [],
'#date_timezone' => $date_timezone,
'#default_value' => $value_date,
'#attached' => [
'library' => ['custom_field/custom-field-widget'],
],
'#wrapper_attributes' => [
'class' => ['views-exposed-form__item'],
],
];
if ($filter_type === 'datetime' && $exposed) {
$element['#theme'] = 'custom_field_flex_wrapper';
$element['#theme_wrappers'] = ['fieldset'];
}
}
else {
$element = [
'#type' => 'textfield',
'#size' => 30,
'#default_value' => $this->value[$key],
];
if ($key === 'value' && !empty($this->options['expose']['placeholder'])) {
$element['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
elseif ($key === 'min' && !empty($this->options['expose']['min_placeholder'])) {
$element['#attributes']['placeholder'] = $this->options['expose']['min_placeholder'];
}
elseif ($key === 'max' && !empty($this->options['expose']['max_placeholder'])) {
$element['#attributes']['placeholder'] = $this->options['expose']['max_placeholder'];
}
}
if (!$exposed) {
$element['#attributes']['autofocus'] = $auto_focus;
}
if (!empty($title)) {
$element['#title'] = $this->t('@title', ['@title' => $title]);
}
return $element;
}
/**
* Override parent method to remove 'regular_expression' as an option.
*
* Since we're operating on date fields, and have a date (and maybe time)
* picker as the widget (not a text field), a 'Regular expression' operation
* makes no sense.
*
* @return mixed[]
* An array of operators.
*/
public function operators(): array {
$operators = parent::operators();
unset($operators['regular_expression']);
unset($operators['not_regular_expression']);
return $operators;
}
}
