MediaWiki fundraising/REL1_35
SpecialUndelete.php
Go to the documentation of this file.
1<?php
30
38 private $mAction;
39 private $mTarget;
40 private $mTimestamp;
41 private $mRestore;
42 private $mRevdel;
43 private $mInvert;
44 private $mFilename;
46 private $mAllowed;
47 private $mCanView;
48 private $mComment;
49 private $mToken;
51 private $mPreview;
53 private $mDiff;
55 private $mDiffOnly;
57 private $mUnsuppress;
60
62 private $mTargetObj;
67
68 public function __construct() {
69 parent::__construct( 'Undelete', 'deletedhistory' );
70 }
71
72 public function doesWrites() {
73 return true;
74 }
75
76 private function loadRequest( $par ) {
77 $request = $this->getRequest();
78 $user = $this->getUser();
79
80 $this->mAction = $request->getVal( 'action' );
81 if ( $par !== null && $par !== '' ) {
82 $this->mTarget = $par;
83 } else {
84 $this->mTarget = $request->getVal( 'target' );
85 }
86
87 $this->mTargetObj = null;
88
89 if ( $this->mTarget !== null && $this->mTarget !== '' ) {
90 $this->mTargetObj = Title::newFromText( $this->mTarget );
91 }
92
93 $this->mSearchPrefix = $request->getText( 'prefix' );
94 $time = $request->getVal( 'timestamp' );
95 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
96 $this->mFilename = $request->getVal( 'file' );
97
98 $posted = $request->wasPosted() &&
99 $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
100 $this->mRestore = $request->getCheck( 'restore' ) && $posted;
101 $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
102 $this->mInvert = $request->getCheck( 'invert' ) && $posted;
103 $this->mPreview = $request->getCheck( 'preview' ) && $posted;
104 $this->mDiff = $request->getCheck( 'diff' );
105 $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
106 $this->mComment = $request->getText( 'wpComment' );
107 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && MediaWikiServices::getInstance()
108 ->getPermissionManager()
109 ->userHasRight( $user, 'suppressrevision' );
110 $this->mToken = $request->getVal( 'token' );
111
112 if ( $this->isAllowed( 'undelete' ) ) {
113 $this->mAllowed = true; // user can restore
114 $this->mCanView = true; // user can view content
115 } elseif ( $this->isAllowed( 'deletedtext' ) ) {
116 $this->mAllowed = false; // user cannot restore
117 $this->mCanView = true; // user can view content
118 $this->mRestore = false;
119 } else { // user can only view the list of revisions
120 $this->mAllowed = false;
121 $this->mCanView = false;
122 $this->mTimestamp = '';
123 $this->mRestore = false;
124 }
125
126 if ( $this->mRestore || $this->mInvert ) {
127 $timestamps = [];
128 $this->mFileVersions = [];
129 foreach ( $request->getValues() as $key => $val ) {
130 $matches = [];
131 if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
132 array_push( $timestamps, $matches[1] );
133 }
134
135 if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
136 $this->mFileVersions[] = intval( $matches[1] );
137 }
138 }
139 rsort( $timestamps );
140 $this->mTargetTimestamp = $timestamps;
141 }
142 }
143
152 protected function isAllowed( $permission, User $user = null ) {
153 $user = $user ?: $this->getUser();
154 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
155 $block = $user->getBlock();
156
157 if ( $this->mTargetObj !== null ) {
158 return $permissionManager->userCan( $permission, $user, $this->mTargetObj );
159 } else {
160 $hasRight = $permissionManager->userHasRight( $user, $permission );
161 $sitewideBlock = $block && $block->isSitewide();
162 return $permission === 'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
163 }
164 }
165
166 public function userCanExecute( User $user ) {
167 return $this->isAllowed( $this->mRestriction, $user );
168 }
169
173 public function checkPermissions() {
174 $user = $this->getUser();
175
176 // First check if user has the right to use this page. If not,
177 // show a permissions error whether they are blocked or not.
178 if ( !parent::userCanExecute( $user ) ) {
180 }
181
182 // If a user has the right to use this page, but is blocked from
183 // the target, show a block error.
184 if (
185 $this->mTargetObj && MediaWikiServices::getInstance()
187 ->isBlockedFrom( $user, $this->mTargetObj )
188 ) {
189 throw new UserBlockedError( $user->getBlock() );
190 }
191
192 // Finally, do the comprehensive permission check via isAllowed.
193 if ( !$this->userCanExecute( $user ) ) {
195 }
196 }
197
198 public function execute( $par ) {
200
201 $user = $this->getUser();
202
203 $this->setHeaders();
204 $this->outputHeader();
205 $this->addHelpLink( 'Help:Deletion_and_undeletion' );
206
207 $this->loadRequest( $par );
208 $this->checkPermissions(); // Needs to be after mTargetObj is set
209
210 $out = $this->getOutput();
211
212 if ( $this->mTargetObj === null ) {
213 $out->addWikiMsg( 'undelete-header' );
214
215 # Not all users can just browse every deleted page from the list
216 if ( MediaWikiServices::getInstance()
218 ->userHasRight( $user, 'browsearchive' )
219 ) {
220 $this->showSearchForm();
221 }
222
223 return;
224 }
225
226 $this->addHelpLink( 'Help:Undelete' );
227 if ( $this->mAllowed ) {
228 $out->setPageTitle( $this->msg( 'undeletepage' ) );
229 } else {
230 $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
231 }
232
233 $this->getSkin()->setRelevantTitle( $this->mTargetObj );
234
235 if ( $this->mTimestamp !== '' ) {
236 $this->showRevision( $this->mTimestamp );
237 } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
238 $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
239 // Check if user is allowed to see this file
240 if ( !$file->exists() ) {
241 $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
242 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
243 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
244 throw new PermissionsError( 'suppressrevision' );
245 } else {
246 throw new PermissionsError( 'deletedtext' );
247 }
248 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
249 $this->showFileConfirmationForm( $this->mFilename );
250 } else {
251 $this->showFile( $this->mFilename );
252 }
253 } elseif ( $this->mAction === 'submit' ) {
254 if ( $this->mRestore ) {
255 $this->undelete();
256 } elseif ( $this->mRevdel ) {
257 $this->redirectToRevDel();
258 }
259
260 } else {
261 $this->showHistory();
262 }
263 }
264
269 private function redirectToRevDel() {
270 $archive = new PageArchive( $this->mTargetObj );
271
272 $revisions = [];
273
274 foreach ( $this->getRequest()->getValues() as $key => $val ) {
275 $matches = [];
276 if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
277 $revisionRecord = $archive->getRevisionRecordByTimestamp( $matches[1] );
278 if ( $revisionRecord ) {
279 // Can return null
280 $revisions[ $revisionRecord->getId() ] = 1;
281 }
282 }
283 }
284
285 $query = [
286 'type' => 'revision',
287 'ids' => $revisions,
288 'target' => $this->mTargetObj->getPrefixedText()
289 ];
290 $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
291 $this->getOutput()->redirect( $url );
292 }
293
294 private function showSearchForm() {
295 $out = $this->getOutput();
296 $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
297 $fuzzySearch = $this->getRequest()->getVal( 'fuzzy', true );
298
299 $out->enableOOUI();
300
301 $fields = [];
302 $fields[] = new OOUI\ActionFieldLayout(
303 new OOUI\TextInputWidget( [
304 'name' => 'prefix',
305 'inputId' => 'prefix',
306 'infusable' => true,
307 'value' => $this->mSearchPrefix,
308 'autofocus' => true,
309 ] ),
310 new OOUI\ButtonInputWidget( [
311 'label' => $this->msg( 'undelete-search-submit' )->text(),
312 'flags' => [ 'primary', 'progressive' ],
313 'inputId' => 'searchUndelete',
314 'type' => 'submit',
315 ] ),
316 [
317 'label' => new OOUI\HtmlSnippet(
318 $this->msg(
319 $fuzzySearch ? 'undelete-search-full' : 'undelete-search-prefix'
320 )->parse()
321 ),
322 'align' => 'left',
323 ]
324 );
325
326 $fieldset = new OOUI\FieldsetLayout( [
327 'label' => $this->msg( 'undelete-search-box' )->text(),
328 'items' => $fields,
329 ] );
330
331 $form = new OOUI\FormLayout( [
332 'method' => 'get',
333 'action' => wfScript(),
334 ] );
335
336 $form->appendContent(
337 $fieldset,
338 new OOUI\HtmlSnippet(
339 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
340 Html::hidden( 'fuzzy', $fuzzySearch )
341 )
342 );
343
344 $out->addHTML(
345 new OOUI\PanelLayout( [
346 'expanded' => false,
347 'padded' => true,
348 'framed' => true,
349 'content' => $form,
350 ] )
351 );
352
353 # List undeletable articles
354 if ( $this->mSearchPrefix ) {
355 // For now, we enable search engine match only when specifically asked to
356 // by using fuzzy=1 parameter.
357 if ( $fuzzySearch ) {
358 $result = PageArchive::listPagesBySearch( $this->mSearchPrefix );
359 } else {
360 $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
361 }
362 $this->showList( $result );
363 }
364 }
365
372 private function showList( $result ) {
373 $out = $this->getOutput();
374
375 if ( $result->numRows() == 0 ) {
376 $out->addWikiMsg( 'undelete-no-results' );
377
378 return false;
379 }
380
381 $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
382
384 $undelete = $this->getPageTitle();
385 $out->addHTML( "<ul id='undeleteResultsList'>\n" );
386 foreach ( $result as $row ) {
387 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
388 if ( $title !== null ) {
389 $item = $linkRenderer->makeKnownLink(
390 $undelete,
391 $title->getPrefixedText(),
392 [],
393 [ 'target' => $title->getPrefixedText() ]
394 );
395 } else {
396 // The title is no longer valid, show as text
397 $item = Html::element(
398 'span',
399 [ 'class' => 'mw-invalidtitle' ],
401 $this->getContext(),
402 $row->ar_namespace,
403 $row->ar_title
404 )
405 );
406 }
407 $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
408 $out->addHTML(
409 Html::rawElement(
410 'li',
411 [ 'class' => 'undeleteResult' ],
412 "{$item} ({$revs})"
413 )
414 );
415 }
416 $result->free();
417 $out->addHTML( "</ul>\n" );
418
419 return true;
420 }
421
422 private function showRevision( $timestamp ) {
423 if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
424 return;
425 }
426
427 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
428 if ( !$this->getHookRunner()->onUndeleteForm__showRevision(
429 $archive, $this->mTargetObj )
430 ) {
431 return;
432 }
433 $revRecord = $archive->getRevisionRecordByTimestamp( $timestamp );
434
435 $out = $this->getOutput();
436 $user = $this->getUser();
437
438 if ( !$revRecord ) {
439 $out->addWikiMsg( 'undeleterevision-missing' );
440
441 return;
442 }
443
444 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
445 if ( !RevisionRecord::userCanBitfield(
446 $revRecord->getVisibility(),
447 RevisionRecord::DELETED_TEXT,
448 $user
449 ) ) {
450 $out->wrapWikiMsg(
451 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
452 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
453 'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
454 );
455
456 return;
457 }
458
459 $out->wrapWikiMsg(
460 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
461 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
462 'rev-suppressed-text-view' : 'rev-deleted-text-view'
463 );
464 $out->addHTML( '<br />' );
465 // and we are allowed to see...
466 }
467
468 if ( $this->mDiff ) {
469 $previousRevRecord = $archive->getPreviousRevisionRecord( $timestamp );
470 if ( $previousRevRecord ) {
471 $this->showDiff( $previousRevRecord, $revRecord );
472 if ( $this->mDiffOnly ) {
473 return;
474 }
475
476 $out->addHTML( '<hr />' );
477 } else {
478 $out->addWikiMsg( 'undelete-nodiff' );
479 }
480 }
481
482 $link = $this->getLinkRenderer()->makeKnownLink(
483 $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
484 $this->mTargetObj->getPrefixedText()
485 );
486
487 $lang = $this->getLanguage();
488
489 // date and time are separate parameters to facilitate localisation.
490 // $time is kept for backward compat reasons.
491 $time = $lang->userTimeAndDate( $timestamp, $user );
492 $d = $lang->userDate( $timestamp, $user );
493 $t = $lang->userTime( $timestamp, $user );
494 $userLink = Linker::revUserTools( $revRecord );
495
496 $content = $revRecord->getContent(
497 SlotRecord::MAIN,
498 RevisionRecord::FOR_THIS_USER,
499 $user
500 );
501
502 // TODO: MCR: this will have to become something like $hasTextSlots and $hasNonTextSlots
503 $isText = ( $content instanceof TextContent );
504
505 if ( $this->mPreview || $isText ) {
506 $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
507 } else {
508 $openDiv = '<div id="mw-undelete-revision">';
509 }
510 $out->addHTML( $openDiv );
511
512 // Revision delete links
513 if ( !$this->mDiff ) {
514 $revdel = Linker::getRevDeleteLink(
515 $user,
516 $revRecord,
517 $this->mTargetObj
518 );
519 if ( $revdel ) {
520 $out->addHTML( "$revdel " );
521 }
522 }
523
524 $out->addWikiMsg(
525 'undelete-revision',
526 Message::rawParam( $link ), $time,
527 Message::rawParam( $userLink ), $d, $t
528 );
529 $out->addHTML( '</div>' );
530
531 // Hook hard deprecated since 1.35
532 if ( $this->getHookContainer()->isRegistered( 'UndeleteShowRevision' ) ) {
533 // Only create the Revision object if needed
534 $rev = new Revision( $revRecord );
535 if ( !$this->getHookRunner()->onUndeleteShowRevision(
536 $this->mTargetObj,
537 $rev
538 ) ) {
539 return;
540 }
541 }
542
543 if ( $this->mPreview || !$isText ) {
544 // NOTE: non-text content has no source view, so always use rendered preview
545
546 $popts = $out->parserOptions();
547 $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
548
549 $rendered = $renderer->getRenderedRevision(
550 $revRecord,
551 $popts,
552 $user,
553 [ 'audience' => RevisionRecord::FOR_THIS_USER ]
554 );
555
556 // Fail hard if the audience check fails, since we already checked
557 // at the beginning of this method.
558 $pout = $rendered->getRevisionParserOutput();
559
560 $out->addParserOutput( $pout, [
561 'enableSectionEditLinks' => false,
562 ] );
563 }
564
565 $out->enableOOUI();
566 $buttonFields = [];
567
568 if ( $isText ) {
569 '@phan-var TextContent $content';
570 // TODO: MCR: make this work for multiple slots
571 // source view for textual content
572 $sourceView = Xml::element( 'textarea', [
573 'readonly' => 'readonly',
574 'cols' => 80,
575 'rows' => 25
576 ], $content->getText() . "\n" );
577
578 $buttonFields[] = new OOUI\ButtonInputWidget( [
579 'type' => 'submit',
580 'name' => 'preview',
581 'label' => $this->msg( 'showpreview' )->text()
582 ] );
583 } else {
584 $sourceView = '';
585 }
586
587 $buttonFields[] = new OOUI\ButtonInputWidget( [
588 'name' => 'diff',
589 'type' => 'submit',
590 'label' => $this->msg( 'showdiff' )->text()
591 ] );
592
593 $out->addHTML(
594 $sourceView .
595 Xml::openElement( 'div', [
596 'style' => 'clear: both' ] ) .
597 Xml::openElement( 'form', [
598 'method' => 'post',
599 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
600 Xml::element( 'input', [
601 'type' => 'hidden',
602 'name' => 'target',
603 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
604 Xml::element( 'input', [
605 'type' => 'hidden',
606 'name' => 'timestamp',
607 'value' => $timestamp ] ) .
608 Xml::element( 'input', [
609 'type' => 'hidden',
610 'name' => 'wpEditToken',
611 'value' => $user->getEditToken() ] ) .
612 new OOUI\FieldLayout(
613 new OOUI\Widget( [
614 'content' => new OOUI\HorizontalLayout( [
615 'items' => $buttonFields
616 ] )
617 ] )
618 ) .
619 Xml::closeElement( 'form' ) .
620 Xml::closeElement( 'div' )
621 );
622 }
623
631 private function showDiff(
632 RevisionRecord $previousRevRecord,
633 RevisionRecord $currentRevRecord
634 ) {
635 $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
636
637 $diffContext = clone $this->getContext();
638 $diffContext->setTitle( $currentTitle );
639 $diffContext->setWikiPage( WikiPage::factory( $currentTitle ) );
640
641 $contentModel = $currentRevRecord->getSlot(
642 SlotRecord::MAIN,
643 RevisionRecord::RAW
644 )->getModel();
645
646 $diffEngine = MediaWikiServices::getInstance()
647 ->getContentHandlerFactory()
648 ->getContentHandler( $contentModel )
649 ->createDifferenceEngine( $diffContext );
650
651 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
652 $diffEngine->showDiffStyle();
653 $formattedDiff = $diffEngine->getDiff(
654 $this->diffHeader( $previousRevRecord, 'o' ),
655 $this->diffHeader( $currentRevRecord, 'n' )
656 );
657
658 $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
659 }
660
666 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
667 $isDeleted = !( $revRecord->getId() && $revRecord->getPageAsLinkTarget() );
668 if ( $isDeleted ) {
670 $targetPage = $this->getPageTitle();
671 $targetQuery = [
672 'target' => $this->mTargetObj->getPrefixedText(),
673 'timestamp' => wfTimestamp( TS_MW, $revRecord->getTimestamp() )
674 ];
675 } else {
677 $targetPage = $revRecord->getPageAsLinkTarget();
678 $targetQuery = [ 'oldid' => $revRecord->getId() ];
679 }
680
681 // Add show/hide deletion links if available
682 $user = $this->getUser();
683 $lang = $this->getLanguage();
684 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
685
686 if ( $rdel ) {
687 $rdel = " $rdel";
688 }
689
690 $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
691
692 $tagIds = wfGetDB( DB_REPLICA )->selectFieldValues(
693 'change_tag',
694 'ct_tag_id',
695 [ 'ct_rev_id' => $revRecord->getId() ],
696 __METHOD__
697 );
698 $tags = [];
699 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
700 foreach ( $tagIds as $tagId ) {
701 try {
702 $tags[] = $changeTagDefStore->getName( (int)$tagId );
703 } catch ( NameTableAccessException $exception ) {
704 continue;
705 }
706 }
707 $tags = implode( ',', $tags );
708 $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
709
710 // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
711 // and partially #showDiffPage, but worse
712 return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
713 $this->getLinkRenderer()->makeLink(
714 $targetPage,
715 $this->msg(
716 'revisionasof',
717 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
718 $lang->userDate( $revRecord->getTimestamp(), $user ),
719 $lang->userTime( $revRecord->getTimestamp(), $user )
720 )->text(),
721 [],
722 $targetQuery
723 ) .
724 '</strong></div>' .
725 '<div id="mw-diff-' . $prefix . 'title2">' .
726 Linker::revUserTools( $revRecord ) . '<br />' .
727 '</div>' .
728 '<div id="mw-diff-' . $prefix . 'title3">' .
729 $minor . Linker::revComment( $revRecord ) . $rdel . '<br />' .
730 '</div>' .
731 '<div id="mw-diff-' . $prefix . 'title5">' .
732 $tagSummary[0] . '<br />' .
733 '</div>';
734 }
735
740 private function showFileConfirmationForm( $key ) {
741 $out = $this->getOutput();
742 $lang = $this->getLanguage();
743 $user = $this->getUser();
744 $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
745 $out->addWikiMsg( 'undelete-show-file-confirm',
746 $this->mTargetObj->getText(),
747 $lang->userDate( $file->getTimestamp(), $user ),
748 $lang->userTime( $file->getTimestamp(), $user ) );
749 $out->addHTML(
750 Xml::openElement( 'form', [
751 'method' => 'POST',
752 'action' => $this->getPageTitle()->getLocalURL( [
753 'target' => $this->mTarget,
754 'file' => $key,
755 'token' => $user->getEditToken( $key ),
756 ] ),
757 ]
758 ) .
759 Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
760 '</form>'
761 );
762 }
763
768 private function showFile( $key ) {
769 $this->getOutput()->disable();
770
771 # We mustn't allow the output to be CDN cached, otherwise
772 # if an admin previews a deleted image, and it's cached, then
773 # a user without appropriate permissions can toddle off and
774 # nab the image, and CDN will serve it
775 $response = $this->getRequest()->response();
776 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
777 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
778 $response->header( 'Pragma: no-cache' );
779
780 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
781 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
782 $repo->streamFileWithStatus( $path );
783 }
784
785 protected function showHistory() {
786 $this->checkReadOnly();
787
788 $out = $this->getOutput();
789 if ( $this->mAllowed ) {
790 $out->addModules( 'mediawiki.special.undelete' );
791 }
792 $out->wrapWikiMsg(
793 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
794 [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
795 );
796
797 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
798 $this->getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
799
800 $out->addHTML( '<div class="mw-undelete-history">' );
801 if ( $this->mAllowed ) {
802 $out->addWikiMsg( 'undeletehistory' );
803 $out->addWikiMsg( 'undeleterevdel' );
804 } else {
805 $out->addWikiMsg( 'undeletehistorynoadmin' );
806 }
807 $out->addHTML( '</div>' );
808
809 # List all stored revisions
810 $revisions = $archive->listRevisions();
811 $files = $archive->listFiles();
812
813 $haveRevisions = $revisions && $revisions->numRows() > 0;
814 $haveFiles = $files && $files->numRows() > 0;
815
816 # Batch existence check on user and talk pages
817 if ( $haveRevisions ) {
818 $batch = new LinkBatch();
819 foreach ( $revisions as $row ) {
820 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
821 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
822 }
823 $batch->execute();
824 $revisions->seek( 0 );
825 }
826 if ( $haveFiles ) {
827 $batch = new LinkBatch();
828 foreach ( $files as $row ) {
829 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
830 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
831 }
832 $batch->execute();
833 $files->seek( 0 );
834 }
835
836 if ( $this->mAllowed ) {
837 $out->enableOOUI();
838
839 $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
840 # Start the form here
841 $form = new OOUI\FormLayout( [
842 'method' => 'post',
843 'action' => $action,
844 'id' => 'undelete',
845 ] );
846 }
847
848 # Show relevant lines from the deletion log:
849 $deleteLogPage = new LogPage( 'delete' );
850 $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
851 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
852 # Show relevant lines from the suppression log:
853 $suppressLogPage = new LogPage( 'suppress' );
854 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
855 if ( $permissionManager->userHasRight( $this->getUser(), 'suppressionlog' ) ) {
856 $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
857 LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
858 }
859
860 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
861 $fields = [];
862 $fields[] = new OOUI\Layout( [
863 'content' => new OOUI\HtmlSnippet( $this->msg( 'undeleteextrahelp' )->parseAsBlock() )
864 ] );
865
866 $fields[] = new OOUI\FieldLayout(
867 new OOUI\TextInputWidget( [
868 'name' => 'wpComment',
869 'inputId' => 'wpComment',
870 'infusable' => true,
871 'value' => $this->mComment,
872 'autofocus' => true,
873 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
874 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
875 // Unicode codepoints.
876 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
877 ] ),
878 [
879 'label' => $this->msg( 'undeletecomment' )->text(),
880 'align' => 'top',
881 ]
882 );
883
884 $fields[] = new OOUI\FieldLayout(
885 new OOUI\Widget( [
886 'content' => new OOUI\HorizontalLayout( [
887 'items' => [
888 new OOUI\ButtonInputWidget( [
889 'name' => 'restore',
890 'inputId' => 'mw-undelete-submit',
891 'value' => '1',
892 'label' => $this->msg( 'undeletebtn' )->text(),
893 'flags' => [ 'primary', 'progressive' ],
894 'type' => 'submit',
895 ] ),
896 new OOUI\ButtonInputWidget( [
897 'name' => 'invert',
898 'inputId' => 'mw-undelete-invert',
899 'value' => '1',
900 'label' => $this->msg( 'undeleteinvert' )->text()
901 ] ),
902 ]
903 ] )
904 ] )
905 );
906
907 if ( $permissionManager->userHasRight( $this->getUser(), 'suppressrevision' ) ) {
908 $fields[] = new OOUI\FieldLayout(
909 new OOUI\CheckboxInputWidget( [
910 'name' => 'wpUnsuppress',
911 'inputId' => 'mw-undelete-unsuppress',
912 'value' => '1',
913 ] ),
914 [
915 'label' => $this->msg( 'revdelete-unsuppress' )->text(),
916 'align' => 'inline',
917 ]
918 );
919 }
920
921 $fieldset = new OOUI\FieldsetLayout( [
922 'label' => $this->msg( 'undelete-fieldset-title' )->text(),
923 'id' => 'mw-undelete-table',
924 'items' => $fields,
925 ] );
926
927 $form->appendContent(
928 new OOUI\PanelLayout( [
929 'expanded' => false,
930 'padded' => true,
931 'framed' => true,
932 'content' => $fieldset,
933 ] ),
934 new OOUI\HtmlSnippet(
935 Html::hidden( 'target', $this->mTarget ) .
936 Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
937 )
938 );
939 }
940
941 $history = '';
942 $history .= Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n";
943
944 if ( $haveRevisions ) {
945 # Show the page's stored (deleted) history
946
947 if ( $permissionManager->userHasRight( $this->getUser(), 'deleterevision' ) ) {
948 $history .= Html::element(
949 'button',
950 [
951 'name' => 'revdel',
952 'type' => 'submit',
953 'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
954 ],
955 $this->msg( 'showhideselectedversions' )->text()
956 ) . "\n";
957 }
958
959 $history .= '<ul class="mw-undelete-revlist">';
960 $remaining = $revisions->numRows();
961 $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
962
963 foreach ( $revisions as $row ) {
964 $remaining--;
965 $history .= $this->formatRevisionRow( $row, $earliestLiveTime, $remaining );
966 }
967 $revisions->free();
968 $history .= '</ul>';
969 } else {
970 $out->addWikiMsg( 'nohistory' );
971 }
972
973 if ( $haveFiles ) {
974 $history .= Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n";
975 $history .= '<ul class="mw-undelete-revlist">';
976 foreach ( $files as $row ) {
977 $history .= $this->formatFileRow( $row );
978 }
979 $files->free();
980 $history .= '</ul>';
981 }
982
983 if ( $this->mAllowed ) {
984 # Slip in the hidden controls here
985 $misc = Html::hidden( 'target', $this->mTarget );
986 $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
987 $history .= $misc;
988
989 $form->appendContent( new OOUI\HtmlSnippet( $history ) );
990 $out->addHTML( $form );
991 } else {
992 $out->addHTML( $history );
993 }
994
995 return true;
996 }
997
998 protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
999 $revRecord = MediaWikiServices::getInstance()
1000 ->getRevisionFactory()
1001 ->newRevisionFromArchiveRow(
1002 $row,
1003 RevisionFactory::READ_NORMAL,
1004 $this->mTargetObj
1005 );
1006
1007 $revTextSize = '';
1008 $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1009 // Build checkboxen...
1010 if ( $this->mAllowed ) {
1011 if ( $this->mInvert ) {
1012 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1013 $checkBox = Xml::check( "ts$ts" );
1014 } else {
1015 $checkBox = Xml::check( "ts$ts", true );
1016 }
1017 } else {
1018 $checkBox = Xml::check( "ts$ts" );
1019 }
1020 } else {
1021 $checkBox = '';
1022 }
1023
1024 // Build page & diff links...
1025 $user = $this->getUser();
1026 if ( $this->mCanView ) {
1027 $titleObj = $this->getPageTitle();
1028 # Last link
1029 if ( !RevisionRecord::userCanBitfield(
1030 $revRecord->getVisibility(),
1031 RevisionRecord::DELETED_TEXT,
1032 $this->getUser()
1033 ) ) {
1034 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1035 $last = $this->msg( 'diff' )->escaped();
1036 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1037 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1038 $last = $this->getLinkRenderer()->makeKnownLink(
1039 $titleObj,
1040 $this->msg( 'diff' )->text(),
1041 [],
1042 [
1043 'target' => $this->mTargetObj->getPrefixedText(),
1044 'timestamp' => $ts,
1045 'diff' => 'prev'
1046 ]
1047 );
1048 } else {
1049 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1050 $last = $this->msg( 'diff' )->escaped();
1051 }
1052 } else {
1053 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1054 $last = $this->msg( 'diff' )->escaped();
1055 }
1056
1057 // User links
1058 $userLink = Linker::revUserTools( $revRecord );
1059
1060 // Minor edit
1061 $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
1062
1063 // Revision text size
1064 $size = $row->ar_len;
1065 if ( $size !== null ) {
1066 $revTextSize = Linker::formatRevisionSize( $size );
1067 }
1068
1069 // Edit summary
1070 $comment = Linker::revComment( $revRecord );
1071
1072 // Tags
1073 $attribs = [];
1074 list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
1075 $row->ts_tags,
1076 'deletedhistory',
1077 $this->getContext()
1078 );
1079 if ( $classes ) {
1080 $attribs['class'] = implode( ' ', $classes );
1081 }
1082
1083 $revisionRow = $this->msg( 'undelete-revision-row2' )
1084 ->rawParams(
1085 $checkBox,
1086 $last,
1087 $pageLink,
1088 $userLink,
1089 $minor,
1090 $revTextSize,
1091 $comment,
1092 $tagSummary
1093 )
1094 ->escaped();
1095
1096 return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1097 }
1098
1099 private function formatFileRow( $row ) {
1101 $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1102 $user = $this->getUser();
1103
1104 $checkBox = '';
1105 if ( $this->mCanView && $row->fa_storage_key ) {
1106 if ( $this->mAllowed ) {
1107 $checkBox = Xml::check( 'fileid' . $row->fa_id );
1108 }
1109 $key = urlencode( $row->fa_storage_key );
1110 $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1111 } else {
1112 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1113 }
1114 $userLink = $this->getFileUser( $file );
1115 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1116 $bytes = $this->msg( 'parentheses' )
1117 ->plaintextParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1118 ->plain();
1119 $data = htmlspecialchars( $data . ' ' . $bytes );
1120 $comment = $this->getFileComment( $file );
1121
1122 // Add show/hide deletion links if available
1123 $canHide = $this->isAllowed( 'deleterevision' );
1124 if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1125 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1126 // Revision was hidden from sysops
1127 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1128 } else {
1129 $query = [
1130 'type' => 'filearchive',
1131 'target' => $this->mTargetObj->getPrefixedDBkey(),
1132 'ids' => $row->fa_id
1133 ];
1134 $revdlink = Linker::revDeleteLink( $query,
1135 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1136 }
1137 } else {
1138 $revdlink = '';
1139 }
1140
1141 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1142 }
1143
1152 private function getPageLink( RevisionRecord $revRecord, $titleObj, $ts ) {
1153 $user = $this->getUser();
1154 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1155
1156 if ( !RevisionRecord::userCanBitfield(
1157 $revRecord->getVisibility(),
1158 RevisionRecord::DELETED_TEXT,
1159 $user
1160 ) ) {
1161 return '<span class="history-deleted">' . $time . '</span>';
1162 }
1163
1164 $link = $this->getLinkRenderer()->makeKnownLink(
1165 $titleObj,
1166 $time,
1167 [],
1168 [
1169 'target' => $this->mTargetObj->getPrefixedText(),
1170 'timestamp' => $ts
1171 ]
1172 );
1173
1174 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1175 $link = '<span class="history-deleted">' . $link . '</span>';
1176 }
1177
1178 return $link;
1179 }
1180
1191 private function getFileLink( $file, $titleObj, $ts, $key ) {
1192 $user = $this->getUser();
1193 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1194
1195 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1196 return '<span class="history-deleted">' . htmlspecialchars( $time ) . '</span>';
1197 }
1198
1199 $link = $this->getLinkRenderer()->makeKnownLink(
1200 $titleObj,
1201 $time,
1202 [],
1203 [
1204 'target' => $this->mTargetObj->getPrefixedText(),
1205 'file' => $key,
1206 'token' => $user->getEditToken( $key )
1207 ]
1208 );
1209
1210 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1211 $link = '<span class="history-deleted">' . $link . '</span>';
1212 }
1213
1214 return $link;
1215 }
1216
1223 private function getFileUser( $file ) {
1224 if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
1225 return '<span class="history-deleted">' .
1226 $this->msg( 'rev-deleted-user' )->escaped() .
1227 '</span>';
1228 }
1229
1230 $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
1231 Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1232
1233 if ( $file->isDeleted( File::DELETED_USER ) ) {
1234 $link = '<span class="history-deleted">' . $link . '</span>';
1235 }
1236
1237 return $link;
1238 }
1239
1246 private function getFileComment( $file ) {
1247 if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
1248 return '<span class="history-deleted"><span class="comment">' .
1249 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
1250 }
1251
1252 $link = Linker::commentBlock( $file->getRawDescription() );
1253
1254 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1255 $link = '<span class="history-deleted">' . $link . '</span>';
1256 }
1257
1258 return $link;
1259 }
1260
1261 private function undelete() {
1262 if ( $this->getConfig()->get( 'UploadMaintenance' )
1263 && $this->mTargetObj->getNamespace() == NS_FILE
1264 ) {
1265 throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1266 }
1267
1268 $this->checkReadOnly();
1269
1270 $out = $this->getOutput();
1271 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1272 $this->getHookRunner()->onUndeleteForm__undelete( $archive, $this->mTargetObj );
1273 $ok = $archive->undeleteAsUser(
1274 $this->mTargetTimestamp,
1275 $this->getUser(),
1276 $this->mComment,
1277 $this->mFileVersions,
1278 $this->mUnsuppress
1279 );
1280
1281 if ( is_array( $ok ) ) {
1282 if ( $ok[1] ) { // Undeleted file count
1283 $this->getHookRunner()->onFileUndeleteComplete(
1284 $this->mTargetObj, $this->mFileVersions, $this->getUser(), $this->mComment );
1285 }
1286
1287 $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
1288 $out->addWikiMsg( 'undeletedpage', Message::rawParam( $link ) );
1289 } else {
1290 $out->setPageTitle( $this->msg( 'undelete-error' ) );
1291 }
1292
1293 // Show revision undeletion warnings and errors
1294 $status = $archive->getRevisionStatus();
1295 if ( $status && !$status->isGood() ) {
1296 $out->wrapWikiTextAsInterface(
1297 'error',
1298 '<div id="mw-error-cannotundelete">' .
1299 $status->getWikiText(
1300 'cannotundelete',
1301 'cannotundelete',
1302 $this->getLanguage()
1303 ) . '</div>'
1304 );
1305 }
1306
1307 // Show file undeletion warnings and errors
1308 $status = $archive->getFileStatus();
1309 if ( $status && !$status->isGood() ) {
1310 $out->wrapWikiTextAsInterface(
1311 'error',
1312 $status->getWikiText(
1313 'undelete-error-short',
1314 'undelete-error-long',
1315 $this->getLanguage()
1316 )
1317 );
1318 }
1319 }
1320
1329 public function prefixSearchSubpages( $search, $limit, $offset ) {
1330 return $this->prefixSearchString( $search, $limit, $offset );
1331 }
1332
1333 protected function getGroupName() {
1334 return 'pagetools';
1335 }
1336}
getPermissionManager()
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
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,...
Class representing a row of the 'filearchive' table.
static newFromRow( $row)
Loads a file object from the filearchive table Stable to override.
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
An error page which can definitely be safely rendered using the OutputPage.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:896
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2275
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1605
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:188
static formatRevisionSize( $size)
Definition Linker.php:1650
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition Linker.php:1574
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1142
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2195
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:941
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2253
Class to simplify the use of log pages.
Definition LogPage.php:37
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
getVisibility()
Get the deletion bitfield of the revision.
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
isMinor()
MCR migration note: this replaces Revision::isMinor.
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
isDeleted( $field)
MCR migration note: this replaces Revision::isDeleted.
Value object representing a content slot associated with a page revision.
Exception representing a failure to look up a row from a name table.
static rawParam( $raw)
Definition Message.php:1053
Used to show archived pages and eventually restore them.
Show an error when a user tries to do something they do not have the necessary permissions for.
Parent class for all special pages.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!...
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
displayRestrictionError()
Output an error message telling the user what access level they have to have Stable to override.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
MediaWiki Linker LinkRenderer null $linkRenderer
Special page allowing users with the appropriate permissions to view and restore deleted content.
redirectToRevDel()
Convert submitted form data to format expected by RevisionDelete and redirect the request.
showList( $result)
Generic list of deleted pages.
getFileComment( $file)
Fetch file upload comment if it's available to this user.
showDiff(RevisionRecord $previousRevRecord, RevisionRecord $currentRevRecord)
Build a diff display between this and the previous either deleted or non-deleted edit.
int[] null $mFileVersions
showFileConfirmationForm( $key)
Show a form confirming whether a tokenless user really wants to see a file.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
showFile( $key)
Show a deleted file version requested by the visitor.
diffHeader(RevisionRecord $revRecord, $prefix)
getFileUser( $file)
Fetch file's user id if it's available to this user.
string $mSearchPrefix
Search prefix.
execute( $par)
Default execute method Checks user permissions.
showRevision( $timestamp)
getFileLink( $file, $titleObj, $ts, $key)
Fetch image view link if it's available to all users.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.Stable to override 1....
doesWrites()
Indicates whether this special page may perform database writes.
isAllowed( $permission, User $user=null)
Checks whether a user is allowed the permission for the specific title if one is set.
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
getPageLink(RevisionRecord $revRecord, $titleObj, $ts)
Fetch revision text link if it's available to all users.
formatRevisionRow( $row, $earliestLiveTime, $remaining)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Content object implementation for representing flat text.
Represents a title within MediaWiki.
Definition Title.php:42
Show an error when the user tries to do something whilst blocked.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
const NS_USER
Definition Defines.php:72
const NS_FILE
Definition Defines.php:76
const NS_USER_TALK
Definition Defines.php:73
Service for constructing revision objects.
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition defines.php:25
$content
Definition router.php:76
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!isset( $args[0])) $lang