Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 78 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
HTMLTimezoneField | |
0.00% |
0 / 77 |
|
0.00% |
0 / 5 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
getTimezoneOptions | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 | |||
getTimeZoneList | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
30 | |||
validate | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getFieldClasses | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\HTMLForm\Field; |
4 | |
5 | use DateTime; |
6 | use DateTimeZone; |
7 | use InvalidArgumentException; |
8 | use MediaWiki\Context\RequestContext; |
9 | use MediaWiki\MainConfigNames; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\User\UserTimeCorrection; |
12 | use MediaWiki\Utils\MWTimestamp; |
13 | use Wikimedia\Message\ITextFormatter; |
14 | use Wikimedia\Message\MessageValue; |
15 | |
16 | /** |
17 | * Dropdown widget that allows the user to select a timezone, either by choosing a geographic zone, by using the wiki |
18 | * default, or by manually specifying an offset. It also has an option to fill the value from the browser settings. |
19 | * The value of this field is in a format accepted by UserTimeCorrection. |
20 | */ |
21 | class HTMLTimezoneField extends HTMLSelectOrOtherField { |
22 | private const FIELD_CLASS = 'mw-htmlform-timezone-field'; |
23 | |
24 | /** @var ITextFormatter */ |
25 | private $msgFormatter; |
26 | |
27 | /** |
28 | * @stable to call |
29 | * @inheritDoc |
30 | * Note that no options should be specified. |
31 | */ |
32 | public function __construct( $params ) { |
33 | if ( isset( $params['options'] ) ) { |
34 | throw new InvalidArgumentException( "Options should not be provided to " . __CLASS__ ); |
35 | } |
36 | $params['placeholder-message'] ??= 'timezone-useoffset-placeholder'; |
37 | $params['options'] = []; |
38 | parent::__construct( $params ); |
39 | $lang = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage(); |
40 | $langCode = $lang->getCode(); |
41 | $this->msgFormatter = MediaWikiServices::getInstance()->getMessageFormatterFactory() |
42 | ->getTextFormatter( $langCode ); |
43 | $this->mOptions = $this->getTimezoneOptions(); |
44 | } |
45 | |
46 | /** |
47 | * @return array<string|string[]> |
48 | */ |
49 | private function getTimezoneOptions(): array { |
50 | $opt = []; |
51 | |
52 | $localTZoffset = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LocalTZoffset ); |
53 | $timeZoneList = $this->getTimeZoneList(); |
54 | |
55 | $timestamp = MWTimestamp::getLocalInstance(); |
56 | // Check that the LocalTZoffset is the same as the local time zone offset |
57 | if ( $localTZoffset === (int)$timestamp->format( 'Z' ) / 60 ) { |
58 | $timezoneName = $timestamp->getTimezone()->getName(); |
59 | // Localize timezone |
60 | if ( isset( $timeZoneList[$timezoneName] ) ) { |
61 | $timezoneName = $timeZoneList[$timezoneName]['name']; |
62 | } |
63 | $server_tz_msg = $this->msgFormatter->format( |
64 | MessageValue::new( 'timezoneuseserverdefault', [ $timezoneName ] ) |
65 | ); |
66 | } else { |
67 | $tzstring = UserTimeCorrection::formatTimezoneOffset( $localTZoffset ); |
68 | $server_tz_msg = $this->msgFormatter->format( |
69 | MessageValue::new( 'timezoneuseserverdefault', [ $tzstring ] ) |
70 | ); |
71 | } |
72 | $opt[$server_tz_msg] = "System|$localTZoffset"; |
73 | $opt[$this->msgFormatter->format( MessageValue::new( 'timezoneuseoffset' ) )] = 'other'; |
74 | $opt[$this->msgFormatter->format( MessageValue::new( 'guesstimezone' ) )] = 'guess'; |
75 | |
76 | foreach ( $timeZoneList as $timeZoneInfo ) { |
77 | $region = $timeZoneInfo['region']; |
78 | if ( !isset( $opt[$region] ) ) { |
79 | $opt[$region] = []; |
80 | } |
81 | $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection']; |
82 | } |
83 | return $opt; |
84 | } |
85 | |
86 | /** |
87 | * Get a list of all time zones |
88 | * @return string[][] A list of all time zones. The system name of the time zone is used as key and |
89 | * the value is an array which contains localized name, the timecorrection value used for |
90 | * preferences and the region |
91 | */ |
92 | private function getTimeZoneList(): array { |
93 | $identifiers = DateTimeZone::listIdentifiers(); |
94 | '@phan-var array|false $identifiers'; // See phan issue #3162 |
95 | if ( $identifiers === false ) { |
96 | return []; |
97 | } |
98 | sort( $identifiers ); |
99 | |
100 | $tzRegions = [ |
101 | 'Africa' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-africa' ) ), |
102 | 'America' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-america' ) ), |
103 | 'Antarctica' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-antarctica' ) ), |
104 | 'Arctic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-arctic' ) ), |
105 | 'Asia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-asia' ) ), |
106 | 'Atlantic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-atlantic' ) ), |
107 | 'Australia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-australia' ) ), |
108 | 'Europe' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-europe' ) ), |
109 | 'Indian' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-indian' ) ), |
110 | 'Pacific' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-pacific' ) ), |
111 | ]; |
112 | asort( $tzRegions ); |
113 | |
114 | $timeZoneList = []; |
115 | |
116 | $now = new DateTime(); |
117 | |
118 | foreach ( $identifiers as $identifier ) { |
119 | $parts = explode( '/', $identifier, 2 ); |
120 | |
121 | // DateTimeZone::listIdentifiers() returns a number of |
122 | // backwards-compatibility entries. This filters them out of the |
123 | // list presented to the user. |
124 | if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) { |
125 | continue; |
126 | } |
127 | |
128 | // Localize region |
129 | $parts[0] = $tzRegions[$parts[0]]; |
130 | |
131 | $dateTimeZone = new DateTimeZone( $identifier ); |
132 | $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 ); |
133 | |
134 | $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] ); |
135 | $value = "ZoneInfo|$minDiff|$identifier"; |
136 | |
137 | $timeZoneList[$identifier] = [ |
138 | 'name' => $display, |
139 | 'timecorrection' => $value, |
140 | 'region' => $parts[0], |
141 | ]; |
142 | } |
143 | |
144 | return $timeZoneList; |
145 | } |
146 | |
147 | /** |
148 | * @inheritDoc |
149 | */ |
150 | public function validate( $value, $alldata ) { |
151 | $p = parent::validate( $value, $alldata ); |
152 | if ( $p !== true ) { |
153 | return $p; |
154 | } |
155 | |
156 | if ( !( new UserTimeCorrection( $value ) )->isValid() ) { |
157 | return $this->mParent->msg( 'timezone-invalid' )->escaped(); |
158 | } |
159 | |
160 | return true; |
161 | } |
162 | |
163 | /** |
164 | * @inheritDoc |
165 | */ |
166 | protected function getFieldClasses(): array { |
167 | $classes = parent::getFieldClasses(); |
168 | $classes[] = self::FIELD_CLASS; |
169 | return $classes; |
170 | } |
171 | } |
172 | |
173 | /** @deprecated class alias since 1.42 */ |
174 | class_alias( HTMLTimezoneField::class, 'HTMLTimezoneField' ); |