[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/src/mediawiki.api/ -> mediawiki.api.js (source)

   1  ( function ( mw, $ ) {
   2  
   3      // We allow people to omit these default parameters from API requests
   4      // there is very customizable error handling here, on a per-call basis
   5      // wondering, would it be simpler to make it easy to clone the api object,
   6      // change error handling, and use that instead?
   7      var defaultOptions = {
   8  
   9              // Query parameters for API requests
  10              parameters: {
  11                  action: 'query',
  12                  format: 'json'
  13              },
  14  
  15              // Ajax options for jQuery.ajax()
  16              ajax: {
  17                  url: mw.util.wikiScript( 'api' ),
  18  
  19                  timeout: 30 * 1000, // 30 seconds
  20  
  21                  dataType: 'json'
  22              }
  23          },
  24          // Keyed by ajax url and symbolic name for the individual request
  25          promises = {};
  26  
  27      // Pre-populate with fake ajax promises to save http requests for tokens
  28      // we already have on the page via the user.tokens module (bug 34733).
  29      promises[ defaultOptions.ajax.url ] = {};
  30      $.each( mw.user.tokens.get(), function ( key, value ) {
  31          // This requires #getToken to use the same key as user.tokens.
  32          // Format: token-type + "Token" (eg. editToken, patrolToken, watchToken).
  33          promises[ defaultOptions.ajax.url ][ key ] = $.Deferred()
  34              .resolve( value )
  35              .promise( { abort: function () {} } );
  36      } );
  37  
  38      /**
  39       * Constructor to create an object to interact with the API of a particular MediaWiki server.
  40       * mw.Api objects represent the API of a particular MediaWiki server.
  41       *
  42       * TODO: Share API objects with exact same config.
  43       *
  44       *     var api = new mw.Api();
  45       *     api.get( {
  46       *         action: 'query',
  47       *         meta: 'userinfo'
  48       *     } ).done ( function ( data ) {
  49       *         console.log( data );
  50       *     } );
  51       *
  52       * @class
  53       *
  54       * @constructor
  55       * @param {Object} options See defaultOptions documentation above. Ajax options can also be
  56       *  overridden for each individual request to {@link jQuery#ajax} later on.
  57       */
  58      mw.Api = function ( options ) {
  59  
  60          if ( options === undefined ) {
  61              options = {};
  62          }
  63  
  64          // Force a string if we got a mw.Uri object
  65          if ( options.ajax && options.ajax.url !== undefined ) {
  66              options.ajax.url = String( options.ajax.url );
  67          }
  68  
  69          options.parameters = $.extend( {}, defaultOptions.parameters, options.parameters );
  70          options.ajax = $.extend( {}, defaultOptions.ajax, options.ajax );
  71  
  72          this.defaults = options;
  73      };
  74  
  75      mw.Api.prototype = {
  76  
  77          /**
  78           * Normalize the ajax options for compatibility and/or convenience methods.
  79           *
  80           * @param {Object} [arg] An object contaning one or more of options.ajax.
  81           * @return {Object} Normalized ajax options.
  82           */
  83          normalizeAjaxOptions: function ( arg ) {
  84              // Arg argument is usually empty
  85              // (before MW 1.20 it was used to pass ok callbacks)
  86              var opts = arg || {};
  87              // Options can also be a success callback handler
  88              if ( typeof arg === 'function' ) {
  89                  opts = { ok: arg };
  90              }
  91              return opts;
  92          },
  93  
  94          /**
  95           * Perform API get request
  96           *
  97           * @param {Object} parameters
  98           * @param {Object|Function} [ajaxOptions]
  99           * @return {jQuery.Promise}
 100           */
 101          get: function ( parameters, ajaxOptions ) {
 102              ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
 103              ajaxOptions.type = 'GET';
 104              return this.ajax( parameters, ajaxOptions );
 105          },
 106  
 107          /**
 108           * Perform API post request
 109           *
 110           * TODO: Post actions for non-local hostnames will need proxy.
 111           *
 112           * @param {Object} parameters
 113           * @param {Object|Function} [ajaxOptions]
 114           * @return {jQuery.Promise}
 115           */
 116          post: function ( parameters, ajaxOptions ) {
 117              ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
 118              ajaxOptions.type = 'POST';
 119              return this.ajax( parameters, ajaxOptions );
 120          },
 121  
 122          /**
 123           * Perform the API call.
 124           *
 125           * @param {Object} parameters
 126           * @param {Object} [ajaxOptions]
 127           * @return {jQuery.Promise} Done: API response data and the jqXHR object.
 128           *  Fail: Error code
 129           */
 130          ajax: function ( parameters, ajaxOptions ) {
 131              var token,
 132                  apiDeferred = $.Deferred(),
 133                  msg = 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.',
 134                  xhr, key, formData;
 135  
 136              parameters = $.extend( {}, this.defaults.parameters, parameters );
 137              ajaxOptions = $.extend( {}, this.defaults.ajax, ajaxOptions );
 138  
 139              // Ensure that token parameter is last (per [[mw:API:Edit#Token]]).
 140              if ( parameters.token ) {
 141                  token = parameters.token;
 142                  delete parameters.token;
 143              }
 144  
 145              // If multipart/form-data has been requested and emulation is possible, emulate it
 146              if (
 147                  ajaxOptions.type === 'POST' &&
 148                  window.FormData &&
 149                  ajaxOptions.contentType === 'multipart/form-data'
 150              ) {
 151  
 152                  formData = new FormData();
 153  
 154                  for ( key in parameters ) {
 155                      formData.append( key, parameters[key] );
 156                  }
 157                  // If we extracted a token parameter, add it back in.
 158                  if ( token ) {
 159                      formData.append( 'token', token );
 160                  }
 161  
 162                  ajaxOptions.data = formData;
 163  
 164                  // Prevent jQuery from mangling our FormData object
 165                  ajaxOptions.processData = false;
 166                  // Prevent jQuery from overriding the Content-Type header
 167                  ajaxOptions.contentType = false;
 168              } else {
 169                  // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug
 170                  // So let's escape them here. See bug #28235
 171                  // This works because jQuery accepts data as a query string or as an Object
 172                  ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' );
 173  
 174                  // If we extracted a token parameter, add it back in.
 175                  if ( token ) {
 176                      ajaxOptions.data += '&token=' + encodeURIComponent( token );
 177                  }
 178  
 179                  if ( ajaxOptions.contentType === 'multipart/form-data' ) {
 180                      // We were asked to emulate but can't, so drop the Content-Type header, otherwise
 181                      // it'll be wrong and the server will fail to decode the POST body
 182                      delete ajaxOptions.contentType;
 183                  }
 184              }
 185  
 186              // Backwards compatibility: Before MediaWiki 1.20,
 187              // callbacks were done with the 'ok' and 'err' property in ajaxOptions.
 188              if ( ajaxOptions.ok ) {
 189                  mw.track( 'mw.deprecate', 'api.cbParam' );
 190                  mw.log.warn( msg );
 191                  apiDeferred.done( ajaxOptions.ok );
 192                  delete ajaxOptions.ok;
 193              }
 194              if ( ajaxOptions.err ) {
 195                  mw.track( 'mw.deprecate', 'api.cbParam' );
 196                  mw.log.warn( msg );
 197                  apiDeferred.fail( ajaxOptions.err );
 198                  delete ajaxOptions.err;
 199              }
 200  
 201              // Make the AJAX request
 202              xhr = $.ajax( ajaxOptions )
 203                  // If AJAX fails, reject API call with error code 'http'
 204                  // and details in second argument.
 205                  .fail( function ( xhr, textStatus, exception ) {
 206                      apiDeferred.reject( 'http', {
 207                          xhr: xhr,
 208                          textStatus: textStatus,
 209                          exception: exception
 210                      } );
 211                  } )
 212                  // AJAX success just means "200 OK" response, also check API error codes
 213                  .done( function ( result, textStatus, jqXHR ) {
 214                      if ( result === undefined || result === null || result === '' ) {
 215                          apiDeferred.reject( 'ok-but-empty',
 216                              'OK response but empty result (check HTTP headers?)'
 217                          );
 218                      } else if ( result.error ) {
 219                          var code = result.error.code === undefined ? 'unknown' : result.error.code;
 220                          apiDeferred.reject( code, result );
 221                      } else {
 222                          apiDeferred.resolve( result, jqXHR );
 223                      }
 224                  } );
 225  
 226              // Return the Promise
 227              return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) {
 228                  if ( !( code === 'http' && details && details.textStatus === 'abort' ) ) {
 229                      mw.log( 'mw.Api error: ', code, details );
 230                  }
 231              } );
 232          },
 233  
 234          /**
 235           * Post to API with specified type of token. If we have no token, get one and try to post.
 236           * If we have a cached token try using that, and if it fails, blank out the
 237           * cached token and start over. For example to change an user option you could do:
 238           *
 239           *     new mw.Api().postWithToken( 'options', {
 240           *         action: 'options',
 241           *         optionname: 'gender',
 242           *         optionvalue: 'female'
 243           *     } );
 244           *
 245           * @param {string} tokenType The name of the token, like options or edit.
 246           * @param {Object} params API parameters
 247           * @param {Object} [ajaxOptions]
 248           * @return {jQuery.Promise} See #post
 249           * @since 1.22
 250           */
 251          postWithToken: function ( tokenType, params, ajaxOptions ) {
 252              var api = this;
 253  
 254              // Do not allow deprecated ok-callback
 255              // FIXME: Remove this check when the deprecated ok-callback is removed in #post
 256              if ( $.isFunction( ajaxOptions ) ) {
 257                  ajaxOptions = undefined;
 258              }
 259  
 260              return api.getToken( tokenType ).then( function ( token ) {
 261                  params.token = token;
 262                  return api.post( params, ajaxOptions ).then(
 263                      // If no error, return to caller as-is
 264                      null,
 265                      // Error handler
 266                      function ( code ) {
 267                          if ( code === 'badtoken' ) {
 268                              // Clear from cache
 269                              promises[ api.defaults.ajax.url ][ tokenType + 'Token' ] =
 270                                  params.token = undefined;
 271  
 272                              // Try again, once
 273                              return api.getToken( tokenType ).then( function ( token ) {
 274                                  params.token = token;
 275                                  return api.post( params, ajaxOptions );
 276                              } );
 277                          }
 278  
 279                          // Different error, pass on to let caller handle the error code
 280                          return this;
 281                      }
 282                  );
 283              } );
 284          },
 285  
 286          /**
 287           * Get a token for a certain action from the API.
 288           *
 289           * @param {string} type Token type
 290           * @return {jQuery.Promise}
 291           * @return {Function} return.done
 292           * @return {string} return.done.token Received token.
 293           * @since 1.22
 294           */
 295          getToken: function ( type ) {
 296              var apiPromise,
 297                  promiseGroup = promises[ this.defaults.ajax.url ],
 298                  d = promiseGroup && promiseGroup[ type + 'Token' ];
 299  
 300              if ( !d ) {
 301                  apiPromise = this.get( { action: 'tokens', type: type } );
 302  
 303                  d = apiPromise
 304                      .then( function ( data ) {
 305                          // If token type is not available for this user,
 306                          // key '...token' is either missing or set to boolean false
 307                          if ( data.tokens && data.tokens[type + 'token'] ) {
 308                              return data.tokens[type + 'token'];
 309                          }
 310  
 311                          return $.Deferred().reject( 'token-missing', data );
 312                      }, function () {
 313                          // Clear promise. Do not cache errors.
 314                          delete promiseGroup[ type + 'Token' ];
 315  
 316                          // Pass on to allow the caller to handle the error
 317                          return this;
 318                      } )
 319                      // Attach abort handler
 320                      .promise( { abort: apiPromise.abort } );
 321  
 322                  // Store deferred now so that we can use it again even if it isn't ready yet
 323                  if ( !promiseGroup ) {
 324                      promiseGroup = promises[ this.defaults.ajax.url ] = {};
 325                  }
 326                  promiseGroup[ type + 'Token' ] = d;
 327              }
 328  
 329              return d;
 330          }
 331      };
 332  
 333      /**
 334       * @static
 335       * @property {Array}
 336       * List of errors we might receive from the API.
 337       * For now, this just documents our expectation that there should be similar messages
 338       * available.
 339       */
 340      mw.Api.errors = [
 341          // occurs when POST aborted
 342          // jQuery 1.4 can't distinguish abort or lost connection from 200 OK + empty result
 343          'ok-but-empty',
 344  
 345          // timeout
 346          'timeout',
 347  
 348          // really a warning, but we treat it like an error
 349          'duplicate',
 350          'duplicate-archive',
 351  
 352          // upload succeeded, but no image info.
 353          // this is probably impossible, but might as well check for it
 354          'noimageinfo',
 355          // remote errors, defined in API
 356          'uploaddisabled',
 357          'nomodule',
 358          'mustbeposted',
 359          'badaccess-groups',
 360          'stashfailed',
 361          'missingresult',
 362          'missingparam',
 363          'invalid-file-key',
 364          'copyuploaddisabled',
 365          'mustbeloggedin',
 366          'empty-file',
 367          'file-too-large',
 368          'filetype-missing',
 369          'filetype-banned',
 370          'filetype-banned-type',
 371          'filename-tooshort',
 372          'illegal-filename',
 373          'verification-error',
 374          'hookaborted',
 375          'unknown-error',
 376          'internal-error',
 377          'overwrite',
 378          'badtoken',
 379          'fetchfileerror',
 380          'fileexists-shared-forbidden',
 381          'invalidtitle',
 382          'notloggedin'
 383      ];
 384  
 385      /**
 386       * @static
 387       * @property {Array}
 388       * List of warnings we might receive from the API.
 389       * For now, this just documents our expectation that there should be similar messages
 390       * available.
 391       */
 392      mw.Api.warnings = [
 393          'duplicate',
 394          'exists'
 395      ];
 396  
 397  }( mediaWiki, jQuery ) );


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1