From: Brad Jorsch Date: Thu, 22 Sep 2016 17:28:29 +0000 (-0400) Subject: HTMLForm: Add HTMLDateTimeField X-Git-Tag: 1.31.0-rc.0~5323^2 X-Git-Url: http://git.heureux-cyclage.org/?a=commitdiff_plain;h=12bdc8421902dcde5c15e7cfb6b58ecbfbacebba;p=lhc%2Fweb%2Fwiklou.git HTMLForm: Add HTMLDateTimeField And to do that, an OOUI PHP widget for mw.widgets.datetime.DateTimeInputWidget too. Bug: T146340 Change-Id: Iaa8b5892b6c3a1f3698cef59684cc3cdc9d483ea --- diff --git a/autoload.php b/autoload.php index 433f907d09..7a5a3f1c85 100644 --- a/autoload.php +++ b/autoload.php @@ -527,6 +527,7 @@ $wgAutoloadLocalClasses = [ 'HTMLCheckField' => __DIR__ . '/includes/htmlform/fields/HTMLCheckField.php', 'HTMLCheckMatrix' => __DIR__ . '/includes/htmlform/fields/HTMLCheckMatrix.php', 'HTMLComboboxField' => __DIR__ . '/includes/htmlform/fields/HTMLComboboxField.php', + 'HTMLDateTimeField' => __DIR__ . '/includes/htmlform/fields/HTMLDateTimeField.php', 'HTMLEditTools' => __DIR__ . '/includes/htmlform/fields/HTMLEditTools.php', 'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php', 'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php', @@ -910,6 +911,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php', 'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php', 'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php', + 'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php', 'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php', 'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php', 'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php', diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 567e692edd..c65d97f0ff 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -153,6 +153,9 @@ class HTMLForm extends ContextSource { 'checkmatrix' => 'HTMLCheckMatrix', 'cloner' => 'HTMLFormFieldCloner', 'autocompleteselect' => 'HTMLAutoCompleteSelectField', + 'date' => 'HTMLDateTimeField', + 'time' => 'HTMLDateTimeField', + 'datetime' => 'HTMLDateTimeField', // HTMLTextField will output the correct type="" attribute automagically. // There are about four zillion other HTML5 input types, like range, but // we don't use those at the moment, so no point in adding all of them. diff --git a/includes/htmlform/fields/HTMLDateTimeField.php b/includes/htmlform/fields/HTMLDateTimeField.php new file mode 100644 index 0000000000..66f89f9564 --- /dev/null +++ b/includes/htmlform/fields/HTMLDateTimeField.php @@ -0,0 +1,190 @@ + '[0-9]{4}-[01][0-9]-[0-3][0-9]', + 'time' => '[0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?', + '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?', + ]; + + protected $mType = 'datetime'; + + public function __construct( $params ) { + parent::__construct( $params ); + + $this->mType = array_key_exists( 'type', $params ) + ? $params['type'] + : 'datetime'; + + if ( !in_array( $this->mType, [ 'date', 'time', 'datetime' ] ) ) { + throw new InvalidArgumentException( "Invalid type '$this->mType'" ); + } + + $this->mClass .= ' mw-htmlform-datetime-field'; + } + + public function getAttributes( array $list ) { + $parentList = array_diff( $list, [ 'min', 'max' ] ); + $ret = parent::getAttributes( $parentList ); + + if ( in_array( 'placeholder', $list ) && !isset( $ret['placeholder'] ) ) { + // Messages: htmlform-date-placeholder htmlform-time-placeholder htmlform-datetime-placeholder + $ret['placeholder'] = $this->msg( "htmlform-{$this->mType}-placeholder" )->text(); + } + + if ( in_array( 'min', $list ) && isset( $this->mParams['min'] ) ) { + $min = $this->parseDate( $this->mParams['min'] ); + if ( $min ) { + $ret['min'] = $this->formatDate( $min ); + // Because Html::expandAttributes filters it out + $ret['data-min'] = $ret['min']; + } + } + if ( in_array( 'max', $list ) && isset( $this->mParams['max'] ) ) { + $max = $this->parseDate( $this->mParams['max'] ); + if ( $max ) { + $ret['max'] = $this->formatDate( $max ); + // Because Html::expandAttributes filters it out + $ret['data-max'] = $ret['max']; + } + } + + $ret['step'] = 1; + // Because Html::expandAttributes filters it out + $ret['data-step'] = 1; + + $ret['type'] = $this->mType; + $ret['pattern'] = static::$patterns[$this->mType]; + + return $ret; + } + + function loadDataFromRequest( $request ) { + if ( !$request->getCheck( $this->mName ) ) { + return $this->getDefault(); + } + + $value = $request->getText( $this->mName ); + $date = $this->parseDate( $value ); + return $date ? $this->formatDate( $date ) : $value; + } + + function validate( $value, $alldata ) { + $p = parent::validate( $value, $alldata ); + + if ( $p !== true ) { + return $p; + } + + if ( $value === '' ) { + // required was already checked by parent::validate + return true; + } + + $date = $this->parseDate( $value ); + if ( !$date ) { + // Messages: htmlform-date-invalid htmlform-time-invalid htmlform-datetime-invalid + return $this->msg( "htmlform-{$this->mType}-invalid" )->parseAsBlock(); + } + + if ( isset( $this->mParams['min'] ) ) { + $min = $this->parseDate( $this->mParams['min'] ); + if ( $min && $date < $min ) { + // Messages: htmlform-date-toolow htmlform-time-toolow htmlform-datetime-toolow + return $this->msg( "htmlform-{$this->mType}-toolow", $this->formatDate( $min ) ) + ->parseAsBlock(); + } + } + + if ( isset( $this->mParams['max'] ) ) { + $max = $this->parseDate( $this->mParams['max'] ); + if ( $max && $date > $max ) { + // Messages: htmlform-date-toohigh htmlform-time-toohigh htmlform-datetime-toohigh + return $this->msg( "htmlform-{$this->mType}-toohigh", $this->formatDate( $max ) ) + ->parseAsBlock(); + } + } + + return true; + } + + protected function parseDate( $value ) { + $value = trim( $value ); + + if ( $this->mType === 'date' ) { + $value .= ' T00:00:00+0000'; + } + if ( $this->mType === 'time' ) { + $value = '1970-01-01 ' . $value . '+0000'; + } + + try { + $date = new DateTime( $value, new DateTimeZone( 'GMT' ) ); + return $date->getTimestamp(); + } catch ( Exception $ex ) { + return 0; + } + } + + protected function formatDate( $value ) { + switch ( $this->mType ) { + case 'date': + return gmdate( 'Y-m-d', $value ); + + case 'time': + return gmdate( 'H:i:s', $value ); + + case 'datetime': + return gmdate( 'Y-m-d\\TH:i:s\\Z', $value ); + } + } + + public function getInputOOUI( $value ) { + $params = [ + 'type' => $this->mType, + 'value' => $value, + 'name' => $this->mName, + 'id' => $this->mID, + ]; + + if ( isset( $this->mParams['min'] ) ) { + $min = $this->parseDate( $this->mParams['min'] ); + if ( $min ) { + $params['min'] = $this->formatDate( $min ); + } + } + if ( isset( $this->mParams['max'] ) ) { + $max = $this->parseDate( $this->mParams['max'] ); + if ( $max ) { + $params['max'] = $this->formatDate( $max ); + } + } + + return new MediaWiki\Widget\DateTimeInputWidget( $params ); + } + + protected function getOOUIModules() { + return [ 'mediawiki.widgets.datetime' ]; + } + + protected function shouldInfuseOOUI() { + return true; + } + +} diff --git a/includes/widget/AUTHORS.txt b/includes/widget/AUTHORS.txt index b91596b01e..ca188ba1fe 100644 --- a/includes/widget/AUTHORS.txt +++ b/includes/widget/AUTHORS.txt @@ -2,6 +2,7 @@ Authors (alphabetically) Alex Monk Bartosz Dziewoński +Brad Jorsch Ed Sanders Florian Schmidt James D. Forrester diff --git a/includes/widget/DateTimeInputWidget.php b/includes/widget/DateTimeInputWidget.php new file mode 100644 index 0000000000..f0d5cdb64f --- /dev/null +++ b/includes/widget/DateTimeInputWidget.php @@ -0,0 +1,76 @@ +type set before calling the parent constructor + if ( isset( $config['type'] ) ) { + $this->type = $config['type']; + } else { + throw new \InvalidArgumentException( '$config[\'type\'] must be specified' ); + } + + // Parent constructor + parent::__construct( $config ); + + // Properties, which are ignored in PHP and just shipped back to JS + if ( isset( $config['min'] ) ) { + $this->min = $config['min']; + } + if ( isset( $config['max'] ) ) { + $this->max = $config['max']; + } + if ( isset( $config['clearable'] ) ) { + $this->clearable = $config['clearable']; + } + + // Initialization + $this->addClasses( [ 'mw-widgets-datetime-dateTimeInputWidget' ] ); + } + + protected function getJavaScriptClassName() { + return 'mw.widgets.datetime.DateTimeInputWidget'; + } + + public function getConfig( &$config ) { + $config['type'] = $this->type; + if ( $this->min !== null ) { + $config['min'] = $this->min; + } + if ( $this->max !== null ) { + $config['max'] = $this->max; + } + if ( $this->clearable !== null ) { + $config['clearable'] = $this->clearable; + } + return parent::getConfig( $config ); + } + + protected function getInputElement( $config ) { + return ( new Tag( 'input' ) )->setAttributes( [ 'type' => $this->type ] ); + } +} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 338d650d76..0056883e10 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -3789,6 +3789,18 @@ "htmlform-cloner-create": "Add more", "htmlform-cloner-delete": "Remove", "htmlform-cloner-required": "At least one value is required.", + "htmlform-date-placeholder": "YYYY-MM-DD", + "htmlform-time-placeholder": "HH:MM:SS", + "htmlform-datetime-placeholder": "YYYY-MM-DD HH:MM:SS", + "htmlform-date-invalid": "The value you specified is not a recognized date. Try using YYYY-MM-DD format.", + "htmlform-time-invalid": "The value you specified is not a recognized time. Try using HH:MM:SS format.", + "htmlform-datetime-invalid": "The value you specified is not a recognized date and time. Try using YYYY-MM-DD HH:MM:SS format.", + "htmlform-date-toolow": "The value you specified is before the earliest allowed date of $1.", + "htmlform-date-toohigh": "The value you specified is after the latest allowed date of $1.", + "htmlform-time-toolow": "The value you specified is before the earliest allowed time of $1.", + "htmlform-time-toohigh": "The value you specified is after the latest allowed time of $1.", + "htmlform-datetime-toolow": "The value you specified is before the earliest allowed date and time of $1.", + "htmlform-datetime-toohigh": "The value you specified is after the latest allowed date and time of $1.", "htmlform-title-badnamespace": "[[:$1]] is not in the \"{{ns:$2}}\" namespace.", "htmlform-title-not-creatable": "\"$1\" is not a creatable page title", "htmlform-title-not-exists": "$1 does not exist.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 4ec5cec5fa..9b29e30612 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -3973,6 +3973,18 @@ "htmlform-cloner-create": "Used as the text for the button that adds a row to a multi-input HTML form element.\n\nSee also:\n* {{msg-mw|htmlform-cloner-delete}}\n* {{msg-mw|htmlform-cloner-required}}", "htmlform-cloner-delete": "Used as the text for the button that removes a row from a multi-input HTML form element\n\nSee also:\n* {{msg-mw|htmlform-cloner-create}}\n* {{msg-mw|htmlform-cloner-required}}\n{{Identical|Remove}}", "htmlform-cloner-required": "Used as an error message in HTML forms.\n\nSee also:\n* {{msg-mw|htmlform-required}}\n* {{msg-mw|htmlform-cloner-create}}\n* {{msg-mw|htmlform-cloner-delete}}", + "htmlform-date-placeholder": "Used as initial placeholder text in \"date\" input boxes. This date MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters. You can localise the letters to your language or script, but you should not change the format.", + "htmlform-date-invalid": "Used as error message in HTML forms. This date MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-date-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toohigh}}\n* {{msg-mw|Htmlform-required}}", + "htmlform-date-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-date-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toohigh}}", + "htmlform-date-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-date-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-date-toolow}}", + "htmlform-time-placeholder": "Used as initial placeholder text in \"time\" input boxes. This time MUST be formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.", + "htmlform-time-invalid": "Used as error message in HTML forms. This time MUST be formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-time-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toohigh}}\n* {{msg-mw|Htmlform-required}}", + "htmlform-time-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-time-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toohigh}}", + "htmlform-time-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-time-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-time-toolow}}", + "htmlform-datetime-placeholder": "Used as initial placeholder text in \"datetime\" input boxes. This date and time MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters, followed by a space (or the letter 'T'), followed by a time formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.", + "htmlform-datetime-invalid": "Used as error message in HTML forms. This date and time MUST be formatted as a 4-digit year, 2-digit month, and 2-digit day, in the Gregorian calendar, separated with ASCII hyphen ('-') characters, followed by a space (or the letter 'T'), followed by a time formatted as a 2-digit hour 00 to 23, a 2-digit minute, and an optional 2-digit second, all separated by ASCII colons. You can localise the letters to your language or script, but you should not change the format.\n\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-placeholder}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toolow}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toohigh}}\n* {{msg-mw|Htmlform-required}}", + "htmlform-datetime-toolow": "Used as error message in HTML forms. Parameters:\n* $1 - minimum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toohigh}}", + "htmlform-datetime-toohigh": "Used as error message in HTML forms. Parameters:\n* $1 - maximum date\nSee also:\n* {{msg-mw|Htmlform-invalid-input}}\n* {{msg-mw|Htmlform-required}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-invalid}}\n* {{msg-mw|Apifeatureusage-htmlform-datetime-toolow}}", "htmlform-title-badnamespace": "Error message shown if the page title provided by the user is not in the required namespace. $1 is the page, $2 is the numerical namespace index.", "htmlform-title-not-creatable": "Error message shown if the page title provided by the user is not creatable (a special page). $1 is the page title.", "htmlform-title-not-exists": "Error message shown if the page title provided by the user does not exist. $1 is the page title.", diff --git a/resources/Resources.php b/resources/Resources.php index 89168db6ca..295f99a741 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1083,6 +1083,7 @@ return [ 'resources/src/mediawiki/htmlform/autocomplete.js', 'resources/src/mediawiki/htmlform/autoinfuse.js', 'resources/src/mediawiki/htmlform/checkmatrix.js', + 'resources/src/mediawiki/htmlform/datetime.js', 'resources/src/mediawiki/htmlform/cloner.js', 'resources/src/mediawiki/htmlform/hide-if.js', 'resources/src/mediawiki/htmlform/multiselect.js', diff --git a/resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js b/resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js index 01d3442798..91f797dad0 100644 --- a/resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js +++ b/resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js @@ -144,6 +144,19 @@ } ); } + // Our form input *should* be type="hidden". But if we're infusing from + // PHP, it's not. + if ( this.$input.attr( 'type' ) !== 'hidden' ) { + try { + this.$input.attr( 'type', 'hidden' ); + } catch ( e ) { + } + // IE <= 8, and IE 9 in quirks mode, doesn't allow changing the + // type, so just hide the field with CSS. IE 9 in quirks mode + // doesn't even throw an error, so do that unconditionally. Sigh. + this.$input.css( 'display', 'none' ); + } + // Initialization this.setTabIndex( -1 ); diff --git a/resources/src/mediawiki/htmlform/datetime.js b/resources/src/mediawiki/htmlform/datetime.js new file mode 100644 index 0000000000..2fd239696b --- /dev/null +++ b/resources/src/mediawiki/htmlform/datetime.js @@ -0,0 +1,44 @@ +/* + * HTMLForm enhancements: + * Add minimal help for date and time fields + */ +( function ( mw ) { + + mw.hook( 'htmlform.enhance' ).add( function ( $root ) { + var supported = {}; + + $root + .find( 'input.mw-htmlform-datetime-field' ) + .each( function () { + var input, + type = this.getAttribute( 'type' ); + + if ( type !== 'date' && type !== 'time' && type !== 'datetime' ) { + // WTF? + return; + } + + if ( supported[ type ] === undefined ) { + // Assume that if the browser implements validation (so it + // rejects "bogus" as a value) then it supports a proper UI too. + input = document.createElement( 'input' ); + input.setAttribute( 'type', type ); + input.value = 'bogus'; + supported[ type ] = ( input.value !== 'bogus' ); + } + + if ( supported[ type ] ) { + if ( !this.getAttribute( 'min' ) ) { + this.setAttribute( 'min', this.getAttribute( 'data-min' ) ); + } + if ( !this.getAttribute( 'max' ) ) { + this.setAttribute( 'max', this.getAttribute( 'data-max' ) ); + } + if ( !this.getAttribute( 'step' ) ) { + this.setAttribute( 'step', this.getAttribute( 'data-step' ) ); + } + } + } ); + } ); + +}( mediaWiki ) );