Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / resources / src / mediawiki.widgets / MediaSearch / mw.widgets.MediaResourceProvider.js
blobd767109c2dd09a49bee1e697eed8ba505c4d5361
1 /*!
2  * MediaWiki Widgets - MediaResourceProvider class.
3  *
4  * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
5  * @license The MIT License (MIT); see LICENSE.txt
6  */
7 ( function ( $, mw ) {
9         /**
10          * MediaWiki media resource provider.
11          *
12          * @class
13          * @extends mw.widgets.APIResultsProvider
14          *
15          * @constructor
16          * @param {string} apiurl The API url
17          * @param {Object} [config] Configuration options
18          * @cfg {string} [scriptDirUrl] The url of the API script
19          */
20         mw.widgets.MediaResourceProvider = function MwWidgetsMediaResourceProvider( apiurl, config ) {
21                 config = config || {};
23                 // Parent constructor
24                 mw.widgets.MediaResourceProvider.super.call( this, apiurl, config );
26                 // Fetching configuration
27                 this.scriptDirUrl = config.scriptDirUrl;
28                 this.isLocal = config.local !== undefined;
30                 if ( this.isLocal ) {
31                         this.setAPIurl( mw.util.wikiScript( 'api' ) );
32                 } else {
33                         // If 'apiurl' is set, use that. Otherwise, build the url
34                         // from scriptDirUrl and /api.php suffix
35                         this.setAPIurl( this.getAPIurl() || ( this.scriptDirUrl + '/api.php' ) );
36                 }
38                 this.siteInfoPromise = null;
39                 this.thumbSizes = [];
40                 this.imageSizes = [];
41         };
43         /* Inheritance */
44         OO.inheritClass( mw.widgets.MediaResourceProvider, mw.widgets.APIResultsProvider );
46         /* Methods */
48         /**
49          * @inheritdoc
50          */
51         mw.widgets.MediaResourceProvider.prototype.getStaticParams = function () {
52                 return $.extend(
53                         {},
54                         // Parent method
55                         mw.widgets.MediaResourceProvider.super.prototype.getStaticParams.call( this ),
56                         {
57                                 action: 'query',
58                                 iiprop: 'dimensions|url|mediatype|extmetadata|timestamp|user',
59                                 iiextmetadatalanguage: this.getLang(),
60                                 prop: 'imageinfo'
61                         }
62                 );
63         };
65         /**
66          * Initialize the source and get the site info.
67          *
68          * Connect to the api url and retrieve the siteinfo parameters
69          * that are required for fetching results.
70          *
71          * @return {jQuery.Promise} Promise that resolves when the class
72          * properties are set.
73          */
74         mw.widgets.MediaResourceProvider.prototype.loadSiteInfo = function () {
75                 var provider = this;
77                 if ( !this.siteInfoPromise ) {
78                         this.siteInfoPromise = new mw.Api().get( {
79                                 action: 'query',
80                                 meta: 'siteinfo'
81                         } )
82                                 .then( function ( data ) {
83                                         provider.setImageSizes( data.query.general.imagelimits || [] );
84                                         provider.setThumbSizes( data.query.general.thumblimits || [] );
85                                         provider.setUserParams( {
86                                                 // Standard width per resource
87                                                 iiurlwidth: provider.getStandardWidth()
88                                         } );
89                                 } );
90                 }
91                 return this.siteInfoPromise;
92         };
94         /**
95          * Override parent method and get results from the source
96          *
97          * @param {number} [howMany] The number of items to pull from the API
98          * @return {jQuery.Promise} Promise that is resolved into an array
99          * of available results, or is rejected if no results are available.
100          */
101         mw.widgets.MediaResourceProvider.prototype.getResults = function ( howMany ) {
102                 var xhr,
103                         aborted = false,
104                         provider = this;
106                 return this.loadSiteInfo()
107                         .then( function () {
108                                 if ( aborted ) {
109                                         return $.Deferred().reject();
110                                 }
111                                 xhr = provider.fetchAPIresults( howMany );
112                                 return xhr;
113                         } )
114                         .then(
115                                 function ( results ) {
116                                         if ( !results || results.length === 0 ) {
117                                                 provider.toggleDepleted( true );
118                                                 return [];
119                                         }
120                                         return results;
121                                 },
122                                 // Process failed, return an empty promise
123                                 function () {
124                                         provider.toggleDepleted( true );
125                                         return $.Deferred().resolve( [] );
126                                 }
127                         )
128                         .promise( { abort: function () {
129                                 aborted = true;
130                                 if ( xhr ) {
131                                         xhr.abort();
132                                 }
133                         } } );
134         };
136         /**
137          * Get continuation API data
138          *
139          * @param {number} howMany The number of results to retrieve
140          * @return {Object} API request data
141          */
142         mw.widgets.MediaResourceProvider.prototype.getContinueData = function () {
143                 return {};
144         };
146         /**
147          * Set continuation data for the next page
148          *
149          * @param {Object} continueData Continuation data
150          */
151         mw.widgets.MediaResourceProvider.prototype.setContinue = function () {
152         };
154         /**
155          * Sort the results
156          *
157          * @param {Object[]} results API results
158          * @return {Object[]} Sorted results
159          */
160         mw.widgets.MediaResourceProvider.prototype.sort = function ( results ) {
161                 return results;
162         };
164         /**
165          * Call the API for search results.
166          *
167          * @param {number} howMany The number of results to retrieve
168          * @return {jQuery.Promise} Promise that resolves with an array of objects that contain
169          *  the fetched data.
170          */
171         mw.widgets.MediaResourceProvider.prototype.fetchAPIresults = function ( howMany ) {
172                 var xhr, api,
173                         provider = this;
175                 if ( !this.isValid() ) {
176                         return $.Deferred().reject().promise( { abort: $.noop } );
177                 }
179                 api = this.isLocal ? new mw.Api() : new mw.ForeignApi( this.getAPIurl(), { anonymous: true } );
180                 xhr = api.get( $.extend( {}, this.getStaticParams(), this.getUserParams(), this.getContinueData( howMany ) ) );
181                 return xhr
182                         .then( function ( data ) {
183                                 var page, newObj, raw,
184                                         results = [];
186                                 if ( data.error ) {
187                                         provider.toggleDepleted( true );
188                                         return [];
189                                 }
191                                 if ( data.continue ) {
192                                         // Update the offset for next time
193                                         provider.setContinue( data.continue );
194                                 } else {
195                                         // This is the last available set of results. Mark as depleted!
196                                         provider.toggleDepleted( true );
197                                 }
199                                 // If the source returned no results, it will not have a
200                                 // query property
201                                 if ( data.query ) {
202                                         raw = data.query.pages;
203                                         if ( raw ) {
204                                                 // Strip away the page ids
205                                                 for ( page in raw ) {
206                                                         if ( !raw[ page ].imageinfo ) {
207                                                                 // The search may give us pages that belong to the File:
208                                                                 // namespace but have no files in them, either because
209                                                                 // they were deleted or imported wrongly, or just started
210                                                                 // as pages. In that case, the response will not include
211                                                                 // imageinfo. Skip those files.
212                                                                 continue;
213                                                         }
214                                                         newObj = raw[ page ].imageinfo[ 0 ];
215                                                         newObj.title = raw[ page ].title;
216                                                         newObj.index = raw[ page ].index;
217                                                         results.push( newObj );
218                                                 }
219                                         }
220                                 }
221                                 return provider.sort( results );
222                         } )
223                         .promise( { abort: xhr.abort } );
224         };
226         /**
227          * Set name
228          *
229          * @param {string} name
230          */
231         mw.widgets.MediaResourceProvider.prototype.setName = function ( name ) {
232                 this.name = name;
233         };
235         /**
236          * Get name
237          *
238          * @return {string} name
239          */
240         mw.widgets.MediaResourceProvider.prototype.getName = function () {
241                 return this.name;
242         };
244         /**
245          * Get standard width, based on the provider source's thumb sizes.
246          *
247          * @return {number|undefined} fetchWidth
248          */
249         mw.widgets.MediaResourceProvider.prototype.getStandardWidth = function () {
250                 return ( this.thumbSizes && this.thumbSizes[ this.thumbSizes.length - 1 ] ) ||
251                         ( this.imageSizes && this.imageSizes[ 0 ] ) ||
252                         // Fall back on a number
253                         300;
254         };
256         /**
257          * Get prop
258          *
259          * @return {string} prop
260          */
261         mw.widgets.MediaResourceProvider.prototype.getFetchProp = function () {
262                 return this.fetchProp;
263         };
265         /**
266          * Set prop
267          *
268          * @param {string} prop
269          */
270         mw.widgets.MediaResourceProvider.prototype.setFetchProp = function ( prop ) {
271                 this.fetchProp = prop;
272         };
274         /**
275          * Set thumb sizes
276          *
277          * @param {number[]} sizes Available thumbnail sizes
278          */
279         mw.widgets.MediaResourceProvider.prototype.setThumbSizes = function ( sizes ) {
280                 this.thumbSizes = sizes;
281         };
283         /**
284          * Set image sizes
285          *
286          * @param {number[]} sizes Available image sizes
287          */
288         mw.widgets.MediaResourceProvider.prototype.setImageSizes = function ( sizes ) {
289                 this.imageSizes = sizes;
290         };
292         /**
293          * Get thumb sizes
294          *
295          * @return {number[]} sizes Available thumbnail sizes
296          */
297         mw.widgets.MediaResourceProvider.prototype.getThumbSizes = function () {
298                 return this.thumbSizes;
299         };
301         /**
302          * Get image sizes
303          *
304          * @return {number[]} sizes Available image sizes
305          */
306         mw.widgets.MediaResourceProvider.prototype.getImageSizes = function () {
307                 return this.imageSizes;
308         };
310         /**
311          * Check if this source is valid.
312          *
313          * @return {boolean} Source is valid
314          */
315         mw.widgets.MediaResourceProvider.prototype.isValid = function () {
316                 return this.isLocal ||
317                         // If we don't have either 'apiurl' or 'scriptDirUrl'
318                         // the source is invalid, and we will skip it
319                         this.apiurl !== undefined ||
320                         this.scriptDirUrl !== undefined;
321         };
322 }( jQuery, mediaWiki ) );