Skip to content
Snippets Groups Projects
Commit 3f15afd8 authored by wodby's avatar wodby
Browse files

Just storing in case want to come back

parent 0e368a68
No related branches found
No related tags found
No related merge requests found
......@@ -130,6 +130,9 @@ field.value.office_hours:
endhours:
type: integer
label: 'End hours'
allday:
type: integer
label: 'All day'
comment:
type: text
label: 'Comment'
name: "Office hours exceptions"
description: "Add exceptions to 'weekly office hours', allowing you to specify day hours for special dates."
package: Field types
dependencies:
- drupal:office_hours
core: 8.x
core_version_requirement: ^8 || ^9
type: module
<?php
namespace Drupal\office_hours_exceptions\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\office_hours\Element\OfficeHoursList;
use Drupal\office_hours\Element\OfficeHoursSlot;
/**
* Provides a one-line text field form element for Exception days.
*
* @FormElement("office_hours_exceptions_slot")
*
* @todo Inherit from class OfficeHoursSlot.
*/
class OfficeHoursExceptionsSlot extends OfficeHoursList {
/**
* @inheritdoc
*/
public static function processOfficeHoursSlot(&$element, FormStateInterface $form_state, &$complete_form) {
// First, process Exception day element as regular Weekday element.
$element = OfficeHoursSlot::processOfficeHoursSlot($element, $form_state, $complete_form);
// Then, facilitate Exception day specific things, such as changing date.
$day = isset($element['#value']['day']) ? $element['#value']['day'] : '';
$default_day = (is_numeric($day)) ? date('Y-m-d', $day) : '';
$day_delta = $element['#daydelta'];
if ($day_delta === 0) {
// For first time slot of a day, set a 'date' select element + day name,
// overriding the hidden (Week widget) or select (List widget) 'day'.
$label = '';
if (!empty($day)) {
// First, cast Exception date back to numeric timestamp.
// @todo Centralize strtotime() and v.v.
$timestamp = is_numeric($day) ? $day : strtotime($day);
$label = t(date('l', $timestamp));
}
// Override (hide) the 'day' select-field.
$element['day'] = [
'#type' => 'date',
'#prefix' => "<div class='office-hours-label'>$label</div>",
'#default_value' => $default_day,
];
}
else {
// Leave 'more slots' as-is, but overriding the value,
// so all slots have same day number.
$element['day'] = [
'#type' => 'value',
'#value' => $default_day,
];
}
return $element;
}
/**
* @inheritdoc
*/
public static function validateOfficeHoursSlot(&$element, FormStateInterface $form_state, &$complete_form) {
$input_exists = FALSE;
$input = &NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
// First, cast Exception date back to numeric timestamp.
// @todo Do this in processOfficeHoursSlot() or massageFormValues()?
// @todo Centralize strtotime() and v.v.
$day = $input['day'];
$input['day'] = $day ? strtotime($day) : $day;
parent::validateOfficeHoursSlot($element, $form_state, $complete_form);
}
}
<?php
namespace Drupal\office_hours_exceptions\Plugin\Field\FieldFormatter;
use Drupal\office_hours\Plugin\Field\FieldFormatter\OfficeHoursFormatterTable;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the formatter.
*
* @FieldFormatter(
* id = "office_hours_exceptions",
* label = @Translation("Table (with exceptions)"),
* field_types = {
* "office_hours",
* }
* )
*/
class OfficeHoursExceptionsFormatterTable extends OfficeHoursFormatterTable {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'exceptions' => [
'restrict_exceptions_to_num_days' => 7,
],
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$settings = $this->getSettings();
$element['exceptions'] = [
'#title' => $this->t('Exception days handling'),
'#type' => 'details',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$element['exceptions']['restrict_exceptions_to_num_days'] = [
'#title' => $this->t('Restrict exceptions display to x days in future'),
'#type' => 'textfield',
'#default_value' => $settings['exceptions']['restrict_exceptions_to_num_days'],
'#description' => $this->t('Leave empty to display all exceptions'),
'#required' => FALSE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
// $element = parent::settingsForm($form, $form_state);
$settings = $this->getSettings();
$summary[] = $this->t('Show exception days until @time days in the future.',
['@time' => $settings['exceptions']['restrict_exceptions_to_num_days']]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$filteredItems = array_filter($items->getValue(), function ($item) {
$day = (int) $item['day'];
if ($day < 7) {
return TRUE;
}
if ($this->isExceptionDayInPast($day) ||
!$this->isExceptionDayInRange($day, $this->getSetting('exceptions')['restrict_exceptions_to_num_days'])
) {
return FALSE;
}
return TRUE;
});
// $filteredItems->rekey(); // from $items->removeItem($key);.
$items->setValue($filteredItems);
$elements = [];
if ($items->getValue()) {
$elements = parent::viewElements($items, $langcode);
}
// Add an extra row to label the exceptions.
// Note: may be changed in template_preprocess_office_hours_table().
foreach ($elements as &$element) {
// Get Table, $elements[] may have Table and Status.
if (isset($element['#table']['#rows'])) {
// Add extra header row, but only if exceptions exist.
$exception_exists = false;
foreach ($element['#table']['#rows'] as $key => $item) {
$exception_exists = ($key > 7) ? true : $exception_exists;
}
if ($exception_exists) {
// Keys -7 to +7 are for sorted weekdays.
$exception_header['data']['label']['data']['#markup'] =
$this->t('Exception hours');
$exception_header['class'] = ['office-hours-exception__label'];
$exception_header['id'] = ['office-hours-exception__title'];
$element['table']['#rows'][8] = $exception_header;
ksort($element['table']['#rows']);
}
}
}
return $elements;
}
/**
* Returns if a timestamp is in the past.
*
* @param int $time
* The time to check if in the past.
*
* @return bool
* TRUE if the timestamp is in the past.
*/
protected function isExceptionDayInPast($time) {
if (date('Y-m-d') > date('Y-m-d', $time)) {
return TRUE;
}
return FALSE;
}
/**
* Returns if a timestamp is in date range of x days to the future.
*
* @param int $time
* The time to check the range for.
* @param int $rangeInDays
* The days into the future we want to check the timestamp against.
*
* @return bool
* TRUE if the timestamp is in range.
*/
protected function isExceptionDayInRange($time, $rangeInDays) {
if ($rangeInDays <= 0) {
return TRUE;
}
$maxTime = time() + $rangeInDays * 24 * 60 * 60;
if (date('Y-m-d', $time) > date('Y-m-d', $maxTime)) {
return FALSE;
}
return TRUE;
}
}
<?php
namespace Drupal\office_hours_exceptions\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\office_hours\Plugin\Field\FieldWidget\OfficeHoursDefaultWidget;
/**
* Plugin implementation of the 'office_hours_exception' widget.
*
* @FieldWidget(
* id = "office_hours_exception",
* label = @Translation("Office hours (week) with exceptions"),
* field_types = {
* "office_hours",
* },
* multiple_values = "FALSE",
* )
*/
class OfficeHoursExceptionsWidget extends OfficeHoursDefaultWidget {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// In D8, we have a (deliberate) anomaly in the widget.
// We prepare 1 widget for the whole week,
// but the field has unlimited cardinality.
// So with $delta = 0, we already show ALL values.
if ($delta > 0) {
return [];
}
// First, create a Week widget for the normal weekdays.
$element = parent::formElement($items, $delta, $element, $form, $form_state);
// Then, add a List Widget fo the Exception days.
// Create an indexed two level array of time slots.
// - First level are day numbers.
// - Second level contains field values arranged by $day_delta.
$indexed_items = [];
foreach ($items as $item) {
$value_of_item = $item->getValue();
// Skip Weekdays.
if (!empty($value_of_item) && 7 < (int) $value_of_item['day']) {
$indexed_items[(int) $value_of_item['day']][] = $item;
}
}
ksort($indexed_items);
// Add more days if we clicked "Add exception".
$field_name = $this->fieldDefinition->getName();
$count = count($indexed_items);
if ($form_state->get('exceptions_count_' . $field_name)) {
$count = $form_state->get('exceptions_count_' . $field_name);
}
$form_state->set('exceptions_count_' . $field_name, $count);
for ($i = count($indexed_items); $i < $count; $i++) {
$indexed_items[] = '';
}
// Build elements, sorted by day number/ timestamp.
$elements = [];
$cardinality = $this->getFieldSetting('cardinality_per_day');
foreach ($indexed_items as $day => $indexed_item) {
for ($day_delta = 0; $day_delta < $cardinality; $day_delta++) {
$default_value = [
'day' => $indexed_item ? $day : '',
'day_delta' => $day_delta,
];
$default_value = isset($indexed_item[$day_delta]) && !empty($indexed_item[$day_delta])
? $indexed_item[$day_delta]->getValue() + $default_value
: $default_value;
$elements[] = [
'#daydelta' => $day_delta,
'#dayname' => '', // @todo Is this needed, used?
'#type' => 'office_hours_exceptions_slot',
'#default_value' => $default_value,
'#field_settings' => $element['#field_settings'],
'#date_element_type' => $this->getSetting('date_element_type'),
];
}
}
$element['exceptions'] = [
'#type' => 'container',
'#field_name' => $field_name,
'#field_parents' => $form['#parents'],
'#prefix' => '<b>' . $this->t('Exceptions') . '</b>',
];
$element['exceptions']['value'] = array_filter($element['value'], 'is_string', ARRAY_FILTER_USE_KEY);
$element['exceptions']['value']['#header']['title'] = $this->t('Date');
$element['exceptions']['value']['#attributes'] = ['id' => 'exceptions-container'];
$element['exceptions']['value'] += $elements;
$element['exceptions']['add_more'] = [
'#type' => 'submit',
'#value' => $this->t('Add exception'),
'#ajax' => [
'callback' => [get_class($this), 'addMoreAjax'],
'wrapper' => 'exceptions-container',
'effect' => 'fade',
],
'#submit' => [
[static::class, 'addMoreSubmit'],
],
];
// Make form_state not cached since we will update it in ajax callback.
$form_state->setCached(FALSE);
return $element;
}
/**
* @inheritdoc
*/
public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_name = $element['#field_name'];
// Increment the items count.
$count = $form_state->get('exceptions_count_' . $field_name);
$count++;
$form_state->set('exceptions_count_' . $field_name, $count);
$form_state->setRebuild();
}
/**
* @inheritdoc
*/
public static function addMoreAjax($form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
// @todo The empty additional slots are displayed after "Add exception".
return $element['value'];
}
/**
* @inheritdoc
*
* We need to reproduce the default WidgetBase extractFormValues since:
* 1. Merge the exceptions values in the main widget values.
* 2. Exceptions are not directly set in `values` but in `exceptions['values']`.
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
// This gets called twice, and we only want to process exceptions once,
// otherwise they would get duplicated.
// @todo Remove this and repair, better merging at the end.
if ($form_state->getValue('office_hours_exceptions_processed')) {
parent::extractFormValues($items, $form, $form_state);
return;
}
$form_state->setValue('office_hours_exceptions_processed', TRUE);
// Extract the values from $form_state->getValues().
$path = array_merge($form['#parents'], [$field_name, 'exceptions']);
$values_exceptions = &NestedArray::getValue($form_state->getValues(), $path);
if (is_array($values_exceptions['value'])) {
$day = '';
// @todo Below code should be in massageFormValues() to avoid duplication.
// Let the widget massage the submitted values.
// $values_exceptions['value'] = $this->massageFormValues($values_exceptions['value'], $form, $form_state);
foreach ($values_exceptions['value'] as &$item) {
// 1. Cast exception day to timestamp.
// 2. Copy Exception date from delta 0 to more slots,
// in case the date has been changed by user
// and to avoid removing lines with day but empty hours.
$day = ($item['day_delta'] == 0) ? $item['day'] : $day;
$item['day'] = $day;
if ($item['allday']) {
$item['starthours'] = $item['endhours'] = -1;
}
// Allow Empty time field with/without comment for Exception day.
elseif (empty($item['starthours']) && empty($item['endhours']) && !(empty($item['day']))) {
if ($item['day_delta'] == 0) {
$item['starthours'] = 0;
$item['endhours'] = 2359;
}
else {
$item['starthours'] = $item['endhours'] = -1;
}
}
else {
$item['allday'] = 0;
}
}
if (!empty($values_exceptions['value'])) {
// Extract the values from $form_state->getValues().
$path = array_merge($form['#parents'], [$field_name]);
$values = NestedArray::getValue($form_state->getValues(), $path);
$merged_values = array_merge($values['value'], $values_exceptions['value']);
$form_state->setValue(array_merge($path, ['value']), $merged_values);
}
}
parent::extractFormValues($items, $form, $form_state);
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// This is called by extractFormValues().
$day = '';
foreach ($values['value'] as &$item) {
if ($item['day'] == '' || $item['day'] > 6) {
// 1. Cast exception day to timestamp.
// 2. Copy Exception date from delta 0 to more slots,
// in case user changed day @todo Move into OfficeHoursWidgetBase.
// and to avoid removing lines with day but empty hours.
if ($item['day_delta'] == 0) {
$day = $item['day'];
if (!is_int($day) && strtotime($day)) {
$day = strtotime($day);
}
}
$item['day'] = $day;
// Allow Empty time field with/without comment for Exception day.
if (empty($item['starthours']) && empty($item['endhours'])) {
$item['starthours'] = $item['endhours'] = -1;
}
}
}
$values = parent::massageFormValues($values, $form, $form_state);
return $values;
}
}
......@@ -141,6 +141,10 @@ class OfficeHoursList extends FormElement {
$element['endhours'] = $element['starthours'];
$element['starthours']['#default_value'] = isset($element['#value']['starthours']) ? $element['#value']['starthours'] : NULL;
$element['endhours']['#default_value'] = isset($element['#value']['endhours']) ? $element['#value']['endhours'] : NULL;
$element['allday'] = [
'#type' => 'checkbox',
'#default_value' => $element['#value']['allday'] ?? NULL,
];
$element['comment'] = !$field_settings['comment'] ? NULL : [
'#type' => 'textfield',
'#default_value' => isset($element['#value']['comment']) ? $element['#value']['comment'] : NULL,
......@@ -175,7 +179,8 @@ class OfficeHoursList extends FormElement {
* @param \Drupal\Core\Form\FormStateInterface $form_state
* @param $complete_form
*/
public static function validateOfficeHoursSlot(&$element, FormStateInterface $form_state, &$complete_form) {
public static function validateOfficeHoursSlot(&$element, FormStateInterface $form_state, &$complete_form)
{
$error_text = '';
// Return an array with starthours, endhours, comment.
......@@ -183,59 +188,58 @@ class OfficeHoursList extends FormElement {
$input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
$input_exists = TRUE;
if (OfficeHoursDatetime::isEmpty($input)) {
if (OfficeHoursDatetime::isEmpty($input) && !$input['allday']) {
return;
}
// @todo JVO: reuse static function OHItem::validateOfficeHoursSettings(
$field_settings = $element['#field_settings'];
$validate_hours = $field_settings['valhrs'];
// Exception: end time 00:00 --> 24:00.
$limit_start = $field_settings['limit_start'];
$limit_end = $field_settings['limit_end'];
$start = OfficeHoursDateHelper::format($input['starthours'], 'Hi', FALSE);
$end = OfficeHoursDateHelper::format($input['endhours'], 'Hi', TRUE);
// If any field of slot is filled, check for required time fields.
$required_start = $validate_hours || $field_settings['required_start'] ?? FALSE;
$required_end = $validate_hours || $field_settings['required_end'] ?? FALSE;
if ($required_start && empty($start)) {
$error_text = 'Opening hours must be set.';
$erroneous_element = &$element['starthours'];
}
elseif ($required_end && empty($end)) {
$error_text = 'Closing hours must be set.';
$erroneous_element = &$element['endhours'];
}
elseif ($validate_hours) {
// Both Start and End must be entered. That is validated above already.
if ($end < $start) {
$error_text = 'Closing hours are earlier than Opening hours.';
$erroneous_element = &$element;
}
elseif ((!empty($limit_start) || !empty($limit_end))) {
$limit_start = OfficeHoursDateHelper::format($field_settings['limit_start'] * 100, 'Hi', TRUE);
$limit_end = OfficeHoursDateHelper::format($field_settings['limit_end'] * 100, 'Hi', TRUE);
if ($start && ($limit_start > $start)
|| ($end && ($limit_end < $end))) {
$error_text = 'Hours are outside limits ( @start - @end ).';
if (!$input['allday']) {
// @todo JVO: reuse static function OHItem::validateOfficeHoursSettings(
$field_settings = $element['#field_settings'];
$validate_hours = $field_settings['valhrs'];
// Exception: end time 00:00 --> 24:00.
$limit_start = $field_settings['limit_start'];
$limit_end = $field_settings['limit_end'];
$start = OfficeHoursDateHelper::format($input['starthours'], 'Hi', FALSE);
$end = OfficeHoursDateHelper::format($input['endhours'], 'Hi', TRUE);
// If any field of slot is filled, check for required time fields.
$required_start = $validate_hours || $field_settings['required_start'] ?? FALSE;
$required_end = $validate_hours || $field_settings['required_end'] ?? FALSE;
if ($required_start && empty($start)) {
$error_text = 'Opening hours must be set.';
$erroneous_element = &$element['starthours'];
} elseif ($required_end && empty($end)) {
$error_text = 'Closing hours must be set.';
$erroneous_element = &$element['endhours'];
} elseif ($validate_hours) {
// Both Start and End must be entered. That is validated above already.
if ($end < $start) {
$error_text = 'Closing hours are earlier than Opening hours.';
$erroneous_element = &$element;
} elseif ((!empty($limit_start) || !empty($limit_end))) {
$limit_start = OfficeHoursDateHelper::format($field_settings['limit_start'] * 100, 'Hi', TRUE);
$limit_end = OfficeHoursDateHelper::format($field_settings['limit_end'] * 100, 'Hi', TRUE);
if ($start && ($limit_start > $start)
|| ($end && ($limit_end < $end))) {
$error_text = 'Hours are outside limits ( @start - @end ).';
$erroneous_element = &$element;
}
}
}
}
if ($error_text) {
$day_name = OfficeHoursDateHelper::weekDays(FALSE)[$input['day']];
$error_text = $day_name // Day name is already translated.
. ': '
. t($error_text,
[
'@start' => $limit_start . ':00',
'@end' => $limit_end . ':00',
],
['context' => 'office_hours']
);
$form_state->setError($erroneous_element, $error_text);
if ($error_text) {
$day_name = OfficeHoursDateHelper::weekDays(FALSE)[$input['day']];
$error_text = $day_name // Day name is already translated.
. ': '
. t($error_text,
[
'@start' => $limit_start . ':00',
'@end' => $limit_end . ':00',
],
['context' => 'office_hours']
);
$form_state->setError($erroneous_element, $error_text);
}
}
}
......
......@@ -42,6 +42,10 @@ class OfficeHoursItem extends FieldItemBase {
'type' => 'int',
'not null' => FALSE,
],
'allday' => [
'type' => 'int',
'not null' => TRUE,
],
'comment' => [
'type' => 'varchar',
'length' => 255,
......@@ -64,6 +68,9 @@ class OfficeHoursItem extends FieldItemBase {
$properties['endhours'] = DataDefinition::create('integer')
->setLabel(t('End hours'))
->setDescription("Stores the end hours value");
$properties['allday'] = DataDefinition::create('integer')
->setLabel(t('All day'))
->setDescription("Open/closed all day (24 hours)");
$properties['comment'] = DataDefinition::create('string')
->setLabel(t('Comment'))
->addConstraint('Length', ['max' => 255])
......@@ -249,7 +256,19 @@ class OfficeHoursItem extends FieldItemBase {
* {@inheritdoc}
*/
public function isEmpty() {
$value = $this->getValue();
if ($value['allday']) {
return FALSE;
}
// if (isset($value['all_day']) && $value['all_day']) {
// return FALSE;
// }
// else {
// return OfficeHoursDatetime::isEmpty($this->getValue());
// }
return OfficeHoursDatetime::isEmpty($this->getValue());
// return FALSE;
}
/**
......
......@@ -104,6 +104,7 @@ class OfficeHoursDefaultWidget extends OfficeHoursWidgetBase {
'title' => $this->t($element['#title']),
'from' => $this->t('From', [], ['context' => 'A point in time']),
'to' => $this->t('To', [], ['context' => 'A point in time']),
'allday' => $this->t('All day', [], ['context' => 'Open/closed all day (24 hours)']),
];
if ($element['#field_settings']['comment']) {
$header['comment'] = $this->t('Comment');
......
{{ dd(items) }}
<div class="office-hours">
{% for item in items %}
<div class="office-hours__item">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment