Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
HTMLDateTimeField
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 9
1482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getAttributes
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 loadDataFromRequest
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 validate
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
110
 parseDate
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 formatDate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getInputOOUI
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 getOOUIModules
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 shouldInfuseOOUI
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\HTMLForm\Field;
4
5use DateTime;
6use DateTimeZone;
7use Exception;
8use InvalidArgumentException;
9use Wikimedia\RequestTimeout\TimeoutException;
10
11/**
12 * A field that will contain a date and/or time
13 *
14 * Currently recognizes only {YYYY}-{MM}-{DD}T{HH}:{MM}:{SS.S*}Z formatted dates.
15 *
16 * Besides the parameters recognized by HTMLTextField, additional recognized
17 * parameters in the field descriptor array include:
18 *  type - 'date', 'time', or 'datetime'
19 *  min - The minimum date to allow, in any recognized format.
20 *  max - The maximum date to allow, in any recognized format.
21 *  placeholder - The default comes from the htmlform-(date|time|datetime)-placeholder message.
22 *
23 * The result is a formatted date.
24 *
25 * @stable to extend
26 * @note This widget is not likely to work well in non-OOUI forms.
27 */
28class HTMLDateTimeField extends HTMLTextField {
29    /** @var string[] */
30    protected static $patterns = [
31        'date' => '[0-9]{4}-[01][0-9]-[0-3][0-9]',
32        'time' => '[0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?',
33        'datetime' => '[0-9]{4}-[01][0-9]-[0-3][0-9][T ][0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?Z?',
34    ];
35
36    /** @var string */
37    protected $mType = 'datetime';
38
39    /**
40     * @stable to call
41     * @inheritDoc
42     */
43    public function __construct( $params ) {
44        parent::__construct( $params );
45
46        $this->mType = $params['type'] ?? 'datetime';
47
48        if ( !in_array( $this->mType, [ 'date', 'time', 'datetime' ] ) ) {
49            throw new InvalidArgumentException( "Invalid type '$this->mType'" );
50        }
51
52        if ( $this->mPlaceholder === '' ) {
53            // Messages: htmlform-date-placeholder htmlform-time-placeholder htmlform-datetime-placeholder
54            $this->mPlaceholder = $this->msg( "htmlform-{$this->mType}-placeholder" )->text();
55        }
56
57        $this->mClass .= ' mw-htmlform-datetime-field';
58    }
59
60    public function getAttributes( array $list ) {
61        $parentList = array_diff( $list, [ 'min', 'max' ] );
62        $ret = parent::getAttributes( $parentList );
63
64        if ( in_array( 'min', $list ) && isset( $this->mParams['min'] ) ) {
65            $min = $this->parseDate( $this->mParams['min'] );
66            if ( $min ) {
67                $ret['min'] = $this->formatDate( $min );
68            }
69        }
70        if ( in_array( 'max', $list ) && isset( $this->mParams['max'] ) ) {
71            $max = $this->parseDate( $this->mParams['max'] );
72            if ( $max ) {
73                $ret['max'] = $this->formatDate( $max );
74            }
75        }
76
77        $ret['step'] = 1;
78
79        $ret['type'] = $this->mType;
80        $ret['pattern'] = static::$patterns[$this->mType];
81
82        return $ret;
83    }
84
85    public function loadDataFromRequest( $request ) {
86        if ( !$request->getCheck( $this->mName ) ) {
87            return $this->getDefault();
88        }
89
90        $value = $request->getText( $this->mName );
91        $date = $this->parseDate( $value );
92        return $date ? $this->formatDate( $date ) : $value;
93    }
94
95    public function validate( $value, $alldata ) {
96        $p = parent::validate( $value, $alldata );
97
98        if ( $p !== true ) {
99            return $p;
100        }
101
102        if ( $value === '' ) {
103            // required was already checked by parent::validate
104            return true;
105        }
106
107        $date = $this->parseDate( $value );
108        if ( !$date ) {
109            // Messages: htmlform-date-invalid htmlform-time-invalid htmlform-datetime-invalid
110            return $this->msg( "htmlform-{$this->mType}-invalid" );
111        }
112
113        if ( isset( $this->mParams['min'] ) ) {
114            $min = $this->parseDate( $this->mParams['min'] );
115            if ( $min && $date < $min ) {
116                // Messages: htmlform-date-toolow htmlform-time-toolow htmlform-datetime-toolow
117                return $this->msg( "htmlform-{$this->mType}-toolow", $this->formatDate( $min ) );
118            }
119        }
120
121        if ( isset( $this->mParams['max'] ) ) {
122            $max = $this->parseDate( $this->mParams['max'] );
123            if ( $max && $date > $max ) {
124                // Messages: htmlform-date-toohigh htmlform-time-toohigh htmlform-datetime-toohigh
125                return $this->msg( "htmlform-{$this->mType}-toohigh", $this->formatDate( $max ) );
126            }
127        }
128
129        return true;
130    }
131
132    protected function parseDate( $value ) {
133        $value = trim( $value ?? '' );
134        if ( $value === '' ) {
135            return false;
136        }
137
138        if ( $this->mType === 'date' ) {
139            $value .= ' T00:00:00+0000';
140        }
141        if ( $this->mType === 'time' ) {
142            $value = '1970-01-01 ' . $value . '+0000';
143        }
144
145        try {
146            $date = new DateTime( $value, new DateTimeZone( 'GMT' ) );
147            return $date->getTimestamp();
148        } catch ( TimeoutException $e ) {
149            throw $e;
150        } catch ( Exception $ex ) {
151            return false;
152        }
153    }
154
155    protected function formatDate( $value ) {
156        switch ( $this->mType ) {
157            case 'date':
158                return gmdate( 'Y-m-d', $value );
159
160            case 'time':
161                return gmdate( 'H:i:s', $value );
162
163            case 'datetime':
164                return gmdate( 'Y-m-d\\TH:i:s\\Z', $value );
165        }
166    }
167
168    public function getInputOOUI( $value ) {
169        $params = [
170            'type' => $this->mType,
171            'value' => $value,
172            'name' => $this->mName,
173            'id' => $this->mID,
174        ];
175
176        $params += \OOUI\Element::configFromHtmlAttributes(
177            $this->getAttributes( [ 'disabled', 'readonly', 'min', 'max' ] )
178        );
179
180        if ( $this->mType === 'date' ) {
181            $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
182            return new \MediaWiki\Widget\DateInputWidget( $params );
183        } else {
184            return new \MediaWiki\Widget\DateTimeInputWidget( $params );
185        }
186    }
187
188    protected function getOOUIModules() {
189        if ( $this->mType === 'date' ) {
190            return [ 'mediawiki.widgets.DateInputWidget' ];
191        } else {
192            return [ 'mediawiki.widgets.datetime' ];
193        }
194    }
195
196    protected function shouldInfuseOOUI() {
197        return true;
198    }
199
200}
201
202/** @deprecated class alias since 1.42 */
203class_alias( HTMLDateTimeField::class, 'HTMLDateTimeField' );