API: Add the ability to flag parameter values as deprecated
authorBrad Jorsch <bjorsch@wikimedia.org>
Thu, 25 May 2017 20:07:25 +0000 (16:07 -0400)
committerBrad Jorsch <bjorsch@wikimedia.org>
Tue, 20 Jun 2017 15:41:26 +0000 (11:41 -0400)
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

12 files changed:
RELEASE-NOTES-1.30
includes/api/ApiBase.php
includes/api/ApiHelp.php
includes/api/ApiHelpParamValueMessage.php
includes/api/ApiParamInfo.php
includes/api/ApiParse.php
includes/api/ApiQuerySearch.php
includes/api/ApiQueryUserInfo.php
includes/api/i18n/en.json
resources/src/mediawiki.special/mediawiki.special.apisandbox.css
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki/mediawiki.apihelp.css

index 77caadf..6bd63d3 100644 (file)
@@ -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
index 8ef317b..aa970f4 100644 (file)
@@ -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 {
index 7cd6d58..192c039 100644 (file)
@@ -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]]";
                                                                }
index 45378ee..ebe4e26 100644 (file)
@@ -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 = '<span class="apihelp-deprecated">' . $msg->fetchMessage() . '</span> ';
+                       }
                        $this->message = ";<span dir=\"ltr\" lang=\"en\">{$this->paramValue}</span>:"
-                               . parent::fetchMessage();
+                               . $dep . parent::fetchMessage();
                }
                return $this->message;
        }
index 39b5897..3be743d 100644 (file)
@@ -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'] = [];
index b2e03c8..031fbf7 100644 (file)
@@ -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,
index 72b39b6..0dd8922 100644 (file)
@@ -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,
index 1bb54c1..036515d 100644 (file)
@@ -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,
                ];
index 06cf9af..2a4dc0b 100644 (file)
        "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": "<span class=\"apihelp-deprecated\">Deprecated.</span> Gives items to put in the <code>&lt;head&gt;</code> of the page.",
+       "apihelp-parse-paramvalue-prop-headitems": "Gives items to put in the <code>&lt;head&gt;</code> of the page.",
        "apihelp-parse-paramvalue-prop-headhtml": "Gives parsed <code>&lt;head&gt;</code> of the page.",
        "apihelp-parse-paramvalue-prop-modules": "Gives the ResourceLoader modules used on the page. To load, use <code>mw.loader.using()</code>. Either <kbd>jsconfigvars</kbd> or <kbd>encodedjsconfigvars</kbd> must be requested jointly with <kbd>modules</kbd>.",
        "apihelp-parse-paramvalue-prop-jsconfigvars": "Gives the JavaScript configuration variables specific to the page. To apply, use <code>mw.config.set()</code>.",
        "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": "<span class=\"apihelp-deprecated\">Deprecated and ignored.</span>",
-       "apihelp-query+search-paramvalue-prop-hasrelated": "<span class=\"apihelp-deprecated\">Deprecated and ignored.</span>",
+       "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.",
        "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": "<span class=\"apihelp-deprecated\">Deprecated.</span> 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.",
index c3205bd..99d0222 100644 (file)
        /* 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;
+}
index bf26f50..c32f953 100644 (file)
                                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;
                        }
                },
 
                                        }
 
                                        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 ) {
                                                $.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 );
                                                }
                                        }
 
                                                menu: { items: [] },
                                                $overlay: $( '#mw-apisandbox-ui' )
                                        } );
-                                       formatDropdown.getMenu().on( 'choose', Util.onFormatDropdownChange );
+                                       formatDropdown.getMenu().on( 'select', Util.onFormatDropdownChange );
                                }
 
                                menu = formatDropdown.getMenu();
index 9a5a66f..bd4741d 100644 (file)
@@ -40,6 +40,10 @@ div.apihelp-linktrail {
        color: #f00;
 }
 
+.apihelp-deprecated-value {
+       text-decoration: line-through;
+}
+
 .apihelp-unknown {
        color: #888;
 }