Merge "Fix order of @var parameter in PHP"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / UriProcessor.js
1 /* eslint no-underscore-dangle: "off" */
2 /**
3 * URI Processor for RCFilters
4 *
5 * @class mw.rcfilters.UriProcessor
6 *
7 * @constructor
8 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
9 * @param {Object} [config] Configuration object
10 * @cfg {boolean} [normalizeTarget] Dictates whether or not to go through the
11 * title normalization to separate title subpage/parts into the target= url
12 * parameter
13 */
14 var UriProcessor = function MwRcfiltersController( filtersModel, config ) {
15 config = config || {};
16 this.filtersModel = filtersModel;
17
18 this.normalizeTarget = !!config.normalizeTarget;
19 };
20
21 /* Initialization */
22 OO.initClass( UriProcessor );
23
24 /* Static methods */
25
26 /**
27 * Replace the url history through replaceState
28 *
29 * @param {mw.Uri} newUri New URI to replace
30 */
31 UriProcessor.static.replaceState = function ( newUri ) {
32 window.history.replaceState(
33 { tag: 'rcfilters' },
34 document.title,
35 newUri.toString()
36 );
37 };
38
39 /**
40 * Push the url to history through pushState
41 *
42 * @param {mw.Uri} newUri New URI to push
43 */
44 UriProcessor.static.pushState = function ( newUri ) {
45 window.history.pushState(
46 { tag: 'rcfilters' },
47 document.title,
48 newUri.toString()
49 );
50 };
51
52 /* Methods */
53
54 /**
55 * Get the version that this URL query is tagged with.
56 *
57 * @param {Object} [uriQuery] URI query
58 * @return {number} URL version
59 */
60 UriProcessor.prototype.getVersion = function ( uriQuery ) {
61 uriQuery = uriQuery || new mw.Uri().query;
62
63 return Number( uriQuery.urlversion || 1 );
64 };
65
66 /**
67 * Get an updated mw.Uri object based on the model state
68 *
69 * @param {mw.Uri} [uri] An external URI to build the new uri
70 * with. This is mainly for tests, to be able to supply external query
71 * parameters and make sure they are retained.
72 * @return {mw.Uri} Updated Uri
73 */
74 UriProcessor.prototype.getUpdatedUri = function ( uri ) {
75 var normalizedUri = this._normalizeTargetInUri( uri || new mw.Uri() ),
76 unrecognizedParams = this.getUnrecognizedParams( normalizedUri.query );
77
78 normalizedUri.query = this.filtersModel.getMinimizedParamRepresentation(
79 $.extend(
80 true,
81 {},
82 normalizedUri.query,
83 // The representation must be expanded so it can
84 // override the uri query params but we then output
85 // a minimized version for the entire URI representation
86 // for the method
87 this.filtersModel.getExpandedParamRepresentation()
88 )
89 );
90
91 // Reapply unrecognized params and url version
92 normalizedUri.query = $.extend(
93 true,
94 {},
95 normalizedUri.query,
96 unrecognizedParams,
97 { urlversion: '2' }
98 );
99
100 return normalizedUri;
101 };
102
103 /**
104 * Move the subpage to the target parameter
105 *
106 * @param {mw.Uri} uri
107 * @return {mw.Uri}
108 * @private
109 */
110 UriProcessor.prototype._normalizeTargetInUri = function ( uri ) {
111 var parts,
112 // matches [/wiki/]SpecialNS:RCL/[Namespace:]Title/Subpage/Subsubpage/etc
113 re = /^((?:\/.+?\/)?.*?:.*?)\/(.*)$/;
114
115 if ( !this.normalizeTarget ) {
116 return uri;
117 }
118
119 // target in title param
120 if ( uri.query.title ) {
121 parts = uri.query.title.match( re );
122 if ( parts ) {
123 uri.query.title = parts[ 1 ];
124 uri.query.target = parts[ 2 ];
125 }
126 }
127
128 // target in path
129 parts = mw.Uri.decode( uri.path ).match( re );
130 if ( parts ) {
131 uri.path = parts[ 1 ];
132 uri.query.target = parts[ 2 ];
133 }
134
135 return uri;
136 };
137
138 /**
139 * Get an object representing given parameters that are unrecognized by the model
140 *
141 * @param {Object} params Full params object
142 * @return {Object} Unrecognized params
143 */
144 UriProcessor.prototype.getUnrecognizedParams = function ( params ) {
145 // Start with full representation
146 var givenParamNames = Object.keys( params ),
147 unrecognizedParams = $.extend( true, {}, params );
148
149 // Extract unrecognized parameters
150 Object.keys( this.filtersModel.getEmptyParameterState() ).forEach( function ( paramName ) {
151 // Remove recognized params
152 if ( givenParamNames.indexOf( paramName ) > -1 ) {
153 delete unrecognizedParams[ paramName ];
154 }
155 } );
156
157 return unrecognizedParams;
158 };
159
160 /**
161 * Update the URL of the page to reflect current filters
162 *
163 * This should not be called directly from outside the controller.
164 * If an action requires changing the URL, it should either use the
165 * highlighting actions below, or call #updateChangesList which does
166 * the uri corrections already.
167 *
168 * @param {Object} [params] Extra parameters to add to the API call
169 */
170 UriProcessor.prototype.updateURL = function ( params ) {
171 var currentUri = new mw.Uri(),
172 updatedUri = this.getUpdatedUri();
173
174 updatedUri.extend( params || {} );
175
176 if (
177 this.getVersion( currentUri.query ) !== 2 ||
178 this.isNewState( currentUri.query, updatedUri.query )
179 ) {
180 this.constructor.static.replaceState( updatedUri );
181 }
182 };
183
184 /**
185 * Update the filters model based on the URI query
186 * This happens on initialization, and from this moment on,
187 * we consider the system synchronized, and the model serves
188 * as the source of truth for the URL.
189 *
190 * This methods should only be called once on initialization.
191 * After initialization, the model updates the URL, not the
192 * other way around.
193 *
194 * @param {Object} [uriQuery] URI query
195 */
196 UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
197 uriQuery = uriQuery || this._normalizeTargetInUri( new mw.Uri() ).query;
198 this.filtersModel.updateStateFromParams(
199 this._getNormalizedQueryParams( uriQuery )
200 );
201 };
202
203 /**
204 * Compare two URI queries to decide whether they are different
205 * enough to represent a new state.
206 *
207 * @param {Object} currentUriQuery Current Uri query
208 * @param {Object} updatedUriQuery Updated Uri query
209 * @return {boolean} This is a new state
210 */
211 UriProcessor.prototype.isNewState = function ( currentUriQuery, updatedUriQuery ) {
212 var currentParamState, updatedParamState,
213 notEquivalent = function ( obj1, obj2 ) {
214 var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
215 return keys.some( function ( key ) {
216 return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
217 } );
218 };
219
220 // Compare states instead of parameters
221 // This will allow us to always have a proper check of whether
222 // the requested new url is one to change or not, regardless of
223 // actual parameter visibility/representation in the URL
224 currentParamState = $.extend(
225 true,
226 {},
227 this.filtersModel.getMinimizedParamRepresentation( currentUriQuery ),
228 this.getUnrecognizedParams( currentUriQuery )
229 );
230 updatedParamState = $.extend(
231 true,
232 {},
233 this.filtersModel.getMinimizedParamRepresentation( updatedUriQuery ),
234 this.getUnrecognizedParams( updatedUriQuery )
235 );
236
237 return notEquivalent( currentParamState, updatedParamState );
238 };
239
240 /**
241 * Check whether the given query has parameters that are
242 * recognized as parameters we should load the system with
243 *
244 * @param {mw.Uri} [uriQuery] Given URI query
245 * @return {boolean} Query contains valid recognized parameters
246 */
247 UriProcessor.prototype.doesQueryContainRecognizedParams = function ( uriQuery ) {
248 var anyValidInUrl,
249 validParameterNames = Object.keys( this.filtersModel.getEmptyParameterState() );
250
251 uriQuery = uriQuery || new mw.Uri().query;
252
253 anyValidInUrl = Object.keys( uriQuery ).some( function ( parameter ) {
254 return validParameterNames.indexOf( parameter ) > -1;
255 } );
256
257 // URL version 2 is allowed to be empty or within nonrecognized params
258 return anyValidInUrl || this.getVersion( uriQuery ) === 2;
259 };
260
261 /**
262 * Get the adjusted URI params based on the url version
263 * If the urlversion is not 2, the parameters are merged with
264 * the model's defaults.
265 * Always merge in the hidden parameter defaults.
266 *
267 * @private
268 * @param {Object} uriQuery Current URI query
269 * @return {Object} Normalized parameters
270 */
271 UriProcessor.prototype._getNormalizedQueryParams = function ( uriQuery ) {
272 // Check whether we are dealing with urlversion=2
273 // If we are, we do not merge the initial request with
274 // defaults. Not having urlversion=2 means we need to
275 // reproduce the server-side request and merge the
276 // requested parameters (or starting state) with the
277 // wiki default.
278 // Any subsequent change of the URL through the RCFilters
279 // system will receive 'urlversion=2'
280 var base = this.getVersion( uriQuery ) === 2 ?
281 {} :
282 this.filtersModel.getDefaultParams();
283
284 return $.extend(
285 true,
286 {},
287 this.filtersModel.getMinimizedParamRepresentation(
288 $.extend( true, {}, base, uriQuery )
289 ),
290 { urlversion: '2' }
291 );
292 };
293
294 module.exports = UriProcessor;