102 $this->mArticle = $article;
103 $this->mTitle = $article->
getTitle();
105 $this->mRequest = $this->mContext->getRequest();
106 $this->mPerformer = $this->mContext->getAuthority();
107 $this->mOut = $this->mContext->getOutput();
108 $this->mLang = $this->mContext->getLanguage();
111 $this->permManager = $services->getPermissionManager();
112 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
113 $this->watchlistManager = $services->getWatchlistManager();
114 $this->titleFormatter = $services->getTitleFormatter();
115 $this->restrictionStore = $services->getRestrictionStore();
116 $this->mApplicableTypes = $this->restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
120 $this->mPermStatus = PermissionStatus::newEmpty();
121 if ( $this->mRequest->wasPosted() ) {
122 $this->mPerformer->authorizeWrite(
'protect', $this->mTitle, $this->mPermStatus );
124 $this->mPerformer->authorizeRead(
'protect', $this->mTitle, $this->mPermStatus );
126 $readOnlyMode = $services->getReadOnlyMode();
127 if ( $readOnlyMode->isReadOnly() ) {
128 $this->mPermStatus->fatal(
'readonlytext', $readOnlyMode->getReason() );
130 $this->disabled = !$this->mPermStatus->isGood();
131 $this->disabledAttrib = $this->disabled ? [
'disabled' =>
'disabled' ] : [];
139 private function loadData() {
140 $levels = $this->permManager->getNamespaceRestrictionLevels(
141 $this->mTitle->getNamespace(), $this->mPerformer->getUser()
144 $this->mCascade = $this->restrictionStore->areRestrictionsCascading( $this->mTitle );
145 $this->mReason = $this->mRequest->getText(
'mwProtect-reason' );
146 $this->mReasonSelection = $this->mRequest->getText(
'wpProtectReasonSelection' );
147 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade', $this->mCascade );
149 foreach ( $this->mApplicableTypes as $action ) {
154 $this->mRestrictions[$action] = implode(
'',
155 $this->restrictionStore->getRestrictions( $this->mTitle, $action ) );
157 if ( !$this->mRestrictions[$action] ) {
159 $existingExpiry =
'';
161 $existingExpiry = $this->restrictionStore->getRestrictionExpiry( $this->mTitle, $action );
163 $this->mExistingExpiry[$action] = $existingExpiry;
165 $requestExpiry = $this->mRequest->getText(
"mwProtect-expiry-$action" );
166 $requestExpirySelection = $this->mRequest->getVal(
"wpProtectExpirySelection-$action" );
168 if ( $requestExpiry ) {
170 $this->mExpiry[$action] = $requestExpiry;
171 $this->mExpirySelection[$action] =
'othertime';
172 } elseif ( $requestExpirySelection ) {
174 $this->mExpiry[$action] =
'';
175 $this->mExpirySelection[$action] = $requestExpirySelection;
176 } elseif ( $existingExpiry ) {
178 $this->mExpiry[$action] =
'';
179 $this->mExpirySelection[$action] = $existingExpiry;
183 $this->mExpiry[$action] =
'';
184 $this->mExpirySelection[$action] =
'infinite';
187 $val = $this->mRequest->getVal(
"mwProtect-level-$action" );
188 if ( $val !==
null && in_array( $val, $levels ) ) {
189 $this->mRestrictions[$action] = $val;
202 private function getExpiry( $action ) {
203 if ( $this->mExpirySelection[$action] ==
'existing' ) {
204 return $this->mExistingExpiry[$action];
205 } elseif ( $this->mExpirySelection[$action] ==
'othertime' ) {
206 $value = $this->mExpiry[$action];
208 $value = $this->mExpirySelection[$action];
211 return ExpiryDef::normalizeExpiry( $value, TS_MW );
212 }
catch ( InvalidArgumentException ) {
222 $this->permManager->getNamespaceRestrictionLevels(
223 $this->mTitle->getNamespace()
226 throw new ErrorPageError(
'protect-badnamespace-title',
'protect-badnamespace-text' );
229 if ( $this->mRequest->wasPosted() ) {
230 if ( $this->save() ) {
231 $q = $this->mArticle->getPage()->isRedirect() ?
'redirect=no' :
'';
232 $this->mOut->redirect( $this->mTitle->getFullURL( $q ) );
245 private function show( $err =
null ) {
247 $out->setRobotPolicy(
'noindex,nofollow' );
248 $out->addBacklinkSubtitle( $this->mTitle );
250 if ( is_array( $err ) ) {
251 $out->addHTML( Html::errorBox( $out->msg( ...$err )->parse() ) );
252 } elseif ( is_string( $err ) ) {
253 $out->addHTML( Html::errorBox( $err ) );
256 if ( $this->mApplicableTypes === [] ) {
259 $out->setPageTitleMsg( $this->mContext->msg(
260 'protect-norestrictiontypes-title'
262 $this->mTitle->getPrefixedText()
264 $out->addWikiTextAsInterface(
265 $this->mContext->msg(
'protect-norestrictiontypes-text' )->plain()
269 $this->showLogExtract();
274 [ $cascadeSources, ] =
275 $this->restrictionStore->getCascadeProtectionSources( $this->mTitle );
276 if ( count( $cascadeSources ) > 0 ) {
279 foreach ( $cascadeSources as $pageIdentity ) {
280 $titles .=
'* [[:' . $this->titleFormatter->getPrefixedText( $pageIdentity ) .
"]]\n";
285 "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles .
"</div>",
286 [
'protect-cascadeon', count( $cascadeSources ) ]
290 # Show an appropriate message if the user isn't allowed or able to change
291 # the protection settings at this time
292 if ( $this->disabled ) {
293 $out->setPageTitleMsg(
294 $this->mContext->msg(
'protect-title-notallowed' )->plaintextParams( $this->mTitle->getPrefixedText() )
296 $out->addWikiTextAsInterface(
297 $out->formatPermissionStatus( $this->mPermStatus,
'protect' )
300 $out->setPageTitleMsg(
301 $this->mContext->msg(
'protect-title' )->plaintextParams( $this->mTitle->getPrefixedText() )
303 $out->addWikiMsg(
'protect-text',
307 $out->addHTML( $this->buildForm() );
308 $this->showLogExtract();
316 private function save() {
318 if ( $this->disabled ) {
323 $token = $this->mRequest->getVal(
'wpEditToken' );
326 ->newFromAuthority( $this->mPerformer );
327 if ( !$legacyUser->matchEditToken( $token, [
'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
328 $this->show( [
'sessionfailure' ] );
332 # Create reason string. Use list and/or custom string.
334 if ( $reasonstr !=
'other' && $this->mReason !=
'' ) {
336 $reasonstr .= $this->mContext->msg(
'colon-separator' )->text() .
$this->mReason;
337 } elseif ( $reasonstr ==
'other' ) {
342 foreach ( $this->mApplicableTypes as $action ) {
343 $expiry[$action] = $this->getExpiry( $action );
344 if ( empty( $this->mRestrictions[$action] ) ) {
348 if ( !$expiry[$action] ) {
349 $this->show( [
'protect_expiry_invalid' ] );
353 $this->show( [
'protect_expiry_old' ] );
358 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade' );
360 $status = $this->mArticle->getPage()->doUpdateRestrictions(
361 $this->mRestrictions,
365 $this->mPerformer->getUser()
368 if ( !$status->isOK() ) {
369 $this->show( $this->mOut->parseInlineAsInterface(
370 $status->getWikiText(
false,
false, $this->mLang )
382 if ( !$this->hookRunner->onProtectionForm__save( $this->mArticle, $errorMsg, $reasonstr ) ) {
383 if ( $errorMsg ==
'' ) {
384 $errorMsg = [
'hookaborted' ];
387 if ( $errorMsg !=
'' ) {
388 $this->show( $errorMsg );
392 $this->watchlistManager->setWatch(
393 $this->mRequest->getCheck(
'mwProtectWatch' ),
406 private function buildForm() {
407 $this->mOut->enableOOUI();
410 if ( !$this->disabled ) {
411 $this->mOut->addModules(
'mediawiki.action.protect' );
412 $this->mOut->addModuleStyles(
'mediawiki.action.styles' );
414 $scExpiryOptions = $this->mContext->msg(
'protect-expiry-options' )->inContentLanguage()->text();
415 $levels = $this->permManager->getNamespaceRestrictionLevels(
416 $this->mTitle->getNamespace(),
417 $this->disabled ?
null : $this->mPerformer->
getUser()
421 foreach ( $this->mRestrictions as $action => $selected ) {
424 $section =
'restriction-' . $action;
425 $id =
'mwProtect-level-' . $action;
427 foreach ( $levels as $key ) {
428 $options[$this->getOptionLabel( $key )] = $key;
434 'default' => $selected,
436 'size' => count( $levels ),
437 'options' => $options,
439 'section' => $section,
444 if ( $this->mExistingExpiry[$action] ) {
445 if ( $this->mExistingExpiry[$action] ==
'infinity' ) {
446 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry-infinity' );
448 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry' )
449 ->dateTimeParams( $this->mExistingExpiry[$action] )
450 ->dateParams( $this->mExistingExpiry[$action] )
451 ->timeParams( $this->mExistingExpiry[$action] );
453 $expiryOptions[$existingExpiryMessage->text()] =
'existing';
456 $expiryOptions[$this->mContext->msg(
'protect-othertime-op' )->text()] =
'othertime';
458 $expiryOptions = array_merge( $expiryOptions, XmlSelect::parseOptionsMessage( $scExpiryOptions ) );
460 # Add expiry dropdown
461 $fields[
"wpProtectExpirySelection-$action"] = [
463 'name' =>
"wpProtectExpirySelection-$action",
464 'id' =>
"mwProtectExpirySelection-$action",
467 'label' => $this->mContext->msg(
'protectexpiry' )->text(),
468 'options' => $expiryOptions,
469 'default' => $this->mExpirySelection[$action],
470 'section' => $section,
473 # Add custom expiry field
474 if ( !$this->disabled ) {
475 $fields[
"mwProtect-expiry-$action"] = [
477 'label' => $this->mContext->msg(
'protect-othertime' )->text(),
478 'name' =>
"mwProtect-expiry-$action",
479 'id' =>
"mwProtect-$action-expires",
481 'default' => $this->mExpiry[$action],
483 'section' => $section,
488 # Give extensions a chance to add items to the form
490 $hookFormOptions = [];
492 $this->hookRunner->onProtectionForm__buildForm( $this->mArticle, $hookFormRaw );
493 $this->hookRunner->onProtectionFormAddFormFields( $this->mArticle, $hookFormOptions );
495 # Merge forms added from addFormFields
496 $fields = array_merge( $fields, $hookFormOptions );
498 # Add raw sections added in buildForm
499 if ( $hookFormRaw ) {
500 $fields[
'rawinfo'] = [
502 'default' => $hookFormRaw,
504 'section' =>
'restriction-blank'
508 # JavaScript will add another row with a value-chaining checkbox
509 if ( $this->mTitle->exists() ) {
510 $fields[
'mwProtect-cascade'] = [
512 'label' => $this->mContext->msg(
'protect-cascade' )->text(),
513 'id' =>
'mwProtect-cascade',
514 'name' =>
'mwProtect-cascade',
520 # Add manual and custom reason field/selects as well as submit
521 if ( !$this->disabled ) {
527 $maxlength = CommentStore::COMMENT_CHARACTER_LIMIT - 75;
528 $fields[
'wpProtectReasonSelection'] = [
530 'cssclass' =>
'mwProtect-reason',
531 'label' => $this->mContext->msg(
'protectcomment' )->text(),
533 'id' =>
'wpProtectReasonSelection',
534 'name' =>
'wpProtectReasonSelection',
536 'options' => Html::listDropdownOptions(
537 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->text(),
538 [
'other' => $this->mContext->msg(
'protect-otherreason-op' )->text() ]
542 $fields[
'mwProtect-reason'] = [
544 'id' =>
'mwProtect-reason',
545 'label' => $this->mContext->msg(
'protect-otherreason' )->text(),
546 'name' =>
'mwProtect-reason',
548 'maxlength' => $maxlength,
551 # Disallow watching if user is not logged in
552 if ( $this->mPerformer->getUser()->isRegistered() ) {
553 $fields[
'mwProtectWatch'] = [
555 'id' =>
'mwProtectWatch',
556 'label' => $this->mContext->msg(
'watchthis' )->text(),
557 'name' =>
'mwProtectWatch',
559 $this->watchlistManager->isWatched( $this->mPerformer, $this->mTitle )
561 $this->mPerformer->getUser(),
569 if ( $this->mPerformer->isAllowed(
'editinterface' ) ) {
571 $link = $linkRenderer->makeKnownLink(
572 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->getTitle(),
573 $this->mContext->msg(
'protect-edit-reasonlist' )->text(),
575 [
'action' =>
'edit' ]
577 $out .=
'<p class="mw-protect-editreasons">' . $link .
'</p>';
580 $htmlForm = HTMLForm::factory(
'ooui', $fields, $this->mContext );
582 ->setMethod(
'post' )
583 ->setId(
'mw-Protect-Form' )
584 ->setTableId(
'mw-protect-table2' )
585 ->setAction( $this->mTitle->getLocalURL(
'action=protect' ) )
586 ->setSubmitID(
'mw-Protect-submit' )
587 ->setSubmitTextMsg(
'confirm' )
588 ->setTokenSalt( [
'protect', $this->mTitle->getPrefixedDBkey() ] )
589 ->suppressDefaultSubmit( $this->disabled )
590 ->setWrapperLegendMsg(
'protect-legend' )
593 return $htmlForm->getHTML(
false ) . $out;
602 private function getOptionLabel( $permission ) {
603 if ( $permission ==
'' ) {
604 return $this->mContext->msg(
'protect-default' )->text();
607 $msg = $this->mContext->msg(
"protect-level-{$permission}" );
608 if ( $msg->exists() ) {
611 return $this->mContext->msg(
'protect-fallback', $permission )->text();
618 private function showLogExtract() {
619 # Show relevant lines from the protection log:
620 $protectLogPage =
new LogPage(
'protect' );
621 $this->mOut->addHTML(
Html::element(
'h2', [], $protectLogPage->getName()->text() ) );
623 LogEventsList::showLogExtract( $this->mOut,
'protect', $this->mTitle );
624 # Let extensions add other relevant log extracts
625 $this->hookRunner->onProtectionForm__showLogExtract( $this->mArticle, $this->mOut );