Move mediawiki.legacy.* modules from skins/common/ to resources/
[mediawiki.git] / resources / src / mediawiki.legacy / protect.js
blobdc142ca9672506530d318335570b33ee009877c5
1 ( function ( mw, $ ) {
3 var ProtectionForm = window.ProtectionForm = {
4         existingMatch: false,
6         /**
7          * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
8          * on the protection form
9          *
10          * @param opts Object : parameters with members:
11          *     tableId              Identifier of the table containing UI bits
12          *     labelText            Text to use for the checkbox label
13          *     numTypes             The number of protection types
14          *     existingMatch        True if all the existing expiry times match
15          */
16         init: function ( opts ) {
17                 var box, boxbody, row, cell, check, label;
19                 if ( !( document.createTextNode && document.getElementById && document.getElementsByTagName ) ) {
20                         return false;
21                 }
23                 box = document.getElementById( opts.tableId );
24                 if ( !box ) {
25                         return false;
26                 }
28                 boxbody = box.getElementsByTagName( 'tbody' )[0];
29                 row = document.createElement( 'tr' );
30                 boxbody.insertBefore( row, boxbody.firstChild.nextSibling );
32                 this.existingMatch = opts.existingMatch;
34                 cell = document.createElement( 'td' );
35                 row.appendChild( cell );
36                 // If there is only one protection type, there is nothing to chain
37                 if ( opts.numTypes > 1 ) {
38                         check = document.createElement( 'input' );
39                         check.id = 'mwProtectUnchained';
40                         check.type = 'checkbox';
41                         $( check ).click( function () {
42                                 ProtectionForm.onChainClick();
43                         } );
45                         label = document.createElement( 'label' );
46                         label.htmlFor = 'mwProtectUnchained';
47                         label.appendChild( document.createTextNode( opts.labelText ) );
49                         cell.appendChild( check );
50                         cell.appendChild( document.createTextNode( ' ' ) );
51                         cell.appendChild( label );
53                         check.checked = !this.areAllTypesMatching();
54                         this.enableUnchainedInputs( check.checked );
55                 }
57                 $( '#mwProtect-reason' ).byteLimit( 180 );
59                 this.updateCascadeCheckbox();
61                 return true;
62         },
64         /**
65          * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
66          */
67         updateCascadeCheckbox: function () {
68                 var i, lists, items, selected;
70                 // For non-existent titles, there is no cascade option
71                 if ( !document.getElementById( 'mwProtect-cascade' ) ) {
72                         return;
73                 }
74                 lists = this.getLevelSelectors();
75                 for ( i = 0; i < lists.length; i++ ) {
76                         if ( lists[i].selectedIndex > -1 ) {
77                                 items = lists[i].getElementsByTagName( 'option' );
78                                 selected = items[ lists[i].selectedIndex ].value;
79                                 if ( !this.isCascadeableLevel( selected ) ) {
80                                         document.getElementById( 'mwProtect-cascade' ).checked = false;
81                                         document.getElementById( 'mwProtect-cascade' ).disabled = true;
82                                         return;
83                                 }
84                         }
85                 }
86                 document.getElementById( 'mwProtect-cascade' ).disabled = false;
87         },
89         /**
90          * Checks if a cerain protection level is cascadeable.
91          * @param level {String}
92          * @return {Boolean}
93          */
94         isCascadeableLevel: function (  level ) {
95                 var cascadeLevels, len, i;
97                 cascadeLevels = mw.config.get( 'wgCascadeableLevels' );
98                 // cascadeLevels isn't defined on all pages
99                 if ( cascadeLevels ) {
100                         for ( i = 0, len = cascadeLevels.length; i < len; i += 1 ) {
101                                 if ( cascadeLevels[i] === level ) {
102                                         return true;
103                                 }
104                         }
105                 }
106                 return false;
107         },
109         /**
110          * When protection levels are locked together, update the rest
111          * when one action's level changes
112          *
113          * @param source Element Level selector that changed
114          */
115         updateLevels: function ( source ) {
116                 if ( !this.isUnchained() ) {
117                         this.setAllSelectors( source.selectedIndex );
118                 }
119                 this.updateCascadeCheckbox();
120         },
122         /**
123          * When protection levels are locked together, update the
124          * expiries when one changes
125          *
126          * @param source Element expiry input that changed
127          */
129         updateExpiry: function ( source ) {
130                 var expiry, listId, list;
132                 if ( !this.isUnchained() ) {
133                         expiry = source.value;
134                         this.forEachExpiryInput( function ( element ) {
135                                 element.value = expiry;
136                         } );
137                 }
138                 listId = source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' );
139                 list = document.getElementById( listId );
140                 if ( list && list.value !== 'othertime' ) {
141                         if ( this.isUnchained() ) {
142                                 list.value = 'othertime';
143                         } else {
144                                 this.forEachExpirySelector( function ( element ) {
145                                         element.value = 'othertime';
146                                 } );
147                         }
148                 }
149         },
151         /**
152          * When protection levels are locked together, update the
153          * expiry lists when one changes and clear the custom inputs
154          *
155          * @param source Element expiry selector that changed
156          */
157         updateExpiryList: function ( source ) {
158                 var expiry;
159                 if ( !this.isUnchained() ) {
160                         expiry = source.value;
161                         this.forEachExpirySelector( function ( element ) {
162                                 element.value = expiry;
163                         } );
164                         this.forEachExpiryInput( function ( element ) {
165                                 element.value = '';
166                         } );
167                 }
168         },
170         /**
171          * Update chain status and enable/disable various bits of the UI
172          * when the user changes the "unlock move permissions" checkbox
173          */
174         onChainClick: function () {
175                 if ( this.isUnchained() ) {
176                         this.enableUnchainedInputs( true );
177                 } else {
178                         this.setAllSelectors( this.getMaxLevel() );
179                         this.enableUnchainedInputs( false );
180                 }
181                 this.updateCascadeCheckbox();
182         },
184         /**
185          * Returns true if the named attribute in all objects in the given array are matching
186          */
187         matchAttribute: function ( objects, attrName ) {
188                 var i, element, value;
190                 // Check levels
191                 value = null;
192                 for ( i = 0; i < objects.length; i++ ) {
193                         element = objects[i];
194                         if ( value === null ) {
195                                 value = element[attrName];
196                         } else {
197                                 if ( value !== element[attrName] ) {
198                                         return false;
199                                 }
200                         }
201                 }
202                 return true;
203         },
205         /**
206          * Are all actions protected at the same level, with the same expiry time?
207          *
208          * @return boolean
209          */
210         areAllTypesMatching: function () {
211                 return this.existingMatch
212                         && this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' )
213                         && this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' )
214                         && this.matchAttribute( this.getExpiryInputs(), 'value' );
215         },
217         /**
218          * Is protection chaining off?
219          *
220          * @return bool
221          */
222         isUnchained: function () {
223                 var element = document.getElementById( 'mwProtectUnchained' );
224                 return element
225                         ? element.checked
226                         : true; // No control, so we need to let the user set both levels
227         },
229         /**
230          * Find the highest protection level in any selector
231          */
232         getMaxLevel: function () {
233                 var maxIndex = -1;
234                 this.forEachLevelSelector( function ( element ) {
235                         if ( element.selectedIndex > maxIndex ) {
236                                 maxIndex = element.selectedIndex;
237                         }
238                 } );
239                 return maxIndex;
240         },
242         /**
243          * Protect all actions at the specified level
244          *
245          * @param index int Protection level
246          */
247         setAllSelectors: function ( index ) {
248                 this.forEachLevelSelector( function ( element ) {
249                         if ( element.selectedIndex !== index ) {
250                                 element.selectedIndex = index;
251                         }
252                 } );
253         },
255         /**
256          * Apply a callback to each protection selector
257          *
258          * @param func callable Callback function
259          */
260         forEachLevelSelector: function ( func ) {
261                 var i, selectors;
263                 selectors = this.getLevelSelectors();
264                 for ( i = 0; i < selectors.length; i++ ) {
265                         func( selectors[i] );
266                 }
267         },
269         /**
270          * Get a list of all protection selectors on the page
271          *
272          * @return Array
273          */
274         getLevelSelectors: function () {
275                 var i, ours, all, element;
277                 all = document.getElementsByTagName( 'select' );
278                 ours = [];
279                 for ( i = 0; i < all.length; i++ ) {
280                         element = all[i];
281                         if ( element.id.match( /^mwProtect-level-/ ) ) {
282                                 ours[ours.length] = element;
283                         }
284                 }
285                 return ours;
286         },
288         /**
289          * Apply a callback to each expiry input
290          *
291          * @param func callable Callback function
292          */
293         forEachExpiryInput: function ( func ) {
294                 var i, inputs;
296                 inputs = this.getExpiryInputs();
297                 for ( i = 0; i < inputs.length; i++ ) {
298                         func( inputs[i] );
299                 }
300         },
302         /**
303          * Get a list of all expiry inputs on the page
304          *
305          * @return Array
306          */
307         getExpiryInputs: function () {
308                 var i, all, element, ours;
310                 all = document.getElementsByTagName( 'input' );
311                 ours = [];
312                 for ( i = 0; i < all.length; i++ ) {
313                         element = all[i];
314                         if ( element.name.match( /^mwProtect-expiry-/ ) ) {
315                                 ours[ours.length] = element;
316                         }
317                 }
318                 return ours;
319         },
321         /**
322          * Apply a callback to each expiry selector list
323          * @param func callable Callback function
324          */
325         forEachExpirySelector: function ( func ) {
326                 var i, inputs;
328                 inputs = this.getExpirySelectors();
329                 for ( i = 0; i < inputs.length; i++ ) {
330                         func( inputs[i] );
331                 }
332         },
334         /**
335          * Get a list of all expiry selector lists on the page
336          *
337          * @return Array
338          */
339         getExpirySelectors: function () {
340                 var i, all, ours, element;
342                 all = document.getElementsByTagName( 'select' );
343                 ours = [];
344                 for ( i = 0; i < all.length; i++ ) {
345                         element = all[i];
346                         if ( element.id.match( /^mwProtectExpirySelection-/ ) ) {
347                                 ours[ours.length] = element;
348                         }
349                 }
350                 return ours;
351         },
353         /**
354          * Enable/disable protection selectors and expiry inputs
355          *
356          * @param val boolean Enable?
357          */
358         enableUnchainedInputs: function ( val ) {
359                 var first = true;
361                 this.forEachLevelSelector( function ( element ) {
362                         if ( first ) {
363                                 first = false;
364                         } else {
365                                 element.disabled = !val;
366                         }
367                 } );
368                 first = true;
369                 this.forEachExpiryInput( function ( element ) {
370                         if ( first ) {
371                                 first = false;
372                         } else {
373                                 element.disabled = !val;
374                         }
375                 } );
376                 first = true;
377                 this.forEachExpirySelector( function ( element ) {
378                         if ( first ) {
379                                 first = false;
380                         } else {
381                                 element.disabled = !val;
382                         }
383                 } );
384         }
387 }( mediaWiki, jQuery ) );