src/Table/Column/FilterModel/TypeDatetime.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

declare(strict_types=1);

namespace Atk4\Ui\Table\Column\FilterModel;

use Atk4\Data\Model;
use Atk4\Ui\Form;
use Atk4\Ui\Table\Column;

class TypeDatetime extends Column\FilterModel
{
    #[\Override]
    protected function init(): void
    {
        parent::init();

        $this->op->values = [
            '=' => 'Is',
            'within' => 'Is within',
            '<' => 'Is before',
            '>' => 'Is after',
            '<=' => 'Is on or before',
            '>=' => 'Is on or after',
            '!=' => 'Is not',
            'empty' => 'Is empty',
            'not empty' => 'Is not empty',
        ];
        $this->op->default = '=';

        // the date value to operate on
        $this->value->values = [
            'today' => 'Today',
            'tomorrow' => 'Tomorrow',
            'yesterday' => 'Yesterday',
            '-1 week' => 'One week ago',
            '+1 week' => 'One week from now',
            '-1 month' => 'One month ago',
            '+1 month' => 'One month from now',
            'x_day_ago' => 'Numbers of days ago',
            'x_day_now' => 'Number of days from now',
            'exact' => 'Exact date',
        ];

        // the range value field use when within is select
        $this->addField('range', [
            'ui' => ['caption' => ''],
            'values' => [
                '-1 week' => 'The past week',
                '-1 month' => 'The past month',
                '-1 year' => 'The past year',
                '+1 week' => 'The next week',
                '+1 month' => 'The next month',
                '+1 year' => 'The next year',
                'x_day_before' => 'The next numbers of days before',
                'x_day_after' => 'The next number of days after',
            ],
        ]);

        // the exact date field input when exact is select as input value
        $this->addField('exact_date', ['type' => 'date', 'ui' => ['caption' => '']]);

        // the integer field to generate a date when x day selector is used
        $this->addField('number_days', ['ui' => ['caption' => '', 'form' => [Form\Control\Line::class]]]);
    }

    #[\Override]
    public function setConditionForModel(Model $model): void
    {
        $filter = $this->recallData();
        if ($filter !== null) {
            switch ($filter['op']) {
                case 'empty':
                    $model->addCondition($filter['name'], '=', null);

                    break;
                case 'not empty':
                    $model->addCondition($filter['name'], '!=', null);

                    break;
                case 'within':
                    $d1 = $this->getDatetime($filter['value'])->setTime(0, 0, 0);
                    $d2 = $this->getDatetime($filter['range'])->setTime(23, 59, 59, 999_999);
                    if ($d1 > $d2) {
                        [$d1, $d2] = [$d2, $d1];
                    }
                    $model->addCondition($model->expr('[field] between [value] and [value2]', [
                        'field' => $model->getField($filter['name']),
                        'value' => $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1),
                        'value2' => $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2),
                    ]));

                    break;
                case '!=':
                case '=':
                    $d1 = clone $this->getDatetime($filter['value'])->setTime(0, 0, 0);
                    $d2 = $this->getDatetime($filter['value'])->setTime(23, 59, 59, 999_999);
                    if ($d1 > $d2) {
                        [$d1, $d2] = [$d2, $d1];
                    }
                    $betweenOperator = $filter['op'] === '!=' ? 'not between' : 'between';
                    $model->addCondition($model->expr('[field] ' . $betweenOperator . ' [value] and [value2]', [
                        'field' => $model->getField($filter['name']),
                        'value' => $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1),
                        'value2' => $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2),
                    ]));

                    break;
                case '>':
                case '<=':
                    $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value'])->setTime(23, 59, 59, 999_999));

                    break;
                case '<':
                case '>=':
                    $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value'])->setTime(0, 0, 0));

                    break;
                default:
                    $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value']));
            }
        }
    }

    /**
     * Get date object according to it's modifier string.
     * Will construct and return a date object base on constructor string.
     *
     * @param string $dateModifier the string to pass to generated a date from
     *
     * @return \DateTime
     */
    public function getDatetime($dateModifier)
    {
        switch ($dateModifier) {
            case 'exact':
                $date = $this->get('exact_date');

                break;
            case 'x_day_ago':
            case 'x_day_before':
                $date = new \DateTime('-' . $this->get('number_days') . ' days');

                break;
            case 'x_day_now':
            case 'x_day_after':
                $date = new \DateTime('+' . $this->get('number_days') . ' days');

                break;
            default:
                $date = $dateModifier ? new \DateTime($dateModifier) : null;
        }

        return $date;
    }

    #[\Override]
    public function getFormDisplayRules(): array
    {
        return [
            'range' => ['op' => 'isExactly[within]'],
            'exact_date' => ['value' => 'isExactly[exact]'],
            'number_days' => [['value' => 'isExactly[x_day_ago]'], ['value' => 'isExactly[x_day_now]']],
        ];
    }
}