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 ) );
264 private function show( $err =
null ) {
266 $out->setRobotPolicy(
'noindex,nofollow' );
267 $out->addBacklinkSubtitle( $this->mTitle );
269 if ( is_array( $err ) ) {
271 } elseif ( is_string( $err ) ) {
275 if ( $this->mApplicableTypes === [] ) {
278 $out->setPageTitle( $this->mContext->msg(
279 'protect-norestrictiontypes-title',
280 $this->mTitle->getPrefixedText()
282 $out->addWikiTextAsInterface(
283 $this->mContext->msg(
'protect-norestrictiontypes-text' )->plain()
287 $this->showLogExtract();
292 list( $cascadeSources, ) =
293 $this->restrictionStore->getCascadeProtectionSources( $this->mTitle );
294 if ( count( $cascadeSources ) > 0 ) {
297 foreach ( $cascadeSources as $pageIdentity ) {
298 $titles .=
'* [[:' . $this->titleFormatter->getPrefixedText( $pageIdentity ) .
"]]\n";
303 "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles .
"</div>",
304 [
'protect-cascadeon', count( $cascadeSources ) ]
308 # Show an appropriate message if the user isn't allowed or able to change
309 # the protection settings at this time
310 if ( $this->disabled ) {
312 $this->mContext->msg(
'protect-title-notallowed',
313 $this->mTitle->getPrefixedText() )
315 $out->addWikiTextAsInterface(
316 $out->formatPermissionStatus( $this->mPermStatus,
'protect' )
320 $this->mContext->msg(
'protect-title', $this->mTitle->getPrefixedText() )
322 $out->addWikiMsg(
'protect-text',
326 $out->addHTML( $this->buildForm() );
327 $this->showLogExtract();
335 private function save() {
337 if ( $this->disabled ) {
342 $token = $this->mRequest->getVal(
'wpEditToken' );
343 $legacyUser = MediaWikiServices::getInstance()
345 ->newFromAuthority( $this->mPerformer );
346 if ( !$legacyUser->matchEditToken( $token, [
'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
347 $this->show( [
'sessionfailure' ] );
351 # Create reason string. Use list and/or custom string.
353 if ( $reasonstr !=
'other' && $this->mReason !=
'' ) {
355 $reasonstr .= $this->mContext->msg(
'colon-separator' )->text() .
$this->mReason;
356 } elseif ( $reasonstr ==
'other' ) {
361 foreach ( $this->mApplicableTypes as $action ) {
362 $expiry[$action] = $this->getExpiry( $action );
363 if ( empty( $this->mRestrictions[$action] ) ) {
367 if ( !$expiry[$action] ) {
368 $this->show( [
'protect_expiry_invalid' ] );
372 $this->show( [
'protect_expiry_old' ] );
377 $this->mCascade = $this->mRequest->getBool(
'mwProtect-cascade' );
379 $status = $this->mArticle->getPage()->doUpdateRestrictions(
380 $this->mRestrictions,
384 $this->mPerformer->getUser()
387 if ( !$status->isOK() ) {
388 $this->show( $this->mOut->parseInlineAsInterface(
389 $status->getWikiText(
false,
false, $this->mLang )
401 if ( !$this->hookRunner->onProtectionForm__save( $this->mArticle, $errorMsg, $reasonstr ) ) {
402 if ( $errorMsg ==
'' ) {
403 $errorMsg = [
'hookaborted' ];
406 if ( $errorMsg !=
'' ) {
407 $this->show( $errorMsg );
411 $this->watchlistManager->setWatch(
412 $this->mRequest->getCheck(
'mwProtectWatch' ),
425 private function buildForm() {
426 $this->mOut->enableOOUI();
429 if ( !$this->disabled ) {
430 $this->mOut->addModules(
'mediawiki.action.protect' );
431 $this->mOut->addModuleStyles(
'mediawiki.action.styles' );
433 $scExpiryOptions = $this->mContext->msg(
'protect-expiry-options' )->inContentLanguage()->text();
434 $levels = $this->permManager->getNamespaceRestrictionLevels(
435 $this->mTitle->getNamespace(),
436 $this->disabled ?
null : $this->mPerformer->getUser()
440 foreach ( $this->mRestrictions as $action => $selected ) {
443 $section =
'restriction-' . $action;
444 $id =
'mwProtect-level-' . $action;
446 foreach ( $levels as $key ) {
447 $options[$this->getOptionLabel( $key )] = $key;
453 'default' => $selected,
455 'size' => count( $levels ),
456 'options' => $options,
458 'section' => $section,
463 if ( $this->mExistingExpiry[$action] ) {
464 if ( $this->mExistingExpiry[$action] ==
'infinity' ) {
465 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry-infinity' );
467 $existingExpiryMessage = $this->mContext->msg(
'protect-existing-expiry' )
468 ->dateTimeParams( $this->mExistingExpiry[$action] )
469 ->dateParams( $this->mExistingExpiry[$action] )
470 ->timeParams( $this->mExistingExpiry[$action] );
472 $expiryOptions[$existingExpiryMessage->text()] =
'existing';
475 $expiryOptions[$this->mContext->msg(
'protect-othertime-op' )->text()] =
'othertime';
479 # Add expiry dropdown
480 $fields[
"wpProtectExpirySelection-$action"] = [
482 'name' =>
"wpProtectExpirySelection-$action",
483 'id' =>
"mwProtectExpirySelection-$action",
486 'label' => $this->mContext->msg(
'protectexpiry' )->text(),
487 'options' => $expiryOptions,
488 'default' => $this->mExpirySelection[$action],
489 'section' => $section,
492 # Add custom expiry field
493 if ( !$this->disabled ) {
494 $fields[
"mwProtect-expiry-$action"] = [
496 'label' => $this->mContext->msg(
'protect-othertime' )->text(),
497 'name' =>
"mwProtect-expiry-$action",
498 'id' =>
"mwProtect-$action-expires",
500 'default' => $this->mExpiry[$action],
502 'section' => $section,
507 # Give extensions a chance to add items to the form
509 $hookFormOptions = [];
511 $this->hookRunner->onProtectionForm__buildForm( $this->mArticle, $hookFormRaw );
512 $this->hookRunner->onProtectionFormAddFormFields( $this->mArticle, $hookFormOptions );
514 # Merge forms added from addFormFields
515 $fields = array_merge( $fields, $hookFormOptions );
517 # Add raw sections added in buildForm
518 if ( $hookFormRaw ) {
519 $fields[
'rawinfo'] = [
521 'default' => $hookFormRaw,
523 'section' =>
'restriction-blank'
527 # JavaScript will add another row with a value-chaining checkbox
528 if ( $this->mTitle->exists() ) {
529 $fields[
'mwProtect-cascade'] = [
531 'label' => $this->mContext->msg(
'protect-cascade' )->text(),
532 'id' =>
'mwProtect-cascade',
533 'name' =>
'mwProtect-cascade',
539 # Add manual and custom reason field/selects as well as submit
540 if ( !$this->disabled ) {
547 $fields[
'wpProtectReasonSelection'] = [
549 'cssclass' =>
'mwProtect-reason',
550 'label' => $this->mContext->msg(
'protectcomment' )->text(),
552 'id' =>
'wpProtectReasonSelection',
553 'name' =>
'wpProtectReasonSelection',
556 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->text(),
557 [
'other' => $this->mContext->msg(
'protect-otherreason-op' )->inContentLanguage()->text() ]
561 $fields[
'mwProtect-reason'] = [
563 'id' =>
'mwProtect-reason',
564 'label' => $this->mContext->msg(
'protect-otherreason' )->text(),
565 'name' =>
'mwProtect-reason',
567 'maxlength' => $maxlength,
570 # Disallow watching if user is not logged in
571 if ( $this->mPerformer->getUser()->isRegistered() ) {
572 $fields[
'mwProtectWatch'] = [
574 'id' =>
'mwProtectWatch',
575 'label' => $this->mContext->msg(
'watchthis' )->text(),
576 'name' =>
'mwProtectWatch',
578 $this->watchlistManager->isWatched( $this->mPerformer, $this->mTitle )
579 || MediaWikiServices::getInstance()->getUserOptionsLookup()->getOption(
580 $this->mPerformer->getUser(),
588 if ( $this->mPerformer->isAllowed(
'editinterface' ) ) {
589 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
590 $link = $linkRenderer->makeKnownLink(
591 $this->mContext->msg(
'protect-dropdown' )->inContentLanguage()->getTitle(),
592 $this->mContext->msg(
'protect-edit-reasonlist' )->text(),
594 [
'action' =>
'edit' ]
596 $out .=
'<p class="mw-protect-editreasons">' . $link .
'</p>';
601 ->setMethod(
'post' )
602 ->setId(
'mw-Protect-Form' )
603 ->setTableId(
'mw-protect-table2' )
604 ->setAction( $this->mTitle->getLocalURL(
'action=protect' ) )
605 ->setSubmitID(
'mw-Protect-submit' )
606 ->setSubmitTextMsg(
'confirm' )
607 ->setTokenSalt( [
'protect', $this->mTitle->getPrefixedDBkey() ] )
608 ->suppressDefaultSubmit( $this->disabled )
609 ->setWrapperLegendMsg(
'protect-legend' )
612 return $htmlForm->getHTML(
false ) . $out;
621 private function getOptionLabel( $permission ) {
622 if ( $permission ==
'' ) {
623 return $this->mContext->msg(
'protect-default' )->text();
626 $msg = $this->mContext->msg(
"protect-level-{$permission}" );
627 if ( $msg->exists() ) {
630 return $this->mContext->msg(
'protect-fallback', $permission )->text();
637 private function showLogExtract() {
638 # Show relevant lines from the protection log:
639 $protectLogPage =
new LogPage(
'protect' );
640 $this->mOut->addHTML(
Xml::element(
'h2',
null, $protectLogPage->getName()->text() ) );
643 # Let extensions add other relevant log extracts
644 $this->hookRunner->onProtectionForm__showLogExtract( $this->mArticle, $this->mOut );
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.
static errorBox( $html, $heading='', $className='')
Return an error box.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
static parseOptionsMessage(string $msg)
Parse labels and values out of a comma- and colon-separated list of options, such as is used for expi...
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.