1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\plugin\struct\meta\DateFormatConverter;
6use dokuwiki\plugin\struct\meta\QueryBuilder;
7use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
8use dokuwiki\plugin\struct\meta\ValidationException;
9
10class DateTime extends Date
11{
12    protected $config = [
13        'format' => '', // filled by constructor
14        'prefilltoday' => false,
15        'pastonly' => false,
16        'futureonly' => false,
17    ];
18
19    /**
20     * DateTime constructor.
21     *
22     * @param array|null $config
23     * @param string $label
24     * @param bool $ismulti
25     * @param int $tid
26     */
27    public function __construct($config = null, $label = '', $ismulti = false, $tid = 0)
28    {
29        global $conf;
30        $this->config['format'] = DateFormatConverter::toDate($conf['dformat']);
31
32        parent::__construct($config, $label, $ismulti, $tid);
33    }
34
35    /**
36     * Return the editor to edit a single value
37     *
38     * @param string $name the form name where this has to be stored
39     * @param string $rawvalue the current value
40     * @param string $htmlID
41     *
42     * @return string html
43     */
44    public function valueEditor($name, $rawvalue, $htmlID)
45    {
46        if ($this->config['prefilltoday'] && !$rawvalue) {
47            $rawvalue = date('Y-m-d\TH:i');
48        }
49        $rawvalue = str_replace(' ', 'T', $rawvalue);
50        $params = [
51            'name' => $name,
52            'value' => $rawvalue,
53            'class' => 'struct_datetime',
54            'type' => 'datetime-local', // HTML5 datetime picker
55            'id' => $htmlID,
56        ];
57        $attributes = buildAttributes($params, true);
58        return "<input $attributes />";
59    }
60
61    /**
62     * Validate a single value
63     *
64     * This function needs to throw a validation exception when validation fails.
65     * The exception message will be prefixed by the appropriate field on output
66     *
67     * @param string|array $rawvalue
68     * @return string
69     * @throws ValidationException
70     */
71    public function validate($rawvalue)
72    {
73        $rawvalue = trim($rawvalue);
74        [$date, $time] = array_pad(preg_split('/[ |T]/', $rawvalue, 2), 2, '');
75        $date = trim($date);
76        $time = trim($time);
77
78        [$year, $month, $day] = explode('-', $date, 3);
79        if (!checkdate((int)$month, (int)$day, (int)$year)) {
80            throw new ValidationException('invalid datetime format');
81        }
82        if ($this->config['pastonly'] && strtotime($rawvalue) > time()) {
83            throw new ValidationException('pastonly');
84        }
85        if ($this->config['futureonly'] && strtotime($rawvalue) < time()) {
86            throw new ValidationException('futureonly');
87        }
88
89        [$h, $m] = array_pad(explode(':', $time, 3), 2, ''); // drop seconds
90        $h = (int)$h;
91        $m = (int)$m;
92        if ($h < 0 || $h > 23 || $m < 0 || $m > 59) {
93            throw new ValidationException('invalid datetime format');
94        }
95
96        return sprintf("%d-%02d-%02d %02d:%02d", $year, $month, $day, $h, $m);
97    }
98
99    /**
100     * @param QueryBuilder $QB
101     * @param string $tablealias
102     * @param string $colname
103     * @param string $alias
104     */
105    public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
106    {
107        $col = "$tablealias.$colname";
108
109        // when accessing the revision column we need to convert from Unix timestamp
110        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\RevisionColumn')) {
111            $rightalias = $QB->generateTableAlias();
112            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
113            $col = "DATETIME($rightalias.lastrev, 'unixepoch', 'localtime')";
114        }
115
116        $QB->addSelectStatement($col, $alias);
117    }
118
119    /**
120     * @param QueryBuilderWhere $add
121     * @param string $tablealias
122     * @param string $colname
123     * @param string $comp
124     * @param string|\string[] $value
125     * @param string $op
126     */
127    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
128    {
129        $col = "$tablealias.$colname";
130        $QB = $add->getQB();
131
132        // when accessing the revision column we need to convert from Unix timestamp
133        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\RevisionColumn')) {
134            $rightalias = $QB->generateTableAlias();
135            $col = "DATETIME($rightalias.lastrev, 'unixepoch', 'localtime')";
136            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
137        }
138
139        /** @var QueryBuilderWhere $add Where additional queries are added to */
140        if (is_array($value)) {
141            $add = $add->where($op); // sub where group
142            $op = 'OR';
143        }
144        foreach ((array)$value as $item) {
145            $pl = $QB->addValue($item);
146            $add->where($op, "$col $comp $pl");
147        }
148    }
149
150    /**
151     * When sorting `%lastupdated%`, then sort the data from the `titles` table instead the `data_` table.
152     *
153     * @param QueryBuilder $QB
154     * @param string $tablealias
155     * @param string $colname
156     * @param string $order
157     */
158    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
159    {
160        $col = "$tablealias.$colname";
161
162        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\RevisionColumn')) {
163            $rightalias = $QB->generateTableAlias();
164            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
165            $col = "$rightalias.lastrev";
166        }
167
168        $QB->addOrderBy("$col $order");
169    }
170}
171