1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Table\Column\FilterModel;
6:
7: use Atk4\Data\Model;
8: use Atk4\Ui\Form;
9: use Atk4\Ui\Table\Column;
10:
11: class TypeDatetime extends Column\FilterModel
12: {
13: #[\Override]
14: protected function init(): void
15: {
16: parent::init();
17:
18: $this->op->values = [
19: '=' => 'Is',
20: 'within' => 'Is within',
21: '<' => 'Is before',
22: '>' => 'Is after',
23: '<=' => 'Is on or before',
24: '>=' => 'Is on or after',
25: '!=' => 'Is not',
26: 'empty' => 'Is empty',
27: 'not empty' => 'Is not empty',
28: ];
29: $this->op->default = '=';
30:
31: // the date value to operate on
32: $this->value->values = [
33: 'today' => 'Today',
34: 'tomorrow' => 'Tomorrow',
35: 'yesterday' => 'Yesterday',
36: '-1 week' => 'One week ago',
37: '+1 week' => 'One week from now',
38: '-1 month' => 'One month ago',
39: '+1 month' => 'One month from now',
40: 'x_day_ago' => 'Numbers of days ago',
41: 'x_day_now' => 'Number of days from now',
42: 'exact' => 'Exact date',
43: ];
44:
45: // the range value field use when within is select
46: $this->addField('range', [
47: 'ui' => ['caption' => ''],
48: 'values' => [
49: '-1 week' => 'The past week',
50: '-1 month' => 'The past month',
51: '-1 year' => 'The past year',
52: '+1 week' => 'The next week',
53: '+1 month' => 'The next month',
54: '+1 year' => 'The next year',
55: 'x_day_before' => 'The next numbers of days before',
56: 'x_day_after' => 'The next number of days after',
57: ],
58: ]);
59:
60: // the exact date field input when exact is select as input value
61: $this->addField('exact_date', ['type' => 'date', 'ui' => ['caption' => '']]);
62:
63: // the integer field to generate a date when x day selector is used
64: $this->addField('number_days', ['ui' => ['caption' => '', 'form' => [Form\Control\Line::class, 'inputType' => 'number']]]);
65: }
66:
67: #[\Override]
68: public function setConditionForModel(Model $model)
69: {
70: $filter = $this->recallData();
71: if ($filter !== null) {
72: switch ($filter['op']) {
73: case 'empty':
74: $model->addCondition($filter['name'], '=', null);
75:
76: break;
77: case 'not empty':
78: $model->addCondition($filter['name'], '!=', null);
79:
80: break;
81: case 'within':
82: $d1 = $this->getDatetime($filter['value'])->setTime(0, 0, 0);
83: $d2 = $this->getDatetime($filter['range'])->setTime(23, 59, 59, 999_999);
84: if ($d2 >= $d1) {
85: $value = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1);
86: $value2 = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2);
87: } else {
88: $value = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2);
89: $value2 = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1);
90: }
91: $model->addCondition($model->expr('[field] between [value] and [value2]', [
92: 'field' => $model->getField($filter['name']),
93: 'value' => $value,
94: 'value2' => $value2,
95: ]));
96:
97: break;
98: case '!=':
99: case '=':
100: $d1 = clone $this->getDatetime($filter['value'])->setTime(0, 0, 0);
101: $d2 = $this->getDatetime($filter['value'])->setTime(23, 59, 59, 999_999);
102: if ($d2 >= $d1) {
103: $value = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1);
104: $value2 = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2);
105: } else {
106: $value = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d2);
107: $value2 = $model->getPersistence()->typecastSaveField($model->getField($filter['name']), $d1);
108: }
109: $betweenOperator = $filter['op'] === '!=' ? 'not between' : 'between';
110: $model->addCondition($model->expr('[field] ' . $betweenOperator . ' [value] and [value2]', [
111: 'field' => $model->getField($filter['name']),
112: 'value' => $value,
113: 'value2' => $value2,
114: ]));
115:
116: break;
117: case '>':
118: case '<=':
119: $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value'])->setTime(23, 59, 59, 999_999));
120:
121: break;
122: case '<':
123: case '>=':
124: $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value'])->setTime(0, 0, 0));
125:
126: break;
127: default:
128: $model->addCondition($filter['name'], $filter['op'], $this->getDatetime($filter['value']));
129: }
130: }
131:
132: return $model;
133: }
134:
135: /**
136: * Get date object according to it's modifier string.
137: * Will construct and return a date object base on constructor string.
138: *
139: * @param string $dateModifier the string to pass to generated a date from
140: *
141: * @return \DateTime
142: */
143: public function getDatetime($dateModifier)
144: {
145: switch ($dateModifier) {
146: case 'exact':
147: $date = $this->get('exact_date');
148:
149: break;
150: case 'x_day_ago':
151: case 'x_day_before':
152: $date = new \DateTime('-' . $this->get('number_days') . ' days');
153:
154: break;
155: case 'x_day_now':
156: case 'x_day_after':
157: $date = new \DateTime('+' . $this->get('number_days') . ' days');
158:
159: break;
160: default:
161: $date = $dateModifier ? new \DateTime($dateModifier) : null;
162: }
163:
164: return $date;
165: }
166:
167: #[\Override]
168: public function getFormDisplayRules(): array
169: {
170: return [
171: 'range' => ['op' => 'isExactly[within]'],
172: 'exact_date' => ['value' => 'isExactly[exact]'],
173: 'number_days' => [['value' => 'isExactly[x_day_ago]'], ['value' => 'isExactly[x_day_now]']],
174: ];
175: }
176: }
177: