From: Brad Jorsch Date: Thu, 25 May 2017 20:07:25 +0000 (-0400) Subject: API: Add the ability to flag parameter values as deprecated X-Git-Tag: 1.31.0-rc.0~2924^2~1 X-Git-Url: http://git.cyclocoop.org/%28?a=commitdiff_plain;h=e2c949677e0f4c64e088fb0d3393bed56886cbfa;p=lhc%2Fweb%2Fwiklou.git API: Add the ability to flag parameter values as deprecated This has a number of implications: * A deprecation warning is automatically generated if the value is used. * action=paraminfo can list it in a machine-readable manner. * It is automatically flagged in the help when message-per-value mode is used. * In values lists in the help, it's specially marked (currently strike-through). * ApiSandbox will mark it in the widgets (currently strike-through). Deprecation of submodules is not automatically detected here, that's left for a later patch. Bug: T123931 Change-Id: Idad6377063e457f9352a99df5c7cc15b1563579e --- diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30 index 77caadfb3a..6bd63d343b 100644 --- a/RELEASE-NOTES-1.30 +++ b/RELEASE-NOTES-1.30 @@ -81,6 +81,8 @@ production. * ApiBase::getDescriptionMessage() and the "apihelp-*-description" messages are deprecated. The existing message should be split between "apihelp-*-summary" and "apihelp-*-extended-description". +* (T123931) Individual values of multi-valued parameters can now be marked as + deprecated. === Languages updated in 1.30 === MediaWiki supports over 350 languages. Many localisations are updated diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 8ef317bc30..aa970f4476 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -188,13 +188,22 @@ abstract class ApiBase extends ContextSource { */ const PARAM_EXTRA_NAMESPACES = 18; - /* + /** * (boolean) Is the parameter sensitive? Note 'password'-type fields are * always sensitive regardless of the value of this field. * @since 1.29 */ const PARAM_SENSITIVE = 19; + /** + * (array) When PARAM_TYPE is an array, this indicates which of the values are deprecated. + * Keys are the deprecated parameter values, values define the warning + * message to emit: either boolean true (to use a default message) or a + * $msg for ApiBase::makeMessage(). + * @since 1.30 + */ + const PARAM_DEPRECATED_VALUES = 20; + /**@}*/ const ALL_DEFAULT_STRING = '*'; @@ -1025,6 +1034,9 @@ abstract class ApiBase extends ContextSource { $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false; + $deprecatedValues = isset( $paramSettings[self::PARAM_DEPRECATED_VALUES] ) + ? $paramSettings[self::PARAM_DEPRECATED_VALUES] + : []; $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? $paramSettings[self::PARAM_REQUIRED] : false; @@ -1266,6 +1278,29 @@ abstract class ApiBase extends ContextSource { } $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature ); } + + // Set a warning if a deprecated parameter value has been passed + $usedDeprecatedValues = $deprecatedValues && $value !== false + ? array_intersect( array_keys( $deprecatedValues ), (array)$value ) + : []; + if ( $usedDeprecatedValues ) { + $feature = "$encParamName="; + $m = $this; + while ( !$m->isMain() ) { + $p = $m->getParent(); + $name = $m->getModuleName(); + $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) ); + $feature = "{$param}={$name}&{$feature}"; + $m = $p; + } + foreach ( $usedDeprecatedValues as $v ) { + $msg = $deprecatedValues[$v]; + if ( $msg === true ) { + $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ]; + } + $this->addDeprecation( $msg, "$feature$v" ); + } + } } elseif ( $required ) { $this->dieWithError( [ 'apierror-missingparam', $paramName ] ); } @@ -2218,6 +2253,10 @@ abstract class ApiBase extends ContextSource { } $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE]; + $deprecatedValues = isset( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ) + ? $settings[ApiBase::PARAM_DEPRECATED_VALUES] + : []; + foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) { if ( isset( $valueMsgs[$value] ) ) { $msg = $valueMsgs[$value]; @@ -2230,7 +2269,8 @@ abstract class ApiBase extends ContextSource { $m = new ApiHelpParamValueMessage( $value, [ $m->getKey(), 'api-help-param-no-description' ], - $m->getParams() + $m->getParams(), + isset( $deprecatedValues[$value] ) ); $msgs[$param][] = $m->setContext( $this->getContext() ); } else { diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 7cd6d58b35..192c039651 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -489,12 +489,22 @@ class ApiHelp extends ApiBase { if ( is_array( $type ) ) { $count = count( $type ); + $deprecatedValues = isset( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ) + ? $settings[ApiBase::PARAM_DEPRECATED_VALUES] + : []; $links = isset( $settings[ApiBase::PARAM_VALUE_LINKS] ) ? $settings[ApiBase::PARAM_VALUE_LINKS] : []; - $values = array_map( function ( $v ) use ( $links ) { - // We can't know whether this contains LTR or RTL text. - $ret = $v === '' ? $v : Html::element( 'span', [ 'dir' => 'auto' ], $v ); + $values = array_map( function ( $v ) use ( $links, $deprecatedValues ) { + $attr = []; + if ( $v !== '' ) { + // We can't know whether this contains LTR or RTL text. + $attr['dir'] = 'auto'; + } + if ( isset( $deprecatedValues[$v] ) ) { + $attr['class'] = 'apihelp-deprecated-value'; + } + $ret = $attr ? Html::element( 'span', $attr, $v ) : $v; if ( isset( $links[$v] ) ) { $ret = "[[{$links[$v]}|$ret]]"; } diff --git a/includes/api/ApiHelpParamValueMessage.php b/includes/api/ApiHelpParamValueMessage.php index 45378eec1e..ebe4e26c1e 100644 --- a/includes/api/ApiHelpParamValueMessage.php +++ b/includes/api/ApiHelpParamValueMessage.php @@ -36,6 +36,7 @@ class ApiHelpParamValueMessage extends Message { protected $paramValue; + protected $deprecated; /** * @see Message::__construct @@ -43,11 +44,14 @@ class ApiHelpParamValueMessage extends Message { * @param string $paramValue Parameter value being documented * @param string $text Message to use. * @param array $params Parameters for the message. + * @param bool $deprecated Whether the value is deprecated * @throws InvalidArgumentException + * @since 1.30 Added the `$deprecated` parameter */ - public function __construct( $paramValue, $text, $params = [] ) { + public function __construct( $paramValue, $text, $params = [], $deprecated = false ) { parent::__construct( $text, $params ); $this->paramValue = $paramValue; + $this->deprecated = (bool)$deprecated; } /** @@ -58,14 +62,32 @@ class ApiHelpParamValueMessage extends Message { return $this->paramValue; } + /** + * Fetch the 'deprecated' flag + * @since 1.30 + * @return bool + */ + public function isDeprecated() { + return $this->deprecated; + } + /** * Fetch the message. * @return string */ public function fetchMessage() { if ( $this->message === null ) { + $dep = ''; + if ( $this->isDeprecated() ) { + $msg = new Message( 'api-help-param-deprecated' ); + $msg->interface = $this->interface; + $msg->language = $this->language; + $msg->useDatabase = $this->useDatabase; + $msg->title = $this->title; + $dep = '' . $msg->fetchMessage() . ' '; + } $this->message = ";{$this->paramValue}:" - . parent::fetchMessage(); + . $dep . parent::fetchMessage(); } return $this->message; } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index 39b5897832..3be743d026 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -448,6 +448,16 @@ class ApiParamInfo extends ApiBase { if ( !empty( $settings[ApiBase::PARAM_RANGE_ENFORCE] ) ) { $item['enforcerange'] = true; } + if ( !empty( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ) ) { + $deprecatedValues = array_keys( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ); + if ( is_array( $item['type'] ) ) { + $deprecatedValues = array_intersect( $deprecatedValues, $item['type'] ); + } + if ( $deprecatedValues ) { + $item['deprecatedvalues'] = array_values( $deprecatedValues ); + ApiResult::setIndexedTagName( $item['deprecatedvalues'], 'v' ); + } + } if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) { $item['info'] = []; diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index b2e03c80ee..031fbf76a7 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -385,7 +385,6 @@ class ApiParse extends ApiBase { } else { $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() ); } - $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' ); } if ( isset( $prop['headhtml'] ) ) { @@ -812,7 +811,6 @@ class ApiParse extends ApiBase { 'sections', 'revid', 'displaytitle', - 'headitems', 'headhtml', 'modules', 'jsconfigvars', @@ -824,11 +822,15 @@ class ApiParse extends ApiBase { 'limitreportdata', 'limitreporthtml', 'parsetree', - 'parsewarnings' + 'parsewarnings', + 'headitems', ], ApiBase::PARAM_HELP_MSG_PER_VALUE => [ 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ], ], + ApiBase::PARAM_DEPRECATED_VALUES => [ + 'headitems' => 'apiwarn-deprecation-parse-headitems', + ], ], 'wrapoutputclass' => 'mw-parser-output', 'pst' => false, diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 72b39b64a9..0dd8922859 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -62,18 +62,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $searchInfo = array_flip( $params['info'] ); $prop = array_flip( $params['prop'] ); - // Deprecated parameters - if ( isset( $prop['hasrelated'] ) ) { - $this->addDeprecation( - [ 'apiwarn-deprecation-parameter', 'srprop=hasrelated' ], 'action=search&srprop=hasrelated' - ); - } - if ( isset( $prop['score'] ) ) { - $this->addDeprecation( - [ 'apiwarn-deprecation-parameter', 'srprop=score' ], 'action=search&srprop=score' - ); - } - // Create search engine instance and set options $search = $this->buildSearchEngine( $params ); $search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] ); @@ -386,6 +374,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ], ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_HELP_MSG_PER_VALUE => [], + ApiBase::PARAM_DEPRECATED_VALUES => [ + 'score' => true, + 'hasrelated' => true + ], ], 'interwiki' => false, 'enablerewrites' => false, diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 1bb54c12a3..036515d6f5 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -182,17 +182,6 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] ); } - if ( isset( $this->prop['preferencestoken'] ) ) { - $p = $this->getModulePrefix(); - $this->addDeprecation( - [ - 'apiwarn-deprecation-withreplacement', - "{$p}prop=preferencestoken", - 'action=query&meta=tokens', - ], - "meta=userinfo&{$p}prop=preferencestoken" - ); - } if ( isset( $this->prop['preferencestoken'] ) && !$this->lacksSameOriginSecurity() && $user->isAllowed( 'editmyoptions' ) @@ -320,7 +309,6 @@ class ApiQueryUserInfo extends ApiQueryBase { 'rights', 'changeablegroups', 'options', - 'preferencestoken', 'editcount', 'ratelimits', 'email', @@ -329,6 +317,7 @@ class ApiQueryUserInfo extends ApiQueryBase { 'registrationdate', 'unreadcount', 'centralids', + 'preferencestoken', ], ApiBase::PARAM_HELP_MSG_PER_VALUE => [ 'unreadcount' => [ @@ -337,6 +326,13 @@ class ApiQueryUserInfo extends ApiQueryBase { self::WL_UNREAD_LIMIT . '+', ], ], + ApiBase::PARAM_DEPRECATED_VALUES => [ + 'preferencestoken' => [ + 'apiwarn-deprecation-withreplacement', + $this->getModulePrefix() . "prop=preferencestoken", + 'action=query&meta=tokens', + ] + ], ], 'attachedwiki' => null, ]; diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 06cf9af7ac..2a4dc0bf29 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -363,7 +363,7 @@ "apihelp-parse-paramvalue-prop-sections": "Gives the sections in the parsed wikitext.", "apihelp-parse-paramvalue-prop-revid": "Adds the revision ID of the parsed page.", "apihelp-parse-paramvalue-prop-displaytitle": "Adds the title of the parsed wikitext.", - "apihelp-parse-paramvalue-prop-headitems": "Deprecated. Gives items to put in the <head> of the page.", + "apihelp-parse-paramvalue-prop-headitems": "Gives items to put in the <head> of the page.", "apihelp-parse-paramvalue-prop-headhtml": "Gives parsed <head> of the page.", "apihelp-parse-paramvalue-prop-modules": "Gives the ResourceLoader modules used on the page. To load, use mw.loader.using(). Either jsconfigvars or encodedjsconfigvars must be requested jointly with modules.", "apihelp-parse-paramvalue-prop-jsconfigvars": "Gives the JavaScript configuration variables specific to the page. To apply, use mw.config.set().", @@ -1150,8 +1150,8 @@ "apihelp-query+search-paramvalue-prop-sectiontitle": "Adds the title of the matching section.", "apihelp-query+search-paramvalue-prop-categorysnippet": "Adds a parsed snippet of the matching category.", "apihelp-query+search-paramvalue-prop-isfilematch": "Adds a boolean indicating if the search matched file content.", - "apihelp-query+search-paramvalue-prop-score": "Deprecated and ignored.", - "apihelp-query+search-paramvalue-prop-hasrelated": "Deprecated and ignored.", + "apihelp-query+search-paramvalue-prop-score": "Ignored.", + "apihelp-query+search-paramvalue-prop-hasrelated": "Ignored.", "apihelp-query+search-param-limit": "How many total pages to return.", "apihelp-query+search-param-interwiki": "Include interwiki results in the search, if available.", "apihelp-query+search-param-backend": "Which search backend to use, if not the default.", @@ -1272,7 +1272,7 @@ "apihelp-query+userinfo-paramvalue-prop-rights": "Lists all the rights the current user has.", "apihelp-query+userinfo-paramvalue-prop-changeablegroups": "Lists the groups the current user can add to and remove from.", "apihelp-query+userinfo-paramvalue-prop-options": "Lists all preferences the current user has set.", - "apihelp-query+userinfo-paramvalue-prop-preferencestoken": "Deprecated. Get a token to change current user's preferences.", + "apihelp-query+userinfo-paramvalue-prop-preferencestoken": "Get a token to change current user's preferences.", "apihelp-query+userinfo-paramvalue-prop-editcount": "Adds the current user's edit count.", "apihelp-query+userinfo-paramvalue-prop-ratelimits": "Lists all rate limits applying to the current user.", "apihelp-query+userinfo-paramvalue-prop-realname": "Adds the user's real name.", diff --git a/resources/src/mediawiki.special/mediawiki.special.apisandbox.css b/resources/src/mediawiki.special/mediawiki.special.apisandbox.css index c3205bd084..99d0222814 100644 --- a/resources/src/mediawiki.special/mediawiki.special.apisandbox.css +++ b/resources/src/mediawiki.special/mediawiki.special.apisandbox.css @@ -98,3 +98,12 @@ /* Leave at least enough space for icon, indicator, and a sliver of text */ min-width: 6em; } + +.apihelp-deprecated { + font-weight: bold; + color: #f00; +} + +.apihelp-deprecated-value .oo-ui-labelElement-label { + text-decoration: line-through; +} diff --git a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js index bf26f50588..c32f9535a9 100644 --- a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js +++ b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js @@ -157,6 +157,15 @@ this.setIcon( ok ? null : 'alert' ); this.setIconTitle( ok ? '' : mw.message( 'apisandbox-alert-field' ).plain() ); return $.Deferred().resolve( ok ).promise(); + }, + createItemWidget: function ( data, label ) { + var item = OO.ui.CapsuleMultiselectWidget.prototype.createItemWidget.call( this, data, label ); + if ( this.paramInfo.deprecatedvalues && + this.paramInfo.deprecatedvalues.indexOf( data ) >= 0 + ) { + item.$element.addClass( 'apihelp-deprecated-value' ); + } + return item; } }, @@ -481,7 +490,15 @@ } items = $.map( pi.type, function ( v ) { - return new OO.ui.MenuOptionWidget( { data: String( v ), label: String( v ) } ); + var config = { + data: String( v ), + label: String( v ), + classes: [] + }; + if ( pi.deprecatedvalues && pi.deprecatedvalues.indexOf( v ) >= 0 ) { + config.classes.push( 'apihelp-deprecated-value' ); + } + return new OO.ui.MenuOptionWidget( config ); } ); if ( Util.apiBool( pi.multi ) ) { if ( pi.allspecifier !== undefined ) { @@ -510,7 +527,15 @@ $.extend( widget, WidgetMethods.dropdownWidget ); if ( Util.apiBool( pi.submodules ) ) { widget.getSubmodules = WidgetMethods.submoduleWidget.single; - widget.getMenu().on( 'choose', ApiSandbox.updateUI ); + widget.getMenu().on( 'select', ApiSandbox.updateUI ); + } + if ( pi.deprecatedvalues ) { + widget.getMenu().on( 'select', function ( item ) { + this.$element.toggleClass( + 'apihelp-deprecated-value', + pi.deprecatedvalues.indexOf( item.data ) >= 0 + ); + }, [], widget ); } } @@ -1040,7 +1065,7 @@ menu: { items: [] }, $overlay: $( '#mw-apisandbox-ui' ) } ); - formatDropdown.getMenu().on( 'choose', Util.onFormatDropdownChange ); + formatDropdown.getMenu().on( 'select', Util.onFormatDropdownChange ); } menu = formatDropdown.getMenu(); diff --git a/resources/src/mediawiki/mediawiki.apihelp.css b/resources/src/mediawiki/mediawiki.apihelp.css index 9a5a66f1aa..bd4741d17c 100644 --- a/resources/src/mediawiki/mediawiki.apihelp.css +++ b/resources/src/mediawiki/mediawiki.apihelp.css @@ -40,6 +40,10 @@ div.apihelp-linktrail { color: #f00; } +.apihelp-deprecated-value { + text-decoration: line-through; +} + .apihelp-unknown { color: #888; }