Merge "Fix grammar in UserPasswordPolicy documentation"
[lhc/web/wiklou.git] / resources / src / mediawiki.legacy / protect.js
1 ( function () {
2 var ProtectionForm,
3 config = require( './config.json' ),
4 reasonCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
5 reasonByteLimit = mw.config.get( 'wgCommentByteLimit' );
6
7 ProtectionForm = window.ProtectionForm = {
8 /**
9 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
10 * on the protection form
11 *
12 * @return {boolean}
13 */
14 init: function () {
15 var $cell = $( '<td>' ),
16 $row = $( '<tr>' ).append( $cell );
17
18 if ( !$( '#mwProtectSet' ).length ) {
19 return false;
20 }
21
22 $( 'form#mw-Protect-Form' ).on( 'submit', this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
23 this.getExpirySelectors().each( function () {
24 $( this ).on( 'change', ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
25 } );
26 this.getExpiryInputs().each( function () {
27 $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
28 } );
29 this.getLevelSelectors().each( function () {
30 $( this ).on( 'change', ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
31 } );
32
33 $( '#mwProtectSet > tbody > tr:first' ).after( $row );
34
35 // If there is only one protection type, there is nothing to chain
36 if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
37 $cell.append(
38 $( '<input>' )
39 .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
40 .on( 'click', this.onChainClick.bind( this ) )
41 .prop( 'checked', !this.areAllTypesMatching() ),
42 document.createTextNode( ' ' ),
43 $( '<label>' )
44 .attr( 'for', 'mwProtectUnchained' )
45 .text( mw.msg( 'protect-unchain-permissions' ) )
46 );
47
48 this.toggleUnchainedInputs( !this.areAllTypesMatching() );
49 }
50
51 // Arbitrary 75 to leave some space for the autogenerated null edit's summary
52 if ( reasonCodePointLimit ) {
53 $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit - 75 );
54 } else if ( reasonByteLimit ) {
55 $( '#mwProtect-reason' ).byteLimit( reasonByteLimit - 75 );
56 }
57
58 this.updateCascadeCheckbox();
59 return true;
60 },
61
62 /**
63 * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
64 */
65 updateCascadeCheckbox: function () {
66 this.getLevelSelectors().each( function () {
67 if ( !ProtectionForm.isCascadeableLevel( $( this ).val() ) ) {
68 $( '#mwProtect-cascade' ).prop( { checked: false, disabled: true } );
69 return false;
70 } else {
71 $( '#mwProtect-cascade' ).prop( 'disabled', false );
72 }
73 } );
74 },
75
76 /**
77 * Checks if a certain protection level is cascadeable.
78 *
79 * @param {string} level
80 * @return {boolean}
81 */
82 isCascadeableLevel: function ( level ) {
83 return config.CascadingRestrictionLevels.indexOf( level ) !== -1;
84 },
85
86 /**
87 * When protection levels are locked together, update the rest
88 * when one action's level changes
89 *
90 * @param {Element} source Level selector that changed
91 */
92 updateLevels: function ( source ) {
93 if ( !this.isUnchained() ) {
94 this.setAllSelectors( source.selectedIndex );
95 }
96 this.updateCascadeCheckbox();
97 },
98
99 /**
100 * When protection levels are locked together, update the
101 * expiries when one changes
102 *
103 * @param {Element} source expiry input that changed
104 */
105
106 updateExpiry: function ( source ) {
107 if ( !this.isUnchained() ) {
108 this.getExpiryInputs().each( function () {
109 this.value = source.value;
110 } );
111 }
112 if ( this.isUnchained() ) {
113 $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
114 } else {
115 this.getExpirySelectors().each( function () {
116 this.value = 'othertime';
117 } );
118 }
119 },
120
121 /**
122 * When protection levels are locked together, update the
123 * expiry lists when one changes and clear the custom inputs
124 *
125 * @param {Element} source Expiry selector that changed
126 */
127 updateExpiryList: function ( source ) {
128 if ( !this.isUnchained() ) {
129 this.getExpirySelectors().each( function () {
130 this.value = source.value;
131 } );
132 this.getExpiryInputs().each( function () {
133 this.value = '';
134 } );
135 }
136 },
137
138 /**
139 * Update chain status and enable/disable various bits of the UI
140 * when the user changes the "unlock move permissions" checkbox
141 */
142 onChainClick: function () {
143 this.toggleUnchainedInputs( this.isUnchained() );
144 if ( !this.isUnchained() ) {
145 this.setAllSelectors( this.getMaxLevel() );
146 }
147 this.updateCascadeCheckbox();
148 },
149
150 /**
151 * Returns true if the named attribute in all objects in the given array are matching
152 *
153 * @param {Object[]} objects
154 * @param {string} attrName
155 * @return {boolean}
156 */
157 matchAttribute: function ( objects, attrName ) {
158 // eslint-disable-next-line no-jquery/no-map-util
159 return $.map( objects, function ( object ) {
160 return object[ attrName ];
161 } ).filter( function ( item, index, a ) {
162 return index === a.indexOf( item );
163 } ).length === 1;
164 },
165
166 /**
167 * Are all actions protected at the same level, with the same expiry time?
168 *
169 * @return {boolean}
170 */
171 areAllTypesMatching: function () {
172 return this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' ) &&
173 this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' ) &&
174 this.matchAttribute( this.getExpiryInputs(), 'value' );
175 },
176
177 /**
178 * Is protection chaining off?
179 *
180 * @return {boolean}
181 */
182 isUnchained: function () {
183 var element = document.getElementById( 'mwProtectUnchained' );
184 return element ?
185 element.checked :
186 true; // No control, so we need to let the user set both levels
187 },
188
189 /**
190 * Find the highest protection level in any selector
191 *
192 * @return {number}
193 */
194 getMaxLevel: function () {
195 return Math.max.apply( Math, this.getLevelSelectors().map( function () {
196 return this.selectedIndex;
197 } ) );
198 },
199
200 /**
201 * Protect all actions at the specified level
202 *
203 * @param {number} index Protection level
204 */
205 setAllSelectors: function ( index ) {
206 this.getLevelSelectors().each( function () {
207 this.selectedIndex = index;
208 } );
209 },
210
211 /**
212 * Get a list of all protection selectors on the page
213 *
214 * @return {jQuery}
215 */
216 getLevelSelectors: function () {
217 return $( 'select[id ^= mwProtect-level-]' );
218 },
219
220 /**
221 * Get a list of all expiry inputs on the page
222 *
223 * @return {jQuery}
224 */
225 getExpiryInputs: function () {
226 return $( 'input[id ^= mwProtect-][id $= -expires]' );
227 },
228
229 /**
230 * Get a list of all expiry selector lists on the page
231 *
232 * @return {jQuery}
233 */
234 getExpirySelectors: function () {
235 return $( 'select[id ^= mwProtectExpirySelection-]' );
236 },
237
238 /**
239 * Enable/disable protection selectors and expiry inputs
240 *
241 * @param {boolean} val Enable?
242 */
243 toggleUnchainedInputs: function ( val ) {
244 var setDisabled = function () {
245 this.disabled = !val;
246 };
247 this.getLevelSelectors().slice( 1 ).each( setDisabled );
248 this.getExpiryInputs().slice( 1 ).each( setDisabled );
249 this.getExpirySelectors().slice( 1 ).each( setDisabled );
250 }
251 };
252
253 $( ProtectionForm.init.bind( ProtectionForm ) );
254
255 }() );