Completed
Branch master (e2eefa)
by
unknown
25:58
created

ProtectionForm::__construct()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
nc 4
nop 1
dl 0
loc 24
rs 8.6845
c 1
b 0
f 0
1
<?php
2
/**
3
 * Page protection
4
 *
5
 * Copyright © 2005 Brion Vibber <[email protected]>
6
 * https://www.mediawiki.org/
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License along
19
 * with this program; if not, write to the Free Software Foundation, Inc.,
20
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
 * http://www.gnu.org/copyleft/gpl.html
22
 *
23
 * @file
24
 */
25
26
/**
27
 * Handles the page protection UI and backend
28
 */
29
class ProtectionForm {
30
	/** @var array A map of action to restriction level, from request or default */
31
	protected $mRestrictions = [];
32
33
	/** @var string The custom/additional protection reason */
34
	protected $mReason = '';
35
36
	/** @var string The reason selected from the list, blank for other/additional */
37
	protected $mReasonSelection = '';
38
39
	/** @var bool True if the restrictions are cascading, from request or existing protection */
40
	protected $mCascade = false;
41
42
	/** @var array Map of action to "other" expiry time. Used in preference to mExpirySelection. */
43
	protected $mExpiry = [];
44
45
	/**
46
	 * @var array Map of action to value selected in expiry drop-down list.
47
	 * Will be set to 'othertime' whenever mExpiry is set.
48
	 */
49
	protected $mExpirySelection = [];
50
51
	/** @var array Permissions errors for the protect action */
52
	protected $mPermErrors = [];
53
54
	/** @var array Types (i.e. actions) for which levels can be selected */
55
	protected $mApplicableTypes = [];
56
57
	/** @var array Map of action to the expiry time of the existing protection */
58
	protected $mExistingExpiry = [];
59
60
	/** @var IContextSource */
61
	private $mContext;
62
63
	function __construct( Article $article ) {
64
		// Set instance variables.
65
		$this->mArticle = $article;
0 ignored issues
show
Bug introduced by
The property mArticle does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
66
		$this->mTitle = $article->getTitle();
0 ignored issues
show
Bug introduced by
The property mTitle does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
67
		$this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
68
		$this->mContext = $article->getContext();
69
70
		// Check if the form should be disabled.
71
		// If it is, the form will be available in read-only to show levels.
72
		$this->mPermErrors = $this->mTitle->getUserPermissionsErrors(
73
			'protect',
74
			$this->mContext->getUser(),
75
			$this->mContext->getRequest()->wasPosted() ? 'secure' : 'full' // T92357
76
		);
77
		if ( wfReadOnly() ) {
78
			$this->mPermErrors[] = [ 'readonlytext', wfReadOnlyReason() ];
79
		}
80
		$this->disabled = $this->mPermErrors != [];
0 ignored issues
show
Bug introduced by
The property disabled does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
81
		$this->disabledAttrib = $this->disabled
0 ignored issues
show
Bug introduced by
The property disabledAttrib does not seem to exist. Did you mean disabled?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
82
			? [ 'disabled' => 'disabled' ]
83
			: [];
84
85
		$this->loadData();
86
	}
87
88
	/**
89
	 * Loads the current state of protection into the object.
90
	 */
91
	function loadData() {
92
		$levels = MWNamespace::getRestrictionLevels(
93
			$this->mTitle->getNamespace(), $this->mContext->getUser()
94
		);
95
		$this->mCascade = $this->mTitle->areRestrictionsCascading();
96
97
		$request = $this->mContext->getRequest();
98
		$this->mReason = $request->getText( 'mwProtect-reason' );
99
		$this->mReasonSelection = $request->getText( 'wpProtectReasonSelection' );
100
		$this->mCascade = $request->getBool( 'mwProtect-cascade', $this->mCascade );
101
102
		foreach ( $this->mApplicableTypes as $action ) {
103
			// @todo FIXME: This form currently requires individual selections,
104
			// but the db allows multiples separated by commas.
105
106
			// Pull the actual restriction from the DB
107
			$this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
108
109
			if ( !$this->mRestrictions[$action] ) {
110
				// No existing expiry
111
				$existingExpiry = '';
112
			} else {
113
				$existingExpiry = $this->mTitle->getRestrictionExpiry( $action );
114
			}
115
			$this->mExistingExpiry[$action] = $existingExpiry;
116
117
			$requestExpiry = $request->getText( "mwProtect-expiry-$action" );
118
			$requestExpirySelection = $request->getVal( "wpProtectExpirySelection-$action" );
119
120
			if ( $requestExpiry ) {
121
				// Custom expiry takes precedence
122
				$this->mExpiry[$action] = $requestExpiry;
123
				$this->mExpirySelection[$action] = 'othertime';
124
			} elseif ( $requestExpirySelection ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestExpirySelection of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
125
				// Expiry selected from list
126
				$this->mExpiry[$action] = '';
127
				$this->mExpirySelection[$action] = $requestExpirySelection;
128
			} elseif ( $existingExpiry ) {
129
				// Use existing expiry in its own list item
130
				$this->mExpiry[$action] = '';
131
				$this->mExpirySelection[$action] = $existingExpiry;
132
			} else {
133
				// Catches 'infinity' - Existing expiry is infinite, use "infinite" in drop-down
134
				// Final default: infinite
135
				$this->mExpiry[$action] = '';
136
				$this->mExpirySelection[$action] = 'infinite';
137
			}
138
139
			$val = $request->getVal( "mwProtect-level-$action" );
140
			if ( isset( $val ) && in_array( $val, $levels ) ) {
141
				$this->mRestrictions[$action] = $val;
142
			}
143
		}
144
	}
145
146
	/**
147
	 * Get the expiry time for a given action, by combining the relevant inputs.
148
	 *
149
	 * @param string $action
150
	 *
151
	 * @return string 14-char timestamp or "infinity", or false if the input was invalid
152
	 */
153
	function getExpiry( $action ) {
154
		if ( $this->mExpirySelection[$action] == 'existing' ) {
155
			return $this->mExistingExpiry[$action];
156
		} elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
157
			$value = $this->mExpiry[$action];
158
		} else {
159
			$value = $this->mExpirySelection[$action];
160
		}
161 View Code Duplication
		if ( wfIsInfinity( $value ) ) {
162
			$time = 'infinity';
163
		} else {
164
			$unix = strtotime( $value );
165
166
			if ( !$unix || $unix === -1 ) {
167
				return false;
168
			}
169
170
			// @todo FIXME: Non-qualified absolute times are not in users specified timezone
171
			// and there isn't notice about it in the ui
172
			$time = wfTimestamp( TS_MW, $unix );
173
		}
174
		return $time;
175
	}
