This patch introduces HTMLTimezoneField, an HTMLForm field type that allows the user to select a timezone, either from a geographic zone, by manually entering an offset, or using the wiki/browser default. This logic is extracted from DefaultPreferencesFactory so that it can be reused elsewhere. The widget itself is really just an HTMLSelectOrOtherField, it's just the list of options and the JS logic that is special. Bug: T309629 Change-Id: I99a00dff7e3319ce45883191daee16bec1ed68ba
151 lines
5.3 KiB
PHP
151 lines
5.3 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\MediaWikiServices;
|
|
use Wikimedia\Message\ITextFormatter;
|
|
use Wikimedia\Message\MessageValue;
|
|
|
|
/**
|
|
* Dropdown widget that allows the user to select a timezone, either by choosing a geographic zone, by using the wiki
|
|
* default, or by manually specifying an offset. It also has an option to fill the value from the browser settings.
|
|
* The value of this field is in a format accepted by UserTimeCorrection.
|
|
*/
|
|
class HTMLTimezoneField extends HTMLSelectOrOtherField {
|
|
private const FIELD_CLASS = 'mw-htmlform-timezone-field';
|
|
|
|
/** @var ITextFormatter */
|
|
private $msgFormatter;
|
|
|
|
/**
|
|
* @stable to call
|
|
* @inheritDoc
|
|
* Note that no options should be specified.
|
|
*/
|
|
public function __construct( $params ) {
|
|
if ( isset( $params['options'] ) ) {
|
|
throw new InvalidArgumentException( "Options should not be provided to " . __CLASS__ );
|
|
}
|
|
$params['placeholder-message'] = $params['placeholder-message'] ?? 'timezone-useoffset-placeholder';
|
|
$params['options'] = [];
|
|
parent::__construct( $params );
|
|
$lang = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage();
|
|
$langCode = $lang->getCode();
|
|
$this->msgFormatter = MediaWikiServices::getInstance()->getMessageFormatterFactory()
|
|
->getTextFormatter( $langCode );
|
|
$this->mOptions = $this->getTimezoneOptions();
|
|
}
|
|
|
|
/**
|
|
* @return array<string|string[]>
|
|
*/
|
|
private function getTimezoneOptions(): array {
|
|
$opt = [];
|
|
|
|
$localTZoffset = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LocalTZoffset );
|
|
$timeZoneList = $this->getTimeZoneList();
|
|
|
|
$timestamp = MWTimestamp::getLocalInstance();
|
|
// Check that the LocalTZoffset is the same as the local time zone offset
|
|
if ( $localTZoffset === (int)$timestamp->format( 'Z' ) / 60 ) {
|
|
$timezoneName = $timestamp->getTimezone()->getName();
|
|
// Localize timezone
|
|
if ( isset( $timeZoneList[$timezoneName] ) ) {
|
|
$timezoneName = $timeZoneList[$timezoneName]['name'];
|
|
}
|
|
$server_tz_msg = $this->msgFormatter->format(
|
|
MessageValue::new( 'timezoneuseserverdefault', [ $timezoneName ] )
|
|
);
|
|
} else {
|
|
$tzstring = sprintf(
|
|
'%+03d:%02d',
|
|
floor( $localTZoffset / 60 ),
|
|
abs( $localTZoffset ) % 60
|
|
);
|
|
$server_tz_msg = $this->msgFormatter->format(
|
|
MessageValue::new( 'timezoneuseserverdefault', [ $tzstring ] )
|
|
);
|
|
}
|
|
$opt[$server_tz_msg] = "System|$localTZoffset";
|
|
$opt[$this->msgFormatter->format( MessageValue::new( 'timezoneuseoffset' ) )] = 'other';
|
|
$opt[$this->msgFormatter->format( MessageValue::new( 'guesstimezone' ) )] = 'guess';
|
|
|
|
foreach ( $timeZoneList as $timeZoneInfo ) {
|
|
$region = $timeZoneInfo['region'];
|
|
if ( !isset( $opt[$region] ) ) {
|
|
$opt[$region] = [];
|
|
}
|
|
$opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
|
|
}
|
|
return $opt;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all time zones
|
|
* @return string[][] A list of all time zones. The system name of the time zone is used as key and
|
|
* the value is an array which contains localized name, the timecorrection value used for
|
|
* preferences and the region
|
|
*/
|
|
private function getTimeZoneList(): array {
|
|
$identifiers = DateTimeZone::listIdentifiers();
|
|
// @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3162
|
|
if ( $identifiers === false ) {
|
|
return [];
|
|
}
|
|
sort( $identifiers );
|
|
|
|
$tzRegions = [
|
|
'Africa' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-africa' ) ),
|
|
'America' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-america' ) ),
|
|
'Antarctica' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-antarctica' ) ),
|
|
'Arctic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-arctic' ) ),
|
|
'Asia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-asia' ) ),
|
|
'Atlantic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-atlantic' ) ),
|
|
'Australia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-australia' ) ),
|
|
'Europe' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-europe' ) ),
|
|
'Indian' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-indian' ) ),
|
|
'Pacific' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-pacific' ) ),
|
|
];
|
|
asort( $tzRegions );
|
|
|
|
$timeZoneList = [];
|
|
|
|
$now = new DateTime();
|
|
|
|
foreach ( $identifiers as $identifier ) {
|
|
$parts = explode( '/', $identifier, 2 );
|
|
|
|
// DateTimeZone::listIdentifiers() returns a number of
|
|
// backwards-compatibility entries. This filters them out of the
|
|
// list presented to the user.
|
|
if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Localize region
|
|
$parts[0] = $tzRegions[$parts[0]];
|
|
|
|
$dateTimeZone = new DateTimeZone( $identifier );
|
|
$minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
|
|
|
|
$display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
|
|
$value = "ZoneInfo|$minDiff|$identifier";
|
|
|
|
$timeZoneList[$identifier] = [
|
|
'name' => $display,
|
|
'timecorrection' => $value,
|
|
'region' => $parts[0],
|
|
];
|
|
}
|
|
|
|
return $timeZoneList;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
protected function getFieldClasses(): array {
|
|
$classes = parent::getFieldClasses();
|
|
$classes[] = self::FIELD_CLASS;
|
|
return $classes;
|
|
}
|
|
}
|