113 private $permManager;
118 private $watchlistManager;
124 private $restrictionStore;
127 private $titleFormatter;
131 $this->mArticle = $article;
132 $this->mTitle = $article->
getTitle();
134 $this->mRequest = $this->mContext->getRequest();
135 $this->mPerformer = $this->mContext->getAuthority();
136 $this->mOut = $this->mContext->getOutput();
137 $this->mLang = $this->mContext->getLanguage();
140 $this->permManager = $services->getPermissionManager();
141 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
142 $this->watchlistManager = $services->getWatchlistManager();
143 $this->titleFormatter = $services->getTitleFormatter();
144 $this->restrictionStore = $services->getRestrictionStore();
145 $this->mApplicableTypes = $this->restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
150 if ( $this->mRequest->wasPosted() ) {
151 $this->mPerformer->authorizeWrite(
'protect', $this->mTitle, $this->mPermStatus );
153 $this->mPerformer->authorizeRead(
'protect', $this->mTitle, $this->mPermStatus );
155 $readOnlyMode = $services->getReadOnlyMode();
156 if ( $readOnlyMode->isReadOnly() ) {
157 $this->mPermStatus->fatal(
'readonlytext', $readOnlyMode->getReason() );
159 $this->disabled = !$this->mPermStatus->isGood();
160 $this->disabledAttrib = $this->disabled ? [
'disabled' =>
'disabled' ] : [];
168 private function loadData() {
169 $levels = $this->permManager->getNamespaceRestrictionLevels(
170 $this->mTitle->getNamespace(), $this->mPerformer->getUser()
173 $this->mCascade = $this->restrictionStore->areRestrictionsCascading( $this->mTitle );
174 $this->mReason = $this->mRequest->getText(
'mwProtect-reason' );
175 $this->mReasonSelection = $this->mRequest->getText(
'wpProtectReasonSelection' );
176 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade', $this->mCascade );
178 foreach ( $this->mApplicableTypes as $action ) {
183 $this->mRestrictions[$action] = implode(
'',
184 $this->restrictionStore->getRestrictions( $this->mTitle, $action ) );
186 if ( !$this->mRestrictions[$action] ) {
188 $existingExpiry =
'';
190 $existingExpiry = $this->restrictionStore->getRestrictionExpiry( $this->mTitle, $action );
192 $this->mExistingExpiry[$action] = $existingExpiry;
194 $requestExpiry = $this->mRequest->getText(
"mwProtect-expiry-$action" );
195 $requestExpirySelection = $this->mRequest->getVal(
"wpProtectExpirySelection-$action" );
197 if ( $requestExpiry ) {
199 $this->mExpiry[$action] = $requestExpiry;
200 $this->mExpirySelection[$action] =
'othertime';
201 } elseif ( $requestExpirySelection ) {
203 $this->mExpiry[$action] =
'';
204 $this->mExpirySelection[$action] = $requestExpirySelection;
205 } elseif ( $existingExpiry ) {
207 $this->mExpiry[$action] =
'';
208 $this->mExpirySelection[$action] = $existingExpiry;
212 $this->mExpiry[$action] =
'';
213 $this->mExpirySelection[$action] =
'infinite';
216 $val = $this->mRequest->getVal(
"mwProtect-level-$action" );
217 if ( isset( $val ) && in_array( $val, $levels ) ) {
218 $this->mRestrictions[$action] = $val;
230 private function getExpiry( $action ) {
231 if ( $this->mExpirySelection[$action] ==
'existing' ) {
232 return $this->mExistingExpiry[$action];
233 } elseif ( $this->mExpirySelection[$action] ==
'othertime' ) {
234 $value = $this->mExpiry[$action];
236 $value = $this->mExpirySelection[$action];
241 $unix = strtotime( $value );
243 if ( !$unix || $unix === -1 ) {
259 $this->permManager->getNamespaceRestrictionLevels(
260 $this->mTitle->getNamespace()
263 throw new ErrorPageError(
'protect-badnamespace-title',
'protect-badnamespace-text' );
266 if ( $this->mRequest->wasPosted() ) {
267 if ( $this->save() ) {
268 $q = $this->mArticle->getPage()->isRedirect() ?
'redirect=no' :
'';
269 $this->mOut->redirect( $this->mTitle->getFullURL( $q ) );
282 private function show( $err =
null ) {
284 $out->setRobotPolicy(
'noindex,nofollow' );
285 $out->addBacklinkSubtitle( $this->mTitle );
287 if ( is_array( $err ) ) {
289 } elseif ( is_string( $err ) ) {
293 if ( $this->mApplicableTypes === [] ) {
296 $out->setPageTitleMsg( $this->mContext->msg(
297 'protect-norestrictiontypes-title'
299 $this->mTitle->getPrefixedText()
301 $out->addWikiTextAsInterface(
302 $this->mContext->msg(
'protect-norestrictiontypes-text' )->plain()
306 $this->showLogExtract();
311 [ $cascadeSources, ] =
312 $this->restrictionStore->getCascadeProtectionSources( $this->mTitle );
313 if ( count( $cascadeSources ) > 0 ) {
316 foreach ( $cascadeSources as $pageIdentity ) {
317 $titles .=
'* [[:' . $this->titleFormatter->getPrefixedText( $pageIdentity ) .
"]]\n";
322 "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles .
"</div>",
323 [
'protect-cascadeon', count( $cascadeSources ) ]
327 # Show an appropriate message if the user isn't allowed or able to change
328 # the protection settings at this time
329 if ( $this->disabled ) {
330 $out->setPageTitleMsg(
331 $this->mContext->msg(
'protect-title-notallowed' )->plaintextParams( $this->mTitle->getPrefixedText() )
333 $out->addWikiTextAsInterface(
334 $out->formatPermissionStatus( $this->mPermStatus,
'protect' )
337 $out->setPageTitleMsg(
338 $this->mContext->msg(
'protect-title' )->plaintextParams( $this->mTitle->getPrefixedText() )
340 $out->addWikiMsg(
'protect-text',
344 $out->addHTML( $this->buildForm() );
345 $this->showLogExtract();
353 private function save() {
355 if ( $this->disabled ) {
360 $token = $this->mRequest->getVal(
'wpEditToken' );
363 ->newFromAuthority( $this->mPerformer );
364 if ( !$legacyUser->matchEditToken( $token, [
'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
365 $this->show( [
'sessionfailure' ] );
369 # Create reason string. Use list and/or custom string.
371 if ( $reasonstr !=
'other' && $this->mReason !=
'' ) {
373 $reasonstr .= $this->mContext->msg(
'colon-separator' )->text() .
$this->mReason;
374 } elseif ( $reasonstr ==
'other' ) {
379 foreach ( $this->mApplicableTypes as $action ) {
380 $expiry[$action] = $this->getExpiry( $action );
381 if ( empty( $this->mRestrictions[$action] ) ) {
385 if ( !$expiry[$action] ) {
386 $this->show( [
'protect_expiry_invalid' ] );
390 $this->show( [
'protect_expiry_old' ] );
395 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade' );
397 $status = $this->mArticle->getPage()->doUpdateRestrictions(
398 $this->mRestrictions,
402 $this->mPerformer->getUser()
405 if ( !$status->isOK() ) {
406 $this->show( $this->mOut->parseInlineAsInterface(
407 $status->getWikiText(
false,
false, $this->mLang )
419 if ( !$this->hookRunner->onProtectionForm__save( $this->mArticle, $errorMsg, $reasonstr ) ) {
420 if ( $errorMsg ==
'' ) {
421 $errorMsg = [
'hookaborted' ];
424 if ( $errorMsg !=
'' ) {
425 $this->show( $errorMsg );
429 $this->watchlistManager->setWatch(
430 $this->mRequest->getCheck(
'mwProtectWatch' ),
443 private function buildForm() {
444 $this->mOut->enableOOUI();
447 if ( !$this->disabled ) {
448 $this->mOut->addModules(
'mediawiki.action.protect' );
449 $this->mOut->addModuleStyles(
'mediawiki.action.styles' );
451 $scExpiryOptions = $this->mContext->msg(
'protect-expiry-options' )->inContentLanguage()->text();
452 $levels = $this->permManager->getNamespaceRestrictionLevels(
453 $this->mTitle->getNamespace(),
454 $this->disabled ?
null : $this->mPerformer->getUser()
458 foreach ( $this->mRestrictions as $action => $selected ) {
461 $section =
'restriction-' . $action;
462 $id =
'mwProtect-level-' . $action;
464 foreach ( $levels as $key ) {
465 $options[$this->getOptionLabel( $key )] = $key;
471 'default' => $selected,
473 'size' => count( $levels ),
474 'options' => $options,
476 'section' => $section,
481 if ( $this->mExistingExpiry[$action] ) {
482 if ( $this->mExistingExpiry[$action] ==
'infinity' ) {
483 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry-infinity' );
485 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry' )
486 ->dateTimeParams( $this->mExistingExpiry[$action] )
487 ->dateParams( $this->mExistingExpiry[$action] )
488 ->timeParams( $this->mExistingExpiry[$action] );
490 $expiryOptions[$existingExpiryMessage->text()] =
'existing';
493 $expiryOptions[$this->mContext->msg(
'protect-othertime-op' )->text()] =
'othertime';
497 # Add expiry dropdown
498 $fields[
"wpProtectExpirySelection-$action"] = [
500 'name' =>
"wpProtectExpirySelection-$action",
501 'id' =>
"mwProtectExpirySelection-$action",
504 'label' => $this->mContext->msg(
'protectexpiry' )->text(),
505 'options' => $expiryOptions,
506 'default' => $this->mExpirySelection[$action],
507 'section' => $section,
510 # Add custom expiry field
511 if ( !$this->disabled ) {
512 $fields[
"mwProtect-expiry-$action"] = [
514 'label' => $this->mContext->msg(
'protect-othertime' )->text(),
515 'name' =>
"mwProtect-expiry-$action",
516 'id' =>
"mwProtect-$action-expires",
518 'default' => $this->mExpiry[$action],
520 'section' => $section,
525 # Give extensions a chance to add items to the form
527 $hookFormOptions = [];
529 $this->hookRunner->onProtectionForm__buildForm( $this->mArticle, $hookFormRaw );
530 $this->hookRunner->onProtectionFormAddFormFields( $this->mArticle, $hookFormOptions );
532 # Merge forms added from addFormFields
533 $fields = array_merge( $fields, $hookFormOptions );
535 # Add raw sections added in buildForm
536 if ( $hookFormRaw ) {
537 $fields[
'rawinfo'] = [
539 'default' => $hookFormRaw,
541 'section' =>
'restriction-blank'
545 # JavaScript will add another row with a value-chaining checkbox
546 if ( $this->mTitle->exists() ) {
547 $fields[
'mwProtect-cascade'] = [
549 'label' => $this->mContext->msg(
'protect-cascade' )->text(),
550 'id' =>
'mwProtect-cascade',
551 'name' =>
'mwProtect-cascade',
557 # Add manual and custom reason field/selects as well as submit
558 if ( !$this->disabled ) {
565 $fields[
'wpProtectReasonSelection'] = [
567 'cssclass' =>
'mwProtect-reason',
568 'label' => $this->mContext->msg(
'protectcomment' )->text(),
570 'id' =>
'wpProtectReasonSelection',
571 'name' =>
'wpProtectReasonSelection',
574 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->text(),
575 [
'other' => $this->mContext->msg(
'protect-otherreason-op' )->text() ]
579 $fields[
'mwProtect-reason'] = [
581 'id' =>
'mwProtect-reason',
582 'label' => $this->mContext->msg(
'protect-otherreason' )->text(),
583 'name' =>
'mwProtect-reason',
585 'maxlength' => $maxlength,
588 # Disallow watching if user is not logged in
589 if ( $this->mPerformer->getUser()->isRegistered() ) {
590 $fields[
'mwProtectWatch'] = [
592 'id' =>
'mwProtectWatch',
593 'label' => $this->mContext->msg(
'watchthis' )->text(),
594 'name' =>
'mwProtectWatch',
596 $this->watchlistManager->isWatched( $this->mPerformer, $this->mTitle )
598 $this->mPerformer->getUser(),
606 if ( $this->mPerformer->isAllowed(
'editinterface' ) ) {
608 $link = $linkRenderer->makeKnownLink(
609 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->getTitle(),
610 $this->mContext->msg(
'protect-edit-reasonlist' )->text(),
612 [
'action' =>
'edit' ]
614 $out .=
'<p class="mw-protect-editreasons">' . $link .
'</p>';
619 ->setMethod(
'post' )
620 ->setId(
'mw-Protect-Form' )
621 ->setTableId(
'mw-protect-table2' )
622 ->setAction( $this->mTitle->getLocalURL(
'action=protect' ) )
623 ->setSubmitID(
'mw-Protect-submit' )
624 ->setSubmitTextMsg(
'confirm' )
625 ->setTokenSalt( [
'protect', $this->mTitle->getPrefixedDBkey() ] )
626 ->suppressDefaultSubmit( $this->disabled )
627 ->setWrapperLegendMsg(
'protect-legend' )
630 return $htmlForm->getHTML(
false ) . $out;
639 private function getOptionLabel( $permission ) {
640 if ( $permission ==
'' ) {
641 return $this->mContext->msg(
'protect-default' )->text();
644 $msg = $this->mContext->msg(
"protect-level-{$permission}" );
645 if ( $msg->exists() ) {
648 return $this->mContext->msg(
'protect-fallback', $permission )->text();
655 private function showLogExtract() {
656 # Show relevant lines from the protection log:
657 $protectLogPage =
new LogPage(
'protect' );
658 $this->mOut->addHTML(
Xml::element(
'h2',
null, $protectLogPage->getName()->text() ) );
661 # Let extensions add other relevant log extracts
662 $this->hookRunner->onProtectionForm__showLogExtract( $this->mArticle, $this->mOut );
669 class_alias( ProtectionForm::class,
'ProtectionForm' );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Legacy class representing an editable page and handling UI for some page actions.
getContext()
Gets the context this Article is executed in.
getTitle()
Get the title object of the article.
An error page which can definitely be safely rendered using the OutputPage.
Base class for language-specific code.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
This is one of the Core classes and should be read at least once by any new developers.
Handles the page protection UI and backend.
bool $mCascade
True if the restrictions are cascading, from request or existing protection.
execute()
Main entry point for action=protect and action=unprotect.
array $mRestrictions
A map of action to restriction level, from request or default.
array $mExpiry
Map of action to "other" expiry time.
PermissionStatus $mPermStatus
Permissions errors for the protect action.
string $mReasonSelection
The reason selected from the list, blank for other/additional.
array $mApplicableTypes
Types (i.e.
array $mExpirySelection
Map of action to value selected in expiry drop-down list.
__construct(Article $article)
string $mReason
The custom/additional protection reason.
array $mExistingExpiry
Map of action to the expiry time of the existing protection.
Class for generating HTML <select> or <datalist> elements.
static parseOptionsMessage(string $msg)
Parse labels and values out of a comma- and colon-separated list of options, such as is used for expi...
Module of static functions for generating XML.
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Interface for objects which can provide a MediaWiki context on request.