Merge "Revert "Log the reason why revision->getContent() returns null""
[lhc/web/wiklou.git] / resources / src / mediawiki.legacy / protect.js
1 ( function ( mw, $ ) {
2 var ProtectionForm,
3 reasonCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
4 reasonByteLimit = mw.config.get( 'wgCommentByteLimit' );
5
6 ProtectionForm = window.ProtectionForm = {
7 /**
8 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
9 * on the protection form
10 *
11 * @return {boolean}
12 */
13 init: function () {
14 var $cell = $( '<td>' ),
15 $row = $( '<tr>' ).append( $cell );
16
17 if ( !$( '#mwProtectSet' ).length ) {
18 return false;
19 }
20
21 if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) {
22 $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
23 }
24 this.getExpirySelectors().each( function () {
25 $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
26 } );
27 this.getExpiryInputs().each( function () {
28 $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
29 } );
30 this.getLevelSelectors().each( function () {
31 $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
32 } );
33
34 $( '#mwProtectSet > tbody > tr:first' ).after( $row );
35
36 // If there is only one protection type, there is nothing to chain
37 if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
38 $cell.append(
39 $( '<input>' )
40 .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
41 .click( this.onChainClick.bind( this ) )
42 .prop( 'checked', !this.areAllTypesMatching() ),
43 document.createTextNode( ' ' ),
44 $( '<label>' )
45 .attr( 'for', 'mwProtectUnchained' )
46 .text( mw.msg( 'protect-unchain-permissions' ) )
47 );
48
49 this.toggleUnchainedInputs( !this.areAllTypesMatching() );
50 }
51
52 // Arbitrary 75 to leave some space for the autogenerated null edit's summary
53 if ( reasonCodePointLimit ) {
54 $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit - 75 );
55 } else if ( reasonByteLimit ) {
56 $( '#mwProtect-reason' ).byteLimit( reasonByteLimit - 75 );
57 }
58
59 this.updateCascadeCheckbox();
60 return true;
61 },
62
63 /**
64 * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
65 */
66 updateCascadeCheckbox: function () {
67 this.getLevelSelectors().each( function () {
68 if ( !ProtectionForm.isCascadeableLevel( $( this ).val() ) ) {
69 $( '#mwProtect-cascade' ).prop( { checked: false, disabled: true } );
70 return false;
71 } else {
72 $( '#mwProtect-cascade' ).prop( 'disabled', false );
73 }
74 } );
75 },
76
77 /**
78 * Checks if a certain protection level is cascadeable.
79 *
80 * @param {string} level
81 * @return {boolean}
82 */
83 isCascadeableLevel: function ( level ) {
84 return $.inArray( level, mw.config.get( 'wgCascadeableLevels' ) ) !== -1;
85 },
86
87 /**
88 * When protection levels are locked together, update the rest
89 * when one action's level changes
90 *
91 * @param {Element} source Level selector that changed
92 */
93 updateLevels: function ( source ) {
94 if ( !this.isUnchained() ) {
95 this.setAllSelectors( source.selectedIndex );
96 }
97 this.updateCascadeCheckbox();
98 },
99
100 /**
101 * When protection levels are locked together, update the
102 * expiries when one changes
103 *
104 * @param {Element} source expiry input that changed
105 */
106
107 updateExpiry: function ( source ) {
108 if ( !this.isUnchained() ) {
109 this.getExpiryInputs().each( function () {
110 this.value = source.value;
111 } );
112 }
113 if ( this.isUnchained() ) {
114 $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
115 } else {
116 this.getExpirySelectors().each( function () {
117 this.value = 'othertime';
118 } );
119 }
120 },
121
122 /**
123 * When protection levels are locked together, update the
124 * expiry lists when one changes and clear the custom inputs
125 *
126 * @param {Element} source Expiry selector that changed
127 */
128 updateExpiryList: function ( source ) {
129 if ( !this.isUnchained() ) {
130 this.getExpirySelectors().each( function () {
131 this.value = source.value;
132 } );
133 this.getExpiryInputs().each( function () {
134 this.value = '';
135 } );
136 }
137 },
138
139 /**
140 * Update chain status and enable/disable various bits of the UI
141 * when the user changes the "unlock move permissions" checkbox
142 */
143 onChainClick: function () {
144 this.toggleUnchainedInputs( this.isUnchained() );
145 if ( !this.isUnchained() ) {
146 this.setAllSelectors( this.getMaxLevel() );
147 }
148 this.updateCascadeCheckbox();
149 },
150
151 /**
152 * Returns true if the named attribute in all objects in the given array are matching
153 *
154 * @param {Object[]} objects
155 * @param {string} attrName
156 * @return {boolean}
157 */
158 matchAttribute: function ( objects, attrName ) {
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 () { this.disabled = !val; };
245 this.getLevelSelectors().slice( 1 ).each( setDisabled );
246 this.getExpiryInputs().slice( 1 ).each( setDisabled );
247 this.getExpirySelectors().slice( 1 ).each( setDisabled );
248 }
249 };
250
251 $( ProtectionForm.init.bind( ProtectionForm ) );
252
253 }( mediaWiki, jQuery ) );