101 private $watchlistManager;
107 private $restrictionStore;
110 private $titleFormatter;
114 $this->mArticle = $article;
115 $this->mTitle = $article->
getTitle();
117 $this->mRequest = $this->mContext->getRequest();
118 $this->mPerformer = $this->mContext->getAuthority();
119 $this->mOut = $this->mContext->getOutput();
120 $this->mLang = $this->mContext->getLanguage();
122 $services = MediaWikiServices::getInstance();
123 $this->permManager = $services->getPermissionManager();
124 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
125 $this->watchlistManager = $services->getWatchlistManager();
126 $this->titleFormatter = $services->getTitleFormatter();
127 $this->restrictionStore = $services->getRestrictionStore();
128 $this->mApplicableTypes = $this->restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
132 $this->mPermStatus = PermissionStatus::newEmpty();
133 if ( $this->mRequest->wasPosted() ) {
134 $this->mPerformer->authorizeWrite(
'protect', $this->mTitle, $this->mPermStatus );
136 $this->mPerformer->authorizeRead(
'protect', $this->mTitle, $this->mPermStatus );
138 $readOnlyMode = $services->getReadOnlyMode();
139 if ( $readOnlyMode->isReadOnly() ) {
140 $this->mPermStatus->fatal(
'readonlytext', $readOnlyMode->getReason() );
142 $this->disabled = !$this->mPermStatus->isGood();
143 $this->disabledAttrib = $this->disabled ? [
'disabled' =>
'disabled' ] : [];
151 private function loadData() {
152 $levels = $this->permManager->getNamespaceRestrictionLevels(
153 $this->mTitle->getNamespace(), $this->mPerformer->getUser()
156 $this->mCascade = $this->restrictionStore->areRestrictionsCascading( $this->mTitle );
157 $this->mReason = $this->mRequest->getText(
'mwProtect-reason' );
158 $this->mReasonSelection = $this->mRequest->getText(
'wpProtectReasonSelection' );
159 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade', $this->mCascade );
161 foreach ( $this->mApplicableTypes as $action ) {
166 $this->mRestrictions[$action] = implode(
'',
167 $this->restrictionStore->getRestrictions( $this->mTitle, $action ) );
169 if ( !$this->mRestrictions[$action] ) {
171 $existingExpiry =
'';
173 $existingExpiry = $this->restrictionStore->getRestrictionExpiry( $this->mTitle, $action );
175 $this->mExistingExpiry[$action] = $existingExpiry;
177 $requestExpiry = $this->mRequest->getText(
"mwProtect-expiry-$action" );
178 $requestExpirySelection = $this->mRequest->getVal(
"wpProtectExpirySelection-$action" );
180 if ( $requestExpiry ) {
182 $this->mExpiry[$action] = $requestExpiry;
183 $this->mExpirySelection[$action] =
'othertime';
184 } elseif ( $requestExpirySelection ) {
186 $this->mExpiry[$action] =
'';
187 $this->mExpirySelection[$action] = $requestExpirySelection;
188 } elseif ( $existingExpiry ) {
190 $this->mExpiry[$action] =
'';
191 $this->mExpirySelection[$action] = $existingExpiry;
195 $this->mExpiry[$action] =
'';
196 $this->mExpirySelection[$action] =
'infinite';
199 $val = $this->mRequest->getVal(
"mwProtect-level-$action" );
200 if ( isset( $val ) && in_array( $val, $levels ) ) {
201 $this->mRestrictions[$action] = $val;
213 private function getExpiry( $action ) {
214 if ( $this->mExpirySelection[$action] ==
'existing' ) {
215 return $this->mExistingExpiry[$action];
216 } elseif ( $this->mExpirySelection[$action] ==
'othertime' ) {
217 $value = $this->mExpiry[$action];
219 $value = $this->mExpirySelection[$action];
224 $unix = strtotime( $value );
226 if ( !$unix || $unix === -1 ) {
242 $this->permManager->getNamespaceRestrictionLevels(
243 $this->mTitle->getNamespace()
246 throw new ErrorPageError(
'protect-badnamespace-title',
'protect-badnamespace-text' );
249 if ( $this->mRequest->wasPosted() ) {
250 if ( $this->save() ) {
251 $q = $this->mArticle->getPage()->isRedirect() ?
'redirect=no' :
'';
252 $this->mOut->redirect( $this->mTitle->getFullURL( $q ) );
265 private function show( $err =
null ) {
267 $out->setRobotPolicy(
'noindex,nofollow' );
268 $out->addBacklinkSubtitle( $this->mTitle );
270 if ( is_array( $err ) ) {
271 $out->addHTML( Html::errorBox( $out->msg( ...$err )->parse() ) );
272 } elseif ( is_string( $err ) ) {
273 $out->addHTML( Html::errorBox( $err ) );
276 if ( $this->mApplicableTypes === [] ) {
279 $out->setPageTitle( $this->mContext->msg(
280 'protect-norestrictiontypes-title',
281 $this->mTitle->getPrefixedText()
283 $out->addWikiTextAsInterface(
284 $this->mContext->msg(
'protect-norestrictiontypes-text' )->plain()
288 $this->showLogExtract();
293 list( $cascadeSources, ) =
294 $this->restrictionStore->getCascadeProtectionSources( $this->mTitle );
295 if ( count( $cascadeSources ) > 0 ) {
298 foreach ( $cascadeSources as $pageIdentity ) {
299 $titles .=
'* [[:' . $this->titleFormatter->getPrefixedText( $pageIdentity ) .
"]]\n";
304 "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles .
"</div>",
305 [
'protect-cascadeon', count( $cascadeSources ) ]
309 # Show an appropriate message if the user isn't allowed or able to change
310 # the protection settings at this time
311 if ( $this->disabled ) {
313 $this->mContext->msg(
'protect-title-notallowed',
314 $this->mTitle->getPrefixedText() )
316 $out->addWikiTextAsInterface(
317 $out->formatPermissionStatus( $this->mPermStatus,
'protect' )
321 $this->mContext->msg(
'protect-title', $this->mTitle->getPrefixedText() )
323 $out->addWikiMsg(
'protect-text',
327 $out->addHTML( $this->buildForm() );
328 $this->showLogExtract();
336 private function save() {
338 if ( $this->disabled ) {
343 $token = $this->mRequest->getVal(
'wpEditToken' );
344 $legacyUser = MediaWikiServices::getInstance()
346 ->newFromAuthority( $this->mPerformer );
347 if ( !$legacyUser->matchEditToken( $token, [
'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
348 $this->show( [
'sessionfailure' ] );
352 # Create reason string. Use list and/or custom string.
354 if ( $reasonstr !=
'other' && $this->mReason !=
'' ) {
356 $reasonstr .= $this->mContext->msg(
'colon-separator' )->text() .
$this->mReason;
357 } elseif ( $reasonstr ==
'other' ) {
362 foreach ( $this->mApplicableTypes as $action ) {
363 $expiry[$action] = $this->getExpiry( $action );
364 if ( empty( $this->mRestrictions[$action] ) ) {
368 if ( !$expiry[$action] ) {
369 $this->show( [
'protect_expiry_invalid' ] );
373 $this->show( [
'protect_expiry_old' ] );
378 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade' );
380 $status = $this->mArticle->getPage()->doUpdateRestrictions(
381 $this->mRestrictions,
385 $this->mPerformer->getUser()
388 if ( !$status->isOK() ) {
389 $this->show( $this->mOut->parseInlineAsInterface(
390 $status->getWikiText(
false,
false, $this->mLang )
402 if ( !$this->hookRunner->onProtectionForm__save( $this->mArticle, $errorMsg, $reasonstr ) ) {
403 if ( $errorMsg ==
'' ) {
404 $errorMsg = [
'hookaborted' ];
407 if ( $errorMsg !=
'' ) {
408 $this->show( $errorMsg );
412 $this->watchlistManager->setWatch(
413 $this->mRequest->getCheck(
'mwProtectWatch' ),
426 private function buildForm() {
427 $this->mOut->enableOOUI();
430 if ( !$this->disabled ) {
431 $this->mOut->addModules(
'mediawiki.action.protect' );
432 $this->mOut->addModuleStyles(
'mediawiki.action.styles' );
434 $scExpiryOptions = $this->mContext->msg(
'protect-expiry-options' )->inContentLanguage()->text();
435 $levels = $this->permManager->getNamespaceRestrictionLevels(
436 $this->mTitle->getNamespace(),
437 $this->disabled ?
null : $this->mPerformer->
getUser()
441 foreach ( $this->mRestrictions as $action => $selected ) {
444 $section =
'restriction-' . $action;
445 $id =
'mwProtect-level-' . $action;
447 foreach ( $levels as $key ) {
448 $options[$this->getOptionLabel( $key )] = $key;
454 'default' => $selected,
456 'size' => count( $levels ),
457 'options' => $options,
459 'section' => $section,
464 if ( $this->mExistingExpiry[$action] ) {
465 if ( $this->mExistingExpiry[$action] ==
'infinity' ) {
466 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry-infinity' );
468 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry' )
469 ->dateTimeParams( $this->mExistingExpiry[$action] )
470 ->dateParams( $this->mExistingExpiry[$action] )
471 ->timeParams( $this->mExistingExpiry[$action] );
473 $expiryOptions[$existingExpiryMessage->text()] =
'existing';
476 $expiryOptions[$this->mContext->msg(
'protect-othertime-op' )->text()] =
'othertime';
480 # Add expiry dropdown
481 $fields[
"wpProtectExpirySelection-$action"] = [
483 'name' =>
"wpProtectExpirySelection-$action",
484 'id' =>
"mwProtectExpirySelection-$action",
487 'label' => $this->mContext->msg(
'protectexpiry' )->text(),
488 'options' => $expiryOptions,
489 'default' => $this->mExpirySelection[$action],
490 'section' => $section,
493 # Add custom expiry field
494 if ( !$this->disabled ) {
495 $fields[
"mwProtect-expiry-$action"] = [
497 'label' => $this->mContext->msg(
'protect-othertime' )->text(),
498 'name' =>
"mwProtect-expiry-$action",
499 'id' =>
"mwProtect-$action-expires",
501 'default' => $this->mExpiry[$action],
503 'section' => $section,
508 # Give extensions a chance to add items to the form
510 $hookFormOptions = [];
512 $this->hookRunner->onProtectionForm__buildForm( $this->mArticle, $hookFormRaw );
513 $this->hookRunner->onProtectionFormAddFormFields( $this->mArticle, $hookFormOptions );
515 # Merge forms added from addFormFields
516 $fields = array_merge( $fields, $hookFormOptions );
518 # Add raw sections added in buildForm
519 if ( $hookFormRaw ) {
520 $fields[
'rawinfo'] = [
522 'default' => $hookFormRaw,
524 'section' =>
'restriction-blank'
528 # JavaScript will add another row with a value-chaining checkbox
529 if ( $this->mTitle->exists() ) {
530 $fields[
'mwProtect-cascade'] = [
532 'label' => $this->mContext->msg(
'protect-cascade' )->text(),
533 'id' =>
'mwProtect-cascade',
534 'name' =>
'mwProtect-cascade',
540 # Add manual and custom reason field/selects as well as submit
541 if ( !$this->disabled ) {
548 $fields[
'wpProtectReasonSelection'] = [
550 'cssclass' =>
'mwProtect-reason',
551 'label' => $this->mContext->msg(
'protectcomment' )->text(),
553 'id' =>
'wpProtectReasonSelection',
554 'name' =>
'wpProtectReasonSelection',
557 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->text(),
558 [
'other' => $this->mContext->msg(
'protect-otherreason-op' )->inContentLanguage()->text() ]
562 $fields[
'mwProtect-reason'] = [
564 'id' =>
'mwProtect-reason',
565 'label' => $this->mContext->msg(
'protect-otherreason' )->text(),
566 'name' =>
'mwProtect-reason',
568 'maxlength' => $maxlength,
571 # Disallow watching if user is not logged in
572 if ( $this->mPerformer->getUser()->isRegistered() ) {
573 $fields[
'mwProtectWatch'] = [
575 'id' =>
'mwProtectWatch',
576 'label' => $this->mContext->msg(
'watchthis' )->text(),
577 'name' =>
'mwProtectWatch',
579 $this->watchlistManager->isWatched( $this->mPerformer, $this->mTitle )
580 || MediaWikiServices::getInstance()->getUserOptionsLookup()->getOption(
581 $this->mPerformer->getUser(),
589 if ( $this->mPerformer->isAllowed(
'editinterface' ) ) {
590 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
591 $link = $linkRenderer->makeKnownLink(
592 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->getTitle(),
593 $this->mContext->msg(
'protect-edit-reasonlist' )->text(),
595 [
'action' =>
'edit' ]
597 $out .=
'<p class="mw-protect-editreasons">' . $link .
'</p>';
602 ->setMethod(
'post' )
603 ->setId(
'mw-Protect-Form' )
604 ->setTableId(
'mw-protect-table2' )
605 ->setAction( $this->mTitle->getLocalURL(
'action=protect' ) )
606 ->setSubmitID(
'mw-Protect-submit' )
607 ->setSubmitTextMsg(
'confirm' )
608 ->setTokenSalt( [
'protect', $this->mTitle->getPrefixedDBkey() ] )
609 ->suppressDefaultSubmit( $this->disabled )
610 ->setWrapperLegendMsg(
'protect-legend' )
613 return $htmlForm->getHTML(
false ) . $out;
622 private function getOptionLabel( $permission ) {
623 if ( $permission ==
'' ) {
624 return $this->mContext->msg(
'protect-default' )->text();
627 $msg = $this->mContext->msg(
"protect-level-{$permission}" );
628 if ( $msg->exists() ) {
631 return $this->mContext->msg(
'protect-fallback', $permission )->text();
638 private function showLogExtract() {
639 # Show relevant lines from the protection log:
640 $protectLogPage =
new LogPage(
'protect' );
641 $this->mOut->addHTML( Xml::element(
'h2',
null, $protectLogPage->getName()->text() ) );
644 # Let extensions add other relevant log extracts
645 $this->hookRunner->onProtectionForm__showLogExtract( $this->mArticle, $this->mOut );