176
177
	/**
178
	 * Main entry point for action=protect and action=unprotect
179
	 */
180
	function execute() {
181
		if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === [ '' ] ) {
182
			throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
183
		}
184
185
		if ( $this->mContext->getRequest()->wasPosted() ) {
186
			if ( $this->save() ) {
187
				$q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
188
				$this->mContext->getOutput()->redirect( $this->mTitle->getFullURL( $q ) );
189
			}
190
		} else {
191
			$this->show();
192
		}
193
	}
194
195
	/**
196
	 * Show the input form with optional error message
197
	 *
198
	 * @param string $err Error message or null if there's no error
199
	 */
200
	function show( $err = null ) {
201
		$out = $this->mContext->getOutput();
202
		$out->setRobotPolicy( 'noindex,nofollow' );
203
		$out->addBacklinkSubtitle( $this->mTitle );
204
205
		if ( is_array( $err ) ) {
206
			$out->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
207
		} elseif ( is_string( $err ) ) {
208
			$out->addHTML( "<p class='error'>{$err}</p>\n" );
209
		}
210
211
		if ( $this->mTitle->getRestrictionTypes() === [] ) {
212
			// No restriction types available for the current title
213
			// this might happen if an extension alters the available types
214
			$out->setPageTitle( $this->mContext->msg(
215
				'protect-norestrictiontypes-title',
216
				$this->mTitle->getPrefixedText()
217
			) );
218
			$out->addWikiText( $this->mContext->msg( 'protect-norestrictiontypes-text' )->plain() );
219
220
			// Show the log in case protection was possible once
221
			$this->showLogExtract( $out );
222
			// return as there isn't anything else we can do
223
			return;
224
		}
225
226
		list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
227
		if ( $cascadeSources && count( $cascadeSources ) > 0 ) {
228
			$titles = '';
229
230
			foreach ( $cascadeSources as $title ) {
231
				$titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
232
			}
233
234
			/** @todo FIXME: i18n issue, should use formatted number. */
235
			$out->wrapWikiMsg(
236
				"<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>",
237
				[ 'protect-cascadeon', count( $cascadeSources ) ]
238
			);
239
		}
240
241
		# Show an appropriate message if the user isn't allowed or able to change
242
		# the protection settings at this time
243
		if ( $this->disabled ) {
244
			$out->setPageTitle(
245
				$this->mContext->msg( 'protect-title-notallowed',
246
					$this->mTitle->getPrefixedText() )
247
			);
248
			$out->addWikiText( $out->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
249
		} else {
250
			$out->setPageTitle( $this->mContext->msg( 'protect-title', $this->mTitle->getPrefixedText() ) );
251
			$out->addWikiMsg( 'protect-text',
252
				wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
253
		}
254
255
		$out->addHTML( $this->buildForm() );
256
		$this->showLogExtract( $out );
257
	}
258
259
	/**
260
	 * Save submitted protection form
261
	 *
262
	 * @return bool Success
263
	 */
264
	function save() {
265
		# Permission check!
266
		if ( $this->disabled ) {
267
			$this->show();
268
			return false;
269
		}
270
271
		$request = $this->mContext->getRequest();
272
		$user = $this->mContext->getUser();
273
		$out = $this->mContext->getOutput();
274
		$token = $request->getVal( 'wpEditToken' );
275
		if ( !$user->matchEditToken( $token, [ 'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
276
			$this->show( [ 'sessionfailure' ] );
277
			return false;
278
		}
279
280
		# Create reason string. Use list and/or custom string.
281
		$reasonstr = $this->mReasonSelection;
282
		if ( $reasonstr != 'other' && $this->mReason != '' ) {
283
			// Entry from drop down menu + additional comment
284
			$reasonstr .= $this->mContext->msg( 'colon-separator' )->text() . $this->mReason;
285
		} elseif ( $reasonstr == 'other' ) {
286
			$reasonstr = $this->mReason;
287
		}
288
		$expiry = [];
289
		foreach ( $this->mApplicableTypes as $action ) {
290
			$expiry[$action] = $this->getExpiry( $action );
291
			if ( empty( $this->mRestrictions[$action] ) ) {
292
				continue; // unprotected
293
			}
294
			if ( !$expiry[$action] ) {
295
				$this->show( [ 'protect_expiry_invalid' ] );
296
				return false;
297
			}
298
			if ( $expiry[$action] < wfTimestampNow() ) {
299
				$this->show( [ 'protect_expiry_old' ] );
300
				return false;
301
			}
302
		}
303
304
		$this->mCascade = $request->getBool( 'mwProtect-cascade' );
305
306
		$status = $this->mArticle->doUpdateRestrictions(
307
			$this->mRestrictions,
308
			$expiry,
309
			$this->mCascade,
310
			$reasonstr,
311
			$user
312
		);
313
314
		if ( !$status->isOK() ) {
315
			$this->show( $out->parseInline( $status->getWikiText() ) );
316
			return false;
317
		}
318
319
		/**
320
		 * Give extensions a change to handle added form items
321
		 *
322
		 * @since 1.19 you can (and you should) return false to abort saving;
323
		 *             you can also return an array of message name and its parameters
324
		 */
325
		$errorMsg = '';
326
		if ( !Hooks::run( 'ProtectionForm::save', [ $this->mArticle, &$errorMsg, $reasonstr ] ) ) {
327
			if ( $errorMsg == '' ) {
328
				$errorMsg = [ 'hookaborted' ];
329
			}
330
		}
331
		if ( $errorMsg != '' ) {
332
			$this->show( $errorMsg );
0 ignored issues
show
Bug introduced by
It seems like $errorMsg defined by array('hookaborted') on line 328 can also be of type array<integer,string,{"0":"string"}>; however, ProtectionForm::show() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
333
			return false;
334
		}
335
336
		WatchAction::doWatchOrUnwatch( $request->getCheck( 'mwProtectWatch' ), $this->mTitle, $user );
337
338
		return true;
339
	}
340
341
	/**
342
	 * Build the input form
343
	 *
344
	 * @return string HTML form
345
	 */
346
	function buildForm() {
347
		$context = $this->mContext;
348
		$user = $context->getUser();
349
		$output = $context->getOutput();
350
		$lang = $context->getLanguage();
351
		$cascadingRestrictionLevels = $context->getConfig()->get( 'CascadingRestrictionLevels' );
352
		$out = '';
353
		if ( !$this->disabled ) {
354
			$output->addModules( 'mediawiki.legacy.protect' );
355
			$output->addJsConfigVars( 'wgCascadeableLevels', $cascadingRestrictionLevels );
356
			$out .= Xml::openElement( 'form', [ 'method' => 'post',
357
				'action' => $this->mTitle->getLocalURL( 'action=protect' ),
358
				'id' => 'mw-Protect-Form' ] );
359
		}
360
361
		$out .= Xml::openElement( 'fieldset' ) .
362
			Xml::element( 'legend', null, $context->msg( 'protect-legend' )->text() ) .
363
			Xml::openElement( 'table', [ 'id' => 'mwProtectSet' ] ) .
364
			Xml::openElement( 'tbody' );
365
366
		$scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
367
		$showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
368
369
		// Not all languages have V_x <-> N_x relation
370
		foreach ( $this->mRestrictions as $action => $selected ) {
371
			// Messages:
372
			// restriction-edit, restriction-move, restriction-create, restriction-upload
373
			$msg = $context->msg( 'restriction-' . $action );
374
			$out .= "<tr><td>" .
375
			Xml::openElement( 'fieldset' ) .
376
			Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
377
			Xml::openElement( 'table', [ 'id' => "mw-protect-table-$action" ] ) .
378
				"<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
379
380
			$mProtectexpiry = Xml::label(
381
				$context->msg( 'protectexpiry' )->text(),
382
				"mwProtectExpirySelection-$action"
383
			);
384
			$mProtectother = Xml::label(
385
				$context->msg( 'protect-othertime' )->text(),
386
				"mwProtect-$action-expires"
387
			);
388
389
			$expiryFormOptions = new XmlSelect(
390
				"wpProtectExpirySelection-$action",
391
				"mwProtectExpirySelection-$action",
392
				$this->mExpirySelection[$action]
393
			);
394
			$expiryFormOptions->setAttribute( 'tabindex', '2' );
395
			if ( $this->disabled ) {
396
				$expiryFormOptions->setAttribute( 'disabled', 'disabled' );
397
			}
398
399
			if ( $this->mExistingExpiry[$action] ) {
400
				if ( $this->mExistingExpiry[$action] == 'infinity' ) {
401
					$existingExpiryMessage = $context->msg( 'protect-existing-expiry-infinity' );
402
				} else {
403
					$timestamp = $lang->userTimeAndDate( $this->mExistingExpiry[$action], $user );
404
					$d = $lang->userDate( $this->mExistingExpiry[$action], $user );
405
					$t = $lang->userTime( $this->mExistingExpiry[$action], $user );
406
					$existingExpiryMessage = $context->msg(
407
						'protect-existing-expiry',
408
						$timestamp,
409
						$d,
410
						$t
411
					);
412
				}
413
				$expiryFormOptions->addOption( $existingExpiryMessage->text(), 'existing' );
414
			}
415
416
			$expiryFormOptions->addOption(
417
				$context->msg( 'protect-othertime-op' )->text(),
418
				'othertime'
419
			);
420
			foreach ( explode( ',', $scExpiryOptions ) as $option ) {
421
				if ( strpos( $option, ":" ) === false ) {
422
					$show = $value = $option;
423
				} else {
424
					list( $show, $value ) = explode( ":", $option );
425
				}
426
				$expiryFormOptions->addOption( $show, htmlspecialchars( $value ) );
427
			}
428
			# Add expiry dropdown
429
			if ( $showProtectOptions && !$this->disabled ) {
430
				$out .= "
431
					<table><tr>
432
						<td class='mw-label'>
433
							{$mProtectexpiry}
434
						</td>
435
						<td class='mw-input'>" .
436
							$expiryFormOptions->getHTML() .
437
						"</td>
438
					</tr></table>";
439
			}
440
			# Add custom expiry field
441
			$attribs = [ 'id' => "mwProtect-$action-expires" ] + $this->disabledAttrib;
0 ignored issues
show
Bug introduced by
The property disabledAttrib does not seem to exist. Did you mean disabled?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
442
			$out .= "<table><tr>
443
					<td class='mw-label'>" .
444
						$mProtectother .
445
					'</td>
446
					<td class="mw-input">' .
447
						Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
448
					'</td>
449
				</tr></table>';
450
			$out .= "</td></tr>" .
451
			Xml::closeElement( 'table' ) .
452
			Xml::closeElement( 'fieldset' ) .
453
			"</td></tr>";
454
		}
455
		# Give extensions a chance to add items to the form
456
		Hooks::run( 'ProtectionForm::buildForm', [ $this->mArticle, &$out ] );
457
458
		$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
459
460
		// JavaScript will add another row with a value-chaining checkbox
461
		if ( $this->mTitle->exists() ) {
462
			$out .= Xml::openElement( 'table', [ 'id' => 'mw-protect-table2' ] ) .
463
				Xml::openElement( 'tbody' );
464
			$out .= '<tr>
465
					<td></td>
466
					<td class="mw-input">' .
467
						Xml::checkLabel(
468
							$context->msg( 'protect-cascade' )->text(),
469
							'mwProtect-cascade',
470
							'mwProtect-cascade',
471
							$this->mCascade, $this->disabledAttrib
0 ignored issues
show
Bug introduced by
The property disabledAttrib does not seem to exist. Did you mean disabled?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
472
						) .
473
					"</td>
474
				</tr>\n";
475
			$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
476
		}
477
478
		# Add manual and custom reason field/selects as well as submit
479
		if ( !$this->disabled ) {
480
			$mProtectreasonother = Xml::label(
481
				$context->msg( 'protectcomment' )->text(),
482
				'wpProtectReasonSelection'
483
			);
484
485
			$mProtectreason = Xml::label(
486
				$context->msg( 'protect-otherreason' )->text(),
487
				'mwProtect-reason'
488
			);
489
490
			$reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
491
				wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
492
				wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
493
				$this->mReasonSelection,
494
				'mwProtect-reason', 4 );
495
496
			$out .= Xml::openElement( 'table', [ 'id' => 'mw-protect-table3' ] ) .
497
				Xml::openElement( 'tbody' );
498
			$out .= "
499
				<tr>
500
					<td class='mw-label'>
501
						{$mProtectreasonother}
502
					</td>
503
					<td class='mw-input'>
504
						{$reasonDropDown}
505
					</td>
506
				</tr>
507
				<tr>
508
					<td class='mw-label'>
509
						{$mProtectreason}
510
					</td>
511
					<td class='mw-input'>" .
512
						Xml::input( 'mwProtect-reason', 60, $this->mReason, [ 'type' => 'text',
513
							'id' => 'mwProtect-reason', 'maxlength' => 180 ] ) .
514
							// Limited maxlength as the database trims at 255 bytes and other texts
515
							// chosen by dropdown menus on this page are also included in this database field.
516
							// The byte limit of 180 bytes is enforced in javascript
517
					"</td>
518
				</tr>";
519
			# Disallow watching is user is not logged in
520
			if ( $user->isLoggedIn() ) {
521
				$out .= "
522
				<tr>
523
					<td></td>
524
					<td class='mw-input'>" .
525
						Xml::checkLabel( $context->msg( 'watchthis' )->text(),
526
							'mwProtectWatch', 'mwProtectWatch',
527
							$user->isWatched( $this->mTitle ) || $user->getOption( 'watchdefault' ) ) .
528
					"</td>
529
				</tr>";
530
			}
531
			$out .= "
532
				<tr>
533
					<td></td>
534
					<td class='mw-submit'>" .
535
						Xml::submitButton(
536
							$context->msg( 'confirm' )->text(),
537
							[ 'id' => 'mw-Protect-submit' ]
538
						) .
539
					"</td>
540
				</tr>\n";
541
			$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
542
		}
543
		$out .= Xml::closeElement( 'fieldset' );
544
545 View Code Duplication
		if ( $user->isAllowed( 'editinterface' ) ) {
546
			$link = Linker::linkKnown(
547
				$context->msg( 'protect-dropdown' )->inContentLanguage()->getTitle(),
548
				$context->msg( 'protect-edit-reasonlist' )->escaped(),
549
				[],
550
				[ 'action' => 'edit' ]
551
			);
552
			$out .= '<p class="mw-protect-editreasons">' . $link . '</p>';
553
		}
554
555
		if ( !$this->disabled ) {
556
			$out .= Html::hidden(
557
				'wpEditToken',
558
				$user->getEditToken( [ 'protect', $this->mTitle->getPrefixedDBkey() ] )
559
			);
560
			$out .= Xml::closeElement( 'form' );
561
		}
562
563
		return $out;
564
	}
565
566
	/**
567
	 * Build protection level selector
568
	 *
569
	 * @param string $action Action to protect
570
	 * @param string $selected Current protection level
571
	 * @return string HTML fragment
572
	 */
573
	function buildSelector( $action, $selected ) {
574
		// If the form is disabled, display all relevant levels. Otherwise,
575
		// just show the ones this user can use.
576
		$levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
577
			$this->disabled ? null : $this->mContext->getUser()
578
		);
579
580
		$id = 'mwProtect-level-' . $action;
581
582
		$select = new XmlSelect( $id, $id, $selected );
583
		$select->setAttribute( 'size', count( $levels ) );
584
		if ( $this->disabled ) {
585
			$select->setAttribute( 'disabled', 'disabled' );
586
		}
587
588
		foreach ( $levels as $key ) {
589
			$select->addOption( $this->getOptionLabel( $key ), $key );
590
		}
591
592
		return $select->getHTML();
593
	}
594
595
	/**
596
	 * Prepare the label for a protection selector option
597
	 *
598
	 * @param string $permission Permission required
599
	 * @return string
600
	 */
601
	private function getOptionLabel( $permission ) {
602
		if ( $permission == '' ) {
603
			return $this->mContext->msg( 'protect-default' )->text();
604
		} else {
605
			// Messages: protect-level-autoconfirmed, protect-level-sysop
606
			$msg = $this->mContext->msg( "protect-level-{$permission}" );
607
			if ( $msg->exists() ) {
608
				return $msg->text();
609
			}
610
			return $this->mContext->msg( 'protect-fallback', $permission )->text();
611
		}
612
	}
613
614
	/**
615
	 * Show protection long extracts for this page
616
	 *
617
	 * @param OutputPage $out
618
	 * @access private
619
	 */
620
	function showLogExtract( &$out ) {
621
		# Show relevant lines from the protection log:
622
		$protectLogPage = new LogPage( 'protect' );
623
		$out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
624
		LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
625
		# Let extensions add other relevant log extracts
626
		Hooks::run( 'ProtectionForm::showLogExtract', [ $this->mArticle, $out ] );
627
	}
628
}
629