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    protected static $patterns = [
30        'date' => '[0-9]{4}-[01][0-9]-[0-3][0-9]',
31        'time' => '[0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?',
32        '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?',
33    ];
34
35    protected $mType = 'datetime';
36
37    /**
38     * @stable to call
39     * @inheritDoc
40     */
41    public function __construct( $params ) {
42        parent::__construct( $params );
43
44        $this->mType = $params['type'] ?? 'datetime';
45
46        if ( !in_array( $this->mType, [ 'date', 'time', 'datetime' ] ) ) {
47            throw new InvalidArgumentException( "Invalid type '$this->mType'" );
48        }
49
50        if ( $this->mPlaceholder === '' ) {
51            // Messages: htmlform-date-placeholder htmlform-time-placeholder htmlform-datetime-placeholder
52            $this->mPlaceholder = $this->msg( "htmlform-{$this->mType}-placeholder" )->text();
53        }
54
55        $this->mClass .= ' mw-htmlform-datetime-field';
56    }
57
58    public function getAttributes( array $list ) {
59        $parentList = array_diff( $list, [ 'min', 'max' ] );
60        $ret = parent::getAttributes( $parentList );
61
62        if ( in_array( 'min', $list ) && isset( $this->mParams['min'] ) ) {
63            $min = $this->parseDate( $this->mParams['min'] );
64            if ( $min ) {
65                $ret['min'] = $this->formatDate( $min );
66            }
67        }
68        if ( in_array( 'max', $list ) && isset( $this->mParams['max'] ) ) {
69            $max = $this->parseDate( $this->mParams['max'] );
70            if ( $max ) {
71                $ret['max'] = $this->formatDate( $max );
72            }
73        }
74
75        $ret['step'] = 1;
76
77        $ret['type'] = $this->mType;
78        $ret['pattern'] = static::$patterns[$this->mType];
79
80        return $ret;
81    }
82
83    public function loadDataFromRequest( $request ) {
84        if ( !$request->getCheck( $this->mName ) ) {
85            return $this->getDefault();
86        }
87
88        $value = $request->getText( $this->mName );
89        $date = $this->parseDate( $value );
90        return $date ? $this->formatDate( $date ) : $value;
91    }
92
93    public function validate( $value, $alldata ) {
94        $p = parent::validate( $value, $alldata );
95
96        if ( $p !== true ) {
97            return $p;
98        }
99
100        if ( $value === '' ) {
101            // required was already checked by parent::validate
102            return true;
103        }
104
105        $date = $this->parseDate( $value );
106        if ( !$date ) {
107            // Messages: htmlform-date-invalid htmlform-time-invalid htmlform-datetime-invalid
108            return $this->msg( "htmlform-{$this->mType}-invalid" );
109        }
110
111        if ( isset( $this->mParams['min'] ) ) {
112            $min = $this->parseDate( $this->mParams['min'] );
113            if ( $min && $date < $min ) {
114                // Messages: htmlform-date-toolow htmlform-time-toolow htmlform-datetime-toolow
115                return $this->msg( "htmlform-{$this->mType}-toolow", $this->formatDate( $min ) );
116            }
117        }
118
119        if ( isset( $this->mParams['max'] ) ) {
120            $max = $this->parseDate( $this->mParams['max'] );
121            if ( $max && $date > $max ) {
122                // Messages: htmlform-date-toohigh htmlform-time-toohigh htmlform-datetime-toohigh
123                return $this->msg( "htmlform-{$this->mType}-toohigh", $this->formatDate( $max ) );
124            }
125        }
126
127        return true;
128    }
129
130    protected function parseDate( $value ) {
131        $value = trim( $value ?? '' );
132        if ( $value === '' ) {
133            return false;
134        }
135
136        if ( $this->mType === 'date' ) {
137            $value .= ' T00:00:00+0000';
138        }
139        if ( $this->mType === 'time' ) {
140            $value = '1970-01-01 ' . $value . '+0000';
141        }
142
143        try {
144            $date = new DateTime( $value, new DateTimeZone( 'GMT' ) );
145            return $date->getTimestamp();
146        } catch ( TimeoutException $e ) {
147            throw $e;
148        } catch ( Exception $ex ) {
149            return false;
150        }
151    }
152
153    protected function formatDate( $value ) {
154        switch ( $this->mType ) {
155            case 'date':
156                return gmdate( 'Y-m-d', $value );
157
158            case 'time':
159                return gmdate( 'H:i:s', $value );
160
161            case 'datetime':
162                return gmdate( 'Y-m-d\\TH:i:s\\Z', $value );
163        }
164    }
165
166    public function getInputOOUI( $value ) {
167        $params = [
168            'type' => $this->mType,
169            'value' => $value,
170            'name' => $this->mName,
171            'id' => $this->mID,
172        ];
173
174        $params += \OOUI\Element::configFromHtmlAttributes(
175            $this->getAttributes( [ 'disabled', 'readonly', 'min', 'max' ] )
176        );
177
178        if ( $this->mType === 'date' ) {
179            $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
180            return new \MediaWiki\Widget\DateInputWidget( $params );
181        } else {
182            return new \MediaWiki\Widget\DateTimeInputWidget( $params );
183        }
184    }
185
186    protected function getOOUIModules() {
187        if ( $this->mType === 'date' ) {
188            return [ 'mediawiki.widgets.DateInputWidget' ];
189        } else {
190            return [ 'mediawiki.widgets.datetime' ];
191        }
192    }
193
194    protected function shouldInfuseOOUI() {
195        return true;
196    }
197
198}
199
200/** @deprecated class alias since 1.42 */
201class_alias( HTMLDateTimeField::class, 'HTMLDateTimeField' );