Fix order of @var parameter in PHP
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / SavedQueriesModel.js
1 ( function () {
2 var SavedQueryItemModel = require( './SavedQueryItemModel.js' ),
3 SavedQueriesModel;
4
5 /**
6 * View model for saved queries
7 *
8 * @class mw.rcfilters.dm.SavedQueriesModel
9 * @mixins OO.EventEmitter
10 * @mixins OO.EmitterList
11 *
12 * @constructor
13 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters model
14 * @param {Object} [config] Configuration options
15 * @cfg {string} [default] Default query ID
16 */
17 SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( filtersModel, config ) {
18 config = config || {};
19
20 // Mixin constructor
21 OO.EventEmitter.call( this );
22 OO.EmitterList.call( this );
23
24 this.default = config.default;
25 this.filtersModel = filtersModel;
26 this.converted = false;
27
28 // Events
29 this.aggregate( { update: 'itemUpdate' } );
30 };
31
32 /* Initialization */
33
34 OO.initClass( SavedQueriesModel );
35 OO.mixinClass( SavedQueriesModel, OO.EventEmitter );
36 OO.mixinClass( SavedQueriesModel, OO.EmitterList );
37
38 /* Events */
39
40 /**
41 * @event initialize
42 *
43 * Model is initialized
44 */
45
46 /**
47 * @event itemUpdate
48 * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item
49 *
50 * An item has changed
51 */
52
53 /**
54 * @event default
55 * @param {string} New default ID
56 *
57 * The default has changed
58 */
59
60 /* Methods */
61
62 /**
63 * Initialize the saved queries model by reading it from the user's settings.
64 * The structure of the saved queries is:
65 * {
66 * version: (string) Version number; if version 2, the query represents
67 * parameters. Otherwise, the older version represented filters
68 * and needs to be readjusted,
69 * default: (string) Query ID
70 * queries:{
71 * query_id_1: {
72 * data:{
73 * filters: (Object) Minimal definition of the filters
74 * highlights: (Object) Definition of the highlights
75 * },
76 * label: (optional) Name of this query
77 * }
78 * }
79 * }
80 *
81 * @param {Object} [savedQueries] An object with the saved queries with
82 * the above structure.
83 * @fires initialize
84 */
85 SavedQueriesModel.prototype.initialize = function ( savedQueries ) {
86 var model = this;
87
88 savedQueries = savedQueries || {};
89
90 this.clearItems();
91 this.default = null;
92 this.converted = false;
93
94 if ( savedQueries.version !== '2' ) {
95 // Old version dealt with filter names. We need to migrate to the new structure
96 // The new structure:
97 // {
98 // version: (string) '2',
99 // default: (string) Query ID,
100 // queries: {
101 // query_id: {
102 // label: (string) Name of the query
103 // data: {
104 // params: (object) Representing all the parameter states
105 // highlights: (object) Representing all the filter highlight states
106 // }
107 // }
108 // }
109 // eslint-disable-next-line no-jquery/no-each-util
110 $.each( savedQueries.queries || {}, function ( id, obj ) {
111 if ( obj.data && obj.data.filters ) {
112 obj.data = model.convertToParameters( obj.data );
113 }
114 } );
115
116 this.converted = true;
117 savedQueries.version = '2';
118 }
119
120 // Initialize the query items
121 // eslint-disable-next-line no-jquery/no-each-util
122 $.each( savedQueries.queries || {}, function ( id, obj ) {
123 var normalizedData = obj.data,
124 isDefault = String( savedQueries.default ) === String( id );
125
126 if ( normalizedData && normalizedData.params ) {
127 // Backwards-compat fix: Remove sticky parameters from
128 // the given data, if they exist
129 normalizedData.params = model.filtersModel.removeStickyParams( normalizedData.params );
130
131 // Correct the invert state for effective selection
132 if ( normalizedData.params.invert && !normalizedData.params.namespace ) {
133 delete normalizedData.params.invert;
134 }
135
136 model.cleanupHighlights( normalizedData );
137
138 id = String( id );
139
140 // Skip the addNewQuery method because we don't want to unnecessarily manipulate
141 // the given saved queries unless we literally intend to (like in backwards compat fixes)
142 // And the addNewQuery method also uses a minimization routine that checks for the
143 // validity of items and minimizes the query. This isn't necessary for queries loaded
144 // from the backend, and has the risk of removing values if they're temporarily
145 // invalid (example: if we temporarily removed a cssClass from a filter in the backend)
146 model.addItems( [
147 new SavedQueryItemModel(
148 id,
149 obj.label,
150 normalizedData,
151 { default: isDefault }
152 )
153 ] );
154
155 if ( isDefault ) {
156 model.default = id;
157 }
158 }
159 } );
160
161 this.emit( 'initialize' );
162 };
163
164 /**
165 * Clean up highlight parameters.
166 * 'highlight' used to be stored, it's not inferred based on the presence of absence of
167 * filter colors.
168 *
169 * @param {Object} data Saved query data
170 */
171 SavedQueriesModel.prototype.cleanupHighlights = function ( data ) {
172 if (
173 data.params.highlight === '0' &&
174 data.highlights && Object.keys( data.highlights ).length
175 ) {
176 data.highlights = {};
177 }
178 delete data.params.highlight;
179 };
180
181 /**
182 * Convert from representation of filters to representation of parameters
183 *
184 * @param {Object} data Query data
185 * @return {Object} New converted query data
186 */
187 SavedQueriesModel.prototype.convertToParameters = function ( data ) {
188 var newData = {},
189 defaultFilters = this.filtersModel.getFiltersFromParameters( this.filtersModel.getDefaultParams() ),
190 fullFilterRepresentation = $.extend( true, {}, defaultFilters, data.filters ),
191 highlightEnabled = data.highlights.highlight;
192
193 delete data.highlights.highlight;
194
195 // Filters
196 newData.params = this.filtersModel.getMinimizedParamRepresentation(
197 this.filtersModel.getParametersFromFilters( fullFilterRepresentation )
198 );
199
200 // Highlights: appending _color to keys
201 newData.highlights = {};
202 // eslint-disable-next-line no-jquery/no-each-util
203 $.each( data.highlights, function ( highlightedFilterName, value ) {
204 if ( value ) {
205 newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
206 }
207 } );
208
209 // Add highlight
210 newData.params.highlight = String( Number( highlightEnabled || 0 ) );
211
212 return newData;
213 };
214
215 /**
216 * Add a query item
217 *
218 * @param {string} label Label for the new query
219 * @param {Object} fulldata Full data representation for the new query, combining highlights and filters
220 * @param {boolean} isDefault Item is default
221 * @param {string} [id] Query ID, if exists. If this isn't given, a random
222 * new ID will be created.
223 * @return {string} ID of the newly added query
224 */
225 SavedQueriesModel.prototype.addNewQuery = function ( label, fulldata, isDefault, id ) {
226 var normalizedData = { params: {}, highlights: {} },
227 highlightParamNames = Object.keys( this.filtersModel.getEmptyHighlightParameters() ),
228 randomID = String( id || ( new Date() ).getTime() ),
229 data = this.filtersModel.getMinimizedParamRepresentation( fulldata );
230
231 // Split highlight/params
232 // eslint-disable-next-line no-jquery/no-each-util
233 $.each( data, function ( param, value ) {
234 if ( param !== 'highlight' && highlightParamNames.indexOf( param ) > -1 ) {
235 normalizedData.highlights[ param ] = value;
236 } else {
237 normalizedData.params[ param ] = value;
238 }
239 } );
240
241 // Correct the invert state for effective selection
242 if ( normalizedData.params.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
243 delete normalizedData.params.invert;
244 }
245
246 // Add item
247 this.addItems( [
248 new SavedQueryItemModel(
249 randomID,
250 label,
251 normalizedData,
252 { default: isDefault }
253 )
254 ] );
255
256 if ( isDefault ) {
257 this.setDefault( randomID );
258 }
259
260 return randomID;
261 };
262
263 /**
264 * Remove query from model
265 *
266 * @param {string} queryID Query ID
267 */
268 SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
269 var query = this.getItemByID( queryID );
270
271 if ( query ) {
272 // Check if this item was the default
273 if ( String( this.getDefault() ) === String( queryID ) ) {
274 // Nulify the default
275 this.setDefault( null );
276 }
277
278 this.removeItems( [ query ] );
279 }
280 };
281
282 /**
283 * Get an item that matches the requested query
284 *
285 * @param {Object} fullQueryComparison Object representing all filters and highlights to compare
286 * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
287 */
288 SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
289 // Minimize before comparison
290 fullQueryComparison = this.filtersModel.getMinimizedParamRepresentation( fullQueryComparison );
291
292 // Correct the invert state for effective selection
293 if ( fullQueryComparison.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
294 delete fullQueryComparison.invert;
295 }
296
297 return this.getItems().filter( function ( item ) {
298 return OO.compare(
299 item.getCombinedData(),
300 fullQueryComparison
301 );
302 } )[ 0 ];
303 };
304
305 /**
306 * Get query by its identifier
307 *
308 * @param {string} queryID Query identifier
309 * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
310 * the search. Undefined if not found.
311 */
312 SavedQueriesModel.prototype.getItemByID = function ( queryID ) {
313 return this.getItems().filter( function ( item ) {
314 return item.getID() === queryID;
315 } )[ 0 ];
316 };
317
318 /**
319 * Get the full data representation of the default query, if it exists
320 *
321 * @return {Object|null} Representation of the default params if exists.
322 * Null if default doesn't exist or if the user is not logged in.
323 */
324 SavedQueriesModel.prototype.getDefaultParams = function () {
325 return ( !mw.user.isAnon() && this.getItemParams( this.getDefault() ) ) || {};
326 };
327
328 /**
329 * Get a full parameter representation of an item data
330 *
331 * @param {Object} queryID Query ID
332 * @return {Object} Parameter representation
333 */
334 SavedQueriesModel.prototype.getItemParams = function ( queryID ) {
335 var item = this.getItemByID( queryID ),
336 data = item ? item.getData() : {};
337
338 return !$.isEmptyObject( data ) ? this.buildParamsFromData( data ) : {};
339 };
340
341 /**
342 * Build a full parameter representation given item data and model sticky values state
343 *
344 * @param {Object} data Item data
345 * @return {Object} Full param representation
346 */
347 SavedQueriesModel.prototype.buildParamsFromData = function ( data ) {
348 data = data || {};
349 // Return parameter representation
350 return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {},
351 data.params,
352 data.highlights
353 ) );
354 };
355
356 /**
357 * Get the object representing the state of the entire model and items
358 *
359 * @return {Object} Object representing the state of the model and items
360 */
361 SavedQueriesModel.prototype.getState = function () {
362 var obj = { queries: {}, version: '2' };
363
364 // Translate the items to the saved object
365 this.getItems().forEach( function ( item ) {
366 obj.queries[ item.getID() ] = item.getState();
367 } );
368
369 if ( this.getDefault() ) {
370 obj.default = this.getDefault();
371 }
372
373 return obj;
374 };
375
376 /**
377 * Set a default query. Null to unset default.
378 *
379 * @param {string} itemID Query identifier
380 * @fires default
381 */
382 SavedQueriesModel.prototype.setDefault = function ( itemID ) {
383 if ( this.default !== itemID ) {
384 this.default = itemID;
385
386 // Set for individual itens
387 this.getItems().forEach( function ( item ) {
388 item.toggleDefault( item.getID() === itemID );
389 } );
390
391 this.emit( 'default', itemID );
392 }
393 };
394
395 /**
396 * Get the default query ID
397 *
398 * @return {string} Default query identifier
399 */
400 SavedQueriesModel.prototype.getDefault = function () {
401 return this.default;
402 };
403
404 /**
405 * Check if the saved queries were converted
406 *
407 * @return {boolean} Saved queries were converted from the previous
408 * version to the new version
409 */
410 SavedQueriesModel.prototype.isConverted = function () {
411 return this.converted;
412 };
413
414 module.exports = SavedQueriesModel;
415 }() );