MediaWiki REL1_28
SpecialUndelete.php
Go to the documentation of this file.
1<?php
31 protected $title;
32
34 protected $fileStatus;
35
37 protected $revisionStatus;
38
40 protected $config;
41
42 function __construct( $title, Config $config = null ) {
43 if ( is_null( $title ) ) {
44 throw new MWException( __METHOD__ . ' given a null title.' );
45 }
46 $this->title = $title;
47 if ( $config === null ) {
48 wfDebug( __METHOD__ . ' did not have a Config object passed to it' );
49 $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
50 }
51 $this->config = $config;
52 }
53
54 public function doesWrites() {
55 return true;
56 }
57
65 public static function listAllPages() {
67
68 return self::listPages( $dbr, '' );
69 }
70
79 public static function listPagesByPrefix( $prefix ) {
81
82 $title = Title::newFromText( $prefix );
83 if ( $title ) {
84 $ns = $title->getNamespace();
85 $prefix = $title->getDBkey();
86 } else {
87 // Prolly won't work too good
88 // @todo handle bare namespace names cleanly?
89 $ns = 0;
90 }
91
92 $conds = [
93 'ar_namespace' => $ns,
94 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
95 ];
96
97 return self::listPages( $dbr, $conds );
98 }
99
105 protected static function listPages( $dbr, $condition ) {
106 return $dbr->select(
107 [ 'archive' ],
108 [
109 'ar_namespace',
110 'ar_title',
111 'count' => 'COUNT(*)'
112 ],
113 $condition,
114 __METHOD__,
115 [
116 'GROUP BY' => [ 'ar_namespace', 'ar_title' ],
117 'ORDER BY' => [ 'ar_namespace', 'ar_title' ],
118 'LIMIT' => 100,
119 ]
120 );
121 }
122
129 function listRevisions() {
131
132 $tables = [ 'archive' ];
133
134 $fields = [
135 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
136 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
137 ];
138
139 if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
140 $fields[] = 'ar_content_format';
141 $fields[] = 'ar_content_model';
142 }
143
144 $conds = [ 'ar_namespace' => $this->title->getNamespace(),
145 'ar_title' => $this->title->getDBkey() ];
146
147 $options = [ 'ORDER BY' => 'ar_timestamp DESC' ];
148
149 $join_conds = [];
150
152 $tables,
153 $fields,
154 $conds,
155 $join_conds,
156 $options,
157 ''
158 );
159
160 return $dbr->select( $tables,
161 $fields,
162 $conds,
163 __METHOD__,
164 $options,
165 $join_conds
166 );
167 }
168
177 function listFiles() {
178 if ( $this->title->getNamespace() != NS_FILE ) {
179 return null;
180 }
181
183 return $dbr->select(
184 'filearchive',
186 [ 'fa_name' => $this->title->getDBkey() ],
187 __METHOD__,
188 [ 'ORDER BY' => 'fa_timestamp DESC' ]
189 );
190 }
191
201
202 $fields = [
203 'ar_rev_id',
204 'ar_text',
205 'ar_comment',
206 'ar_user',
207 'ar_user_text',
208 'ar_timestamp',
209 'ar_minor_edit',
210 'ar_flags',
211 'ar_text_id',
212 'ar_deleted',
213 'ar_len',
214 'ar_sha1',
215 ];
216
217 if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
218 $fields[] = 'ar_content_format';
219 $fields[] = 'ar_content_model';
220 }
221
222 $row = $dbr->selectRow( 'archive',
223 $fields,
224 [ 'ar_namespace' => $this->title->getNamespace(),
225 'ar_title' => $this->title->getDBkey(),
226 'ar_timestamp' => $dbr->timestamp( $timestamp ) ],
227 __METHOD__ );
228
229 if ( $row ) {
230 return Revision::newFromArchiveRow( $row, [ 'title' => $this->title ] );
231 }
232
233 return null;
234 }
235
248
249 // Check the previous deleted revision...
250 $row = $dbr->selectRow( 'archive',
251 'ar_timestamp',
252 [ 'ar_namespace' => $this->title->getNamespace(),
253 'ar_title' => $this->title->getDBkey(),
254 'ar_timestamp < ' .
255 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
256 __METHOD__,
257 [
258 'ORDER BY' => 'ar_timestamp DESC',
259 'LIMIT' => 1 ] );
260 $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
261
262 $row = $dbr->selectRow( [ 'page', 'revision' ],
263 [ 'rev_id', 'rev_timestamp' ],
264 [
265 'page_namespace' => $this->title->getNamespace(),
266 'page_title' => $this->title->getDBkey(),
267 'page_id = rev_page',
268 'rev_timestamp < ' .
269 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
270 __METHOD__,
271 [
272 'ORDER BY' => 'rev_timestamp DESC',
273 'LIMIT' => 1 ] );
274 $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
275 $prevLiveId = $row ? intval( $row->rev_id ) : null;
276
277 if ( $prevLive && $prevLive > $prevDeleted ) {
278 // Most prior revision was live
279 return Revision::newFromId( $prevLiveId );
280 } elseif ( $prevDeleted ) {
281 // Most prior revision was deleted
282 return $this->getRevision( $prevDeleted );
283 }
284
285 // No prior revision on this page.
286 return null;
287 }
288
295 function getTextFromRow( $row ) {
296 if ( is_null( $row->ar_text_id ) ) {
297 // An old row from MediaWiki 1.4 or previous.
298 // Text is embedded in this row in classic compression format.
299 return Revision::getRevisionText( $row, 'ar_' );
300 }
301
302 // New-style: keyed to the text storage backend.
304 $text = $dbr->selectRow( 'text',
305 [ 'old_text', 'old_flags' ],
306 [ 'old_id' => $row->ar_text_id ],
307 __METHOD__ );
308
309 return Revision::getRevisionText( $text );
310 }
311
322 $row = $dbr->selectRow( 'archive',
323 [ 'ar_text', 'ar_flags', 'ar_text_id' ],
324 [ 'ar_namespace' => $this->title->getNamespace(),
325 'ar_title' => $this->title->getDBkey() ],
326 __METHOD__,
327 [ 'ORDER BY' => 'ar_timestamp DESC' ] );
328
329 if ( $row ) {
330 return $this->getTextFromRow( $row );
331 }
332
333 return null;
334 }
335
341 function isDeleted() {
343 $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
344 [ 'ar_namespace' => $this->title->getNamespace(),
345 'ar_title' => $this->title->getDBkey() ],
346 __METHOD__
347 );
348
349 return ( $n > 0 );
350 }
351
371 function undelete( $timestamps, $comment = '', $fileVersions = [],
372 $unsuppress = false, User $user = null, $tags = null
373 ) {
374 // If both the set of text revisions and file revisions are empty,
375 // restore everything. Otherwise, just restore the requested items.
376 $restoreAll = empty( $timestamps ) && empty( $fileVersions );
377
378 $restoreText = $restoreAll || !empty( $timestamps );
379 $restoreFiles = $restoreAll || !empty( $fileVersions );
380
381 if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
382 $img = wfLocalFile( $this->title );
383 $img->load( File::READ_LATEST );
384 $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
385 if ( !$this->fileStatus->isOK() ) {
386 return false;
387 }
388 $filesRestored = $this->fileStatus->successCount;
389 } else {
390 $filesRestored = 0;
391 }
392
393 if ( $restoreText ) {
394 $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
395 if ( !$this->revisionStatus->isOK() ) {
396 return false;
397 }
398
399 $textRestored = $this->revisionStatus->getValue();
400 } else {
401 $textRestored = 0;
402 }
403
404 // Touch the log!
405
406 if ( $textRestored && $filesRestored ) {
407 $reason = wfMessage( 'undeletedrevisions-files' )
408 ->numParams( $textRestored, $filesRestored )->inContentLanguage()->text();
409 } elseif ( $textRestored ) {
410 $reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored )
411 ->inContentLanguage()->text();
412 } elseif ( $filesRestored ) {
413 $reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored )
414 ->inContentLanguage()->text();
415 } else {
416 wfDebug( "Undelete: nothing undeleted...\n" );
417
418 return false;
419 }
420
421 if ( trim( $comment ) != '' ) {
422 $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
423 }
424
425 if ( $user === null ) {
427 $user = $wgUser;
428 }
429
430 $logEntry = new ManualLogEntry( 'delete', 'restore' );
431 $logEntry->setPerformer( $user );
432 $logEntry->setTarget( $this->title );
433 $logEntry->setComment( $reason );
434 $logEntry->setTags( $tags );
435
436 Hooks::run( 'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] );
437
438 $logid = $logEntry->insert();
439 $logEntry->publish( $logid );
440
441 return [ $textRestored, $filesRestored, $reason ];
442 }
443
455 private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
456 if ( wfReadOnly() ) {
457 throw new ReadOnlyError();
458 }
459
460 $dbw = wfGetDB( DB_MASTER );
461 $dbw->startAtomic( __METHOD__ );
462
463 $restoreAll = empty( $timestamps );
464
465 # Does this page already exist? We'll have to update it...
466 $article = WikiPage::factory( $this->title );
467 # Load latest data for the current page (bug 31179)
468 $article->loadPageData( 'fromdbmaster' );
469 $oldcountable = $article->isCountable();
470
471 $page = $dbw->selectRow( 'page',
472 [ 'page_id', 'page_latest' ],
473 [ 'page_namespace' => $this->title->getNamespace(),
474 'page_title' => $this->title->getDBkey() ],
475 __METHOD__,
476 [ 'FOR UPDATE' ] // lock page
477 );
478
479 if ( $page ) {
480 $makepage = false;
481 # Page already exists. Import the history, and if necessary
482 # we'll update the latest revision field in the record.
483
484 # Get the time span of this page
485 $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
486 [ 'rev_id' => $page->page_latest ],
487 __METHOD__ );
488
489 if ( $previousTimestamp === false ) {
490 wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" );
491
492 $status = Status::newGood( 0 );
493 $status->warning( 'undeleterevision-missing' );
494 $dbw->endAtomic( __METHOD__ );
495
496 return $status;
497 }
498 } else {
499 # Have to create a new article...
500 $makepage = true;
501 $previousTimestamp = 0;
502 }
503
504 $oldWhere = [
505 'ar_namespace' => $this->title->getNamespace(),
506 'ar_title' => $this->title->getDBkey(),
507 ];
508 if ( !$restoreAll ) {
509 $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
510 }
511
512 $fields = [
513 'ar_id',
514 'ar_rev_id',
515 'rev_id',
516 'ar_text',
517 'ar_comment',
518 'ar_user',
519 'ar_user_text',
520 'ar_timestamp',
521 'ar_minor_edit',
522 'ar_flags',
523 'ar_text_id',
524 'ar_deleted',
525 'ar_page_id',
526 'ar_len',
527 'ar_sha1'
528 ];
529
530 if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
531 $fields[] = 'ar_content_format';
532 $fields[] = 'ar_content_model';
533 }
534
538 $result = $dbw->select(
539 [ 'archive', 'revision' ],
540 $fields,
541 $oldWhere,
542 __METHOD__,
543 /* options */
544 [ 'ORDER BY' => 'ar_timestamp' ],
545 [ 'revision' => [ 'LEFT JOIN', 'ar_rev_id=rev_id' ] ]
546 );
547
548 $rev_count = $result->numRows();
549 if ( !$rev_count ) {
550 wfDebug( __METHOD__ . ": no revisions to restore\n" );
551
552 $status = Status::newGood( 0 );
553 $status->warning( "undelete-no-results" );
554 $dbw->endAtomic( __METHOD__ );
555
556 return $status;
557 }
558
559 // We use ar_id because there can be duplicate ar_rev_id even for the same
560 // page. In this case, we may be able to restore the first one.
561 $restoreFailedArIds = [];
562
563 // Map rev_id to the ar_id that is allowed to use it. When checking later,
564 // if it doesn't match, the current ar_id can not be restored.
565
566 // Value can be an ar_id or -1 (-1 means no ar_id can use it, since the
567 // rev_id is taken before we even start the restore).
568 $allowedRevIdToArIdMap = [];
569
570 $latestRestorableRow = null;
571
572 foreach ( $result as $row ) {
573 if ( $row->ar_rev_id ) {
574 // rev_id is taken even before we start restoring.
575 if ( $row->ar_rev_id === $row->rev_id ) {
576 $restoreFailedArIds[] = $row->ar_id;
577 $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
578 } else {
579 // rev_id is not taken yet in the DB, but it might be taken
580 // by a prior revision in the same restore operation. If
581 // not, we need to reserve it.
582 if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
583 $restoreFailedArIds[] = $row->ar_id;
584 } else {
585 $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
586 $latestRestorableRow = $row;
587 }
588 }
589 } else {
590 // If ar_rev_id is null, there can't be a collision, and a
591 // rev_id will be chosen automatically.
592 $latestRestorableRow = $row;
593 }
594 }
595
596 $result->seek( 0 ); // move back
597
598 $oldPageId = 0;
599 if ( $latestRestorableRow !== null ) {
600 $oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook
601
602 // grab the content to check consistency with global state before restoring the page.
603 $revision = Revision::newFromArchiveRow( $latestRestorableRow,
604 [
605 'title' => $article->getTitle(), // used to derive default content model
606 ]
607 );
608 $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
609 $content = $revision->getContent( Revision::RAW );
610
611 // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
612 $status = $content->prepareSave( $article, 0, -1, $user );
613 if ( !$status->isOK() ) {
614 $dbw->endAtomic( __METHOD__ );
615
616 return $status;
617 }
618 }
619
620 $newid = false; // newly created page ID
621 $restored = 0; // number of revisions restored
623 $revision = null;
624
625 // If there are no restorable revisions, we can skip most of the steps.
626 if ( $latestRestorableRow === null ) {
627 $failedRevisionCount = $rev_count;
628 } else {
629 if ( $makepage ) {
630 // Check the state of the newest to-be version...
631 if ( !$unsuppress
632 && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
633 ) {
634 $dbw->endAtomic( __METHOD__ );
635
636 return Status::newFatal( "undeleterevdel" );
637 }
638 // Safe to insert now...
639 $newid = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
640 if ( $newid === false ) {
641 // The old ID is reserved; let's pick another
642 $newid = $article->insertOn( $dbw );
643 }
644 $pageId = $newid;
645 } else {
646 // Check if a deleted revision will become the current revision...
647 if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
648 // Check the state of the newest to-be version...
649 if ( !$unsuppress
650 && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
651 ) {
652 $dbw->endAtomic( __METHOD__ );
653
654 return Status::newFatal( "undeleterevdel" );
655 }
656 }
657
658 $newid = false;
659 $pageId = $article->getId();
660 }
661
662 foreach ( $result as $row ) {
663 // Check for key dupes due to needed archive integrity.
664 if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
665 continue;
666 }
667 // Insert one revision at a time...maintaining deletion status
668 // unless we are specifically removing all restrictions...
669 $revision = Revision::newFromArchiveRow( $row,
670 [
671 'page' => $pageId,
672 'title' => $this->title,
673 'deleted' => $unsuppress ? 0 : $row->ar_deleted
674 ] );
675
676 $revision->insertOn( $dbw );
677 $restored++;
678
679 Hooks::run( 'ArticleRevisionUndeleted',
680 [ &$this->title, $revision, $row->ar_page_id ] );
681 }
682
683 // Now that it's safely stored, take it out of the archive
684 // Don't delete rows that we failed to restore
685 $toDeleteConds = $oldWhere;
686 $failedRevisionCount = count( $restoreFailedArIds );
687 if ( $failedRevisionCount > 0 ) {
688 $toDeleteConds[] = 'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) . ' )';
689 }
690
691 $dbw->delete( 'archive',
692 $toDeleteConds,
693 __METHOD__ );
694 }
695
696 $status = Status::newGood( $restored );
697
698 if ( $failedRevisionCount > 0 ) {
699 $status->warning(
700 wfMessage( 'undeleterevision-duplicate-revid', $failedRevisionCount ) );
701 }
702
703 // Was anything restored at all?
704 if ( $restored ) {
705 $created = (bool)$newid;
706 // Attach the latest revision to the page...
707 $wasnew = $article->updateIfNewerOn( $dbw, $revision );
708 if ( $created || $wasnew ) {
709 // Update site stats, link tables, etc
710 $article->doEditUpdates(
711 $revision,
712 User::newFromName( $revision->getUserText( Revision::RAW ), false ),
713 [
714 'created' => $created,
715 'oldcountable' => $oldcountable,
716 'restored' => true
717 ]
718 );
719 }
720
721 Hooks::run( 'ArticleUndelete', [ &$this->title, $created, $comment, $oldPageId ] );
722 if ( $this->title->getNamespace() == NS_FILE ) {
723 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->title, 'imagelinks' ) );
724 }
725 }
726
727 $dbw->endAtomic( __METHOD__ );
728
729 return $status;
730 }
731
735 function getFileStatus() {
736 return $this->fileStatus;
737 }
738
742 function getRevisionStatus() {
744 }
745}
746
754 private $mAction;
755 private $mTarget;
756 private $mTimestamp;
757 private $mRestore;
758 private $mRevdel;
759 private $mInvert;
760 private $mFilename;
762 private $mAllowed;
763 private $mCanView;
764 private $mComment;
765 private $mToken;
766
768 private $mTargetObj;
769
770 function __construct() {
771 parent::__construct( 'Undelete', 'deletedhistory' );
772 }
773
774 public function doesWrites() {
775 return true;
776 }
777
778 function loadRequest( $par ) {
779 $request = $this->getRequest();
780 $user = $this->getUser();
781
782 $this->mAction = $request->getVal( 'action' );
783 if ( $par !== null && $par !== '' ) {
784 $this->mTarget = $par;
785 } else {
786 $this->mTarget = $request->getVal( 'target' );
787 }
788
789 $this->mTargetObj = null;
790
791 if ( $this->mTarget !== null && $this->mTarget !== '' ) {
792 $this->mTargetObj = Title::newFromText( $this->mTarget );
793 }
794
795 $this->mSearchPrefix = $request->getText( 'prefix' );
796 $time = $request->getVal( 'timestamp' );
797 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
798 $this->mFilename = $request->getVal( 'file' );
799
800 $posted = $request->wasPosted() &&
801 $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
802 $this->mRestore = $request->getCheck( 'restore' ) && $posted;
803 $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
804 $this->mInvert = $request->getCheck( 'invert' ) && $posted;
805 $this->mPreview = $request->getCheck( 'preview' ) && $posted;
806 $this->mDiff = $request->getCheck( 'diff' );
807 $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
808 $this->mComment = $request->getText( 'wpComment' );
809 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
810 $this->mToken = $request->getVal( 'token' );
811
812 if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
813 $this->mAllowed = true; // user can restore
814 $this->mCanView = true; // user can view content
815 } elseif ( $this->isAllowed( 'deletedtext' ) ) {
816 $this->mAllowed = false; // user cannot restore
817 $this->mCanView = true; // user can view content
818 $this->mRestore = false;
819 } else { // user can only view the list of revisions
820 $this->mAllowed = false;
821 $this->mCanView = false;
822 $this->mTimestamp = '';
823 $this->mRestore = false;
824 }
825
826 if ( $this->mRestore || $this->mInvert ) {
827 $timestamps = [];
828 $this->mFileVersions = [];
829 foreach ( $request->getValues() as $key => $val ) {
830 $matches = [];
831 if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
832 array_push( $timestamps, $matches[1] );
833 }
834
835 if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
836 $this->mFileVersions[] = intval( $matches[1] );
837 }
838 }
839 rsort( $timestamps );
840 $this->mTargetTimestamp = $timestamps;
841 }
842 }
843
852 protected function isAllowed( $permission, User $user = null ) {
853 $user = $user ?: $this->getUser();
854 if ( $this->mTargetObj !== null ) {
855 return $this->mTargetObj->userCan( $permission, $user );
856 } else {
857 return $user->isAllowed( $permission );
858 }
859 }
860
861 function userCanExecute( User $user ) {
862 return $this->isAllowed( $this->mRestriction, $user );
863 }
864
865 function execute( $par ) {
867
868 $user = $this->getUser();
869
870 $this->setHeaders();
871 $this->outputHeader();
872
873 $this->loadRequest( $par );
874 $this->checkPermissions(); // Needs to be after mTargetObj is set
875
876 $out = $this->getOutput();
877
878 if ( is_null( $this->mTargetObj ) ) {
879 $out->addWikiMsg( 'undelete-header' );
880
881 # Not all users can just browse every deleted page from the list
882 if ( $user->isAllowed( 'browsearchive' ) ) {
883 $this->showSearchForm();
884 }
885
886 return;
887 }
888
889 $this->addHelpLink( 'Help:Undelete' );
890 if ( $this->mAllowed ) {
891 $out->setPageTitle( $this->msg( 'undeletepage' ) );
892 } else {
893 $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
894 }
895
896 $this->getSkin()->setRelevantTitle( $this->mTargetObj );
897
898 if ( $this->mTimestamp !== '' ) {
899 $this->showRevision( $this->mTimestamp );
900 } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
901 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
902 // Check if user is allowed to see this file
903 if ( !$file->exists() ) {
904 $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
905 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
906 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
907 throw new PermissionsError( 'suppressrevision' );
908 } else {
909 throw new PermissionsError( 'deletedtext' );
910 }
911 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
912 $this->showFileConfirmationForm( $this->mFilename );
913 } else {
914 $this->showFile( $this->mFilename );
915 }
916 } elseif ( $this->mAction === "submit" ) {
917 if ( $this->mRestore ) {
918 $this->undelete();
919 } elseif ( $this->mRevdel ) {
920 $this->redirectToRevDel();
921 }
922
923 } else {
924 $this->showHistory();
925 }
926 }
927
932 private function redirectToRevDel() {
933 $archive = new PageArchive( $this->mTargetObj );
934
935 $revisions = [];
936
937 foreach ( $this->getRequest()->getValues() as $key => $val ) {
938 $matches = [];
939 if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
940 $revisions[ $archive->getRevision( $matches[1] )->getId() ] = 1;
941 }
942 }
943 $query = [
944 "type" => "revision",
945 "ids" => $revisions,
946 "target" => $this->mTargetObj->getPrefixedText()
947 ];
948 $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
949 $this->getOutput()->redirect( $url );
950 }
951
952 function showSearchForm() {
953 $out = $this->getOutput();
954 $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
955 $out->addHTML(
956 Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) .
957 Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
958 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
959 Html::rawElement(
960 'label',
961 [ 'for' => 'prefix' ],
962 $this->msg( 'undelete-search-prefix' )->parse()
963 ) .
965 'prefix',
966 20,
967 $this->mSearchPrefix,
968 [ 'id' => 'prefix', 'autofocus' => '' ]
969 ) . ' ' .
970 Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
971 Xml::closeElement( 'fieldset' ) .
972 Xml::closeElement( 'form' )
973 );
974
975 # List undeletable articles
976 if ( $this->mSearchPrefix ) {
977 $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
978 $this->showList( $result );
979 }
980 }
981
988 private function showList( $result ) {
989 $out = $this->getOutput();
990
991 if ( $result->numRows() == 0 ) {
992 $out->addWikiMsg( 'undelete-no-results' );
993
994 return false;
995 }
996
997 $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
998
999 $undelete = $this->getPageTitle();
1000 $out->addHTML( "<ul>\n" );
1001 foreach ( $result as $row ) {
1002 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
1003 if ( $title !== null ) {
1004 $item = Linker::linkKnown(
1005 $undelete,
1006 htmlspecialchars( $title->getPrefixedText() ),
1007 [],
1008 [ 'target' => $title->getPrefixedText() ]
1009 );
1010 } else {
1011 // The title is no longer valid, show as text
1012 $item = Html::element(
1013 'span',
1014 [ 'class' => 'mw-invalidtitle' ],
1016 $this->getContext(),
1017 $row->ar_namespace,
1018 $row->ar_title
1019 )
1020 );
1021 }
1022 $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
1023 $out->addHTML( "<li>{$item} ({$revs})</li>\n" );
1024 }
1025 $result->free();
1026 $out->addHTML( "</ul>\n" );
1027
1028 return true;
1029 }
1030
1031 private function showRevision( $timestamp ) {
1032 if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
1033 return;
1034 }
1035
1036 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1037 if ( !Hooks::run( 'UndeleteForm::showRevision', [ &$archive, $this->mTargetObj ] ) ) {
1038 return;
1039 }
1040 $rev = $archive->getRevision( $timestamp );
1041
1042 $out = $this->getOutput();
1043 $user = $this->getUser();
1044
1045 if ( !$rev ) {
1046 $out->addWikiMsg( 'undeleterevision-missing' );
1047
1048 return;
1049 }
1050
1051 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
1052 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
1053 $out->wrapWikiMsg(
1054 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1055 $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
1056 'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
1057 );
1058
1059 return;
1060 }
1061
1062 $out->wrapWikiMsg(
1063 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1064 $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
1065 'rev-suppressed-text-view' : 'rev-deleted-text-view'
1066 );
1067 $out->addHTML( '<br />' );
1068 // and we are allowed to see...
1069 }
1070
1071 if ( $this->mDiff ) {
1072 $previousRev = $archive->getPreviousRevision( $timestamp );
1073 if ( $previousRev ) {
1074 $this->showDiff( $previousRev, $rev );
1075 if ( $this->mDiffOnly ) {
1076 return;
1077 }
1078
1079 $out->addHTML( '<hr />' );
1080 } else {
1081 $out->addWikiMsg( 'undelete-nodiff' );
1082 }
1083 }
1084
1086 $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
1087 htmlspecialchars( $this->mTargetObj->getPrefixedText() )
1088 );
1089
1090 $lang = $this->getLanguage();
1091
1092 // date and time are separate parameters to facilitate localisation.
1093 // $time is kept for backward compat reasons.
1094 $time = $lang->userTimeAndDate( $timestamp, $user );
1095 $d = $lang->userDate( $timestamp, $user );
1096 $t = $lang->userTime( $timestamp, $user );
1097 $userLink = Linker::revUserTools( $rev );
1098
1099 $content = $rev->getContent( Revision::FOR_THIS_USER, $user );
1100
1101 $isText = ( $content instanceof TextContent );
1102
1103 if ( $this->mPreview || $isText ) {
1104 $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
1105 } else {
1106 $openDiv = '<div id="mw-undelete-revision">';
1107 }
1108 $out->addHTML( $openDiv );
1109
1110 // Revision delete links
1111 if ( !$this->mDiff ) {
1112 $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
1113 if ( $revdel ) {
1114 $out->addHTML( "$revdel " );
1115 }
1116 }
1117
1118 $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
1119 $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
1120
1121 if ( !Hooks::run( 'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) {
1122 return;
1123 }
1124
1125 if ( ( $this->mPreview || !$isText ) && $content ) {
1126 // NOTE: non-text content has no source view, so always use rendered preview
1127
1128 // Hide [edit]s
1129 $popts = $out->parserOptions();
1130 $popts->setEditSection( false );
1131
1132 $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
1133 $out->addParserOutput( $pout );
1134 }
1135
1136 if ( $isText ) {
1137 // source view for textual content
1138 $sourceView = Xml::element(
1139 'textarea',
1140 [
1141 'readonly' => 'readonly',
1142 'cols' => $user->getIntOption( 'cols' ),
1143 'rows' => $user->getIntOption( 'rows' )
1144 ],
1145 $content->getNativeData() . "\n"
1146 );
1147
1148 $previewButton = Xml::element( 'input', [
1149 'type' => 'submit',
1150 'name' => 'preview',
1151 'value' => $this->msg( 'showpreview' )->text()
1152 ] );
1153 } else {
1154 $sourceView = '';
1155 $previewButton = '';
1156 }
1157
1158 $diffButton = Xml::element( 'input', [
1159 'name' => 'diff',
1160 'type' => 'submit',
1161 'value' => $this->msg( 'showdiff' )->text() ] );
1162
1163 $out->addHTML(
1164 $sourceView .
1165 Xml::openElement( 'div', [
1166 'style' => 'clear: both' ] ) .
1167 Xml::openElement( 'form', [
1168 'method' => 'post',
1169 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
1170 Xml::element( 'input', [
1171 'type' => 'hidden',
1172 'name' => 'target',
1173 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
1174 Xml::element( 'input', [
1175 'type' => 'hidden',
1176 'name' => 'timestamp',
1177 'value' => $timestamp ] ) .
1178 Xml::element( 'input', [
1179 'type' => 'hidden',
1180 'name' => 'wpEditToken',
1181 'value' => $user->getEditToken() ] ) .
1182 $previewButton .
1183 $diffButton .
1184 Xml::closeElement( 'form' ) .
1185 Xml::closeElement( 'div' )
1186 );
1187 }
1188
1197 function showDiff( $previousRev, $currentRev ) {
1198 $diffContext = clone $this->getContext();
1199 $diffContext->setTitle( $currentRev->getTitle() );
1200 $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
1201
1202 $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
1203 $diffEngine->showDiffStyle();
1204
1205 $formattedDiff = $diffEngine->generateContentDiffBody(
1206 $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
1207 $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
1208 );
1209
1210 $formattedDiff = $diffEngine->addHeader(
1211 $formattedDiff,
1212 $this->diffHeader( $previousRev, 'o' ),
1213 $this->diffHeader( $currentRev, 'n' )
1214 );
1215
1216 $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
1217 }
1218
1224 private function diffHeader( $rev, $prefix ) {
1225 $isDeleted = !( $rev->getId() && $rev->getTitle() );
1226 if ( $isDeleted ) {
1228 $targetPage = $this->getPageTitle();
1229 $targetQuery = [
1230 'target' => $this->mTargetObj->getPrefixedText(),
1231 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
1232 ];
1233 } else {
1235 $targetPage = $rev->getTitle();
1236 $targetQuery = [ 'oldid' => $rev->getId() ];
1237 }
1238
1239 // Add show/hide deletion links if available
1240 $user = $this->getUser();
1241 $lang = $this->getLanguage();
1242 $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
1243
1244 if ( $rdel ) {
1245 $rdel = " $rdel";
1246 }
1247
1248 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
1249
1250 $tags = wfGetDB( DB_REPLICA )->selectField(
1251 'tag_summary',
1252 'ts_tags',
1253 [ 'ts_rev_id' => $rev->getId() ],
1254 __METHOD__
1255 );
1256 $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
1257
1258 // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
1259 // and partially #showDiffPage, but worse
1260 return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
1262 $targetPage,
1263 $this->msg(
1264 'revisionasof',
1265 $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
1266 $lang->userDate( $rev->getTimestamp(), $user ),
1267 $lang->userTime( $rev->getTimestamp(), $user )
1268 )->escaped(),
1269 [],
1270 $targetQuery
1271 ) .
1272 '</strong></div>' .
1273 '<div id="mw-diff-' . $prefix . 'title2">' .
1274 Linker::revUserTools( $rev ) . '<br />' .
1275 '</div>' .
1276 '<div id="mw-diff-' . $prefix . 'title3">' .
1277 $minor . Linker::revComment( $rev ) . $rdel . '<br />' .
1278 '</div>' .
1279 '<div id="mw-diff-' . $prefix . 'title5">' .
1280 $tagSummary[0] . '<br />' .
1281 '</div>';
1282 }
1283
1288 private function showFileConfirmationForm( $key ) {
1289 $out = $this->getOutput();
1290 $lang = $this->getLanguage();
1291 $user = $this->getUser();
1292 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
1293 $out->addWikiMsg( 'undelete-show-file-confirm',
1294 $this->mTargetObj->getText(),
1295 $lang->userDate( $file->getTimestamp(), $user ),
1296 $lang->userTime( $file->getTimestamp(), $user ) );
1297 $out->addHTML(
1298 Xml::openElement( 'form', [
1299 'method' => 'POST',
1300 'action' => $this->getPageTitle()->getLocalURL( [
1301 'target' => $this->mTarget,
1302 'file' => $key,
1303 'token' => $user->getEditToken( $key ),
1304 ] ),
1305 ]
1306 ) .
1307 Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
1308 '</form>'
1309 );
1310 }
1311
1316 private function showFile( $key ) {
1317 $this->getOutput()->disable();
1318
1319 # We mustn't allow the output to be CDN cached, otherwise
1320 # if an admin previews a deleted image, and it's cached, then
1321 # a user without appropriate permissions can toddle off and
1322 # nab the image, and CDN will serve it
1323 $response = $this->getRequest()->response();
1324 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
1325 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
1326 $response->header( 'Pragma: no-cache' );
1327
1328 $repo = RepoGroup::singleton()->getLocalRepo();
1329 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
1330 $repo->streamFile( $path );
1331 }
1332
1333 protected function showHistory() {
1334 $this->checkReadOnly();
1335
1336 $out = $this->getOutput();
1337 if ( $this->mAllowed ) {
1338 $out->addModules( 'mediawiki.special.undelete' );
1339 }
1340 $out->wrapWikiMsg(
1341 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1342 [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
1343 );
1344
1345 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1346 Hooks::run( 'UndeleteForm::showHistory', [ &$archive, $this->mTargetObj ] );
1347 /*
1348 $text = $archive->getLastRevisionText();
1349 if( is_null( $text ) ) {
1350 $out->addWikiMsg( 'nohistory' );
1351 return;
1352 }
1353 */
1354 $out->addHTML( '<div class="mw-undelete-history">' );
1355 if ( $this->mAllowed ) {
1356 $out->addWikiMsg( 'undeletehistory' );
1357 $out->addWikiMsg( 'undeleterevdel' );
1358 } else {
1359 $out->addWikiMsg( 'undeletehistorynoadmin' );
1360 }
1361 $out->addHTML( '</div>' );
1362
1363 # List all stored revisions
1364 $revisions = $archive->listRevisions();
1365 $files = $archive->listFiles();
1366
1367 $haveRevisions = $revisions && $revisions->numRows() > 0;
1368 $haveFiles = $files && $files->numRows() > 0;
1369
1370 # Batch existence check on user and talk pages
1371 if ( $haveRevisions ) {
1372 $batch = new LinkBatch();
1373 foreach ( $revisions as $row ) {
1374 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
1375 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
1376 }
1377 $batch->execute();
1378 $revisions->seek( 0 );
1379 }
1380 if ( $haveFiles ) {
1381 $batch = new LinkBatch();
1382 foreach ( $files as $row ) {
1383 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
1384 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
1385 }
1386 $batch->execute();
1387 $files->seek( 0 );
1388 }
1389
1390 if ( $this->mAllowed ) {
1391 $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
1392 # Start the form here
1393 $top = Xml::openElement(
1394 'form',
1395 [ 'method' => 'post', 'action' => $action, 'id' => 'undelete' ]
1396 );
1397 $out->addHTML( $top );
1398 }
1399
1400 # Show relevant lines from the deletion log:
1401 $deleteLogPage = new LogPage( 'delete' );
1402 $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
1403 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
1404 # Show relevant lines from the suppression log:
1405 $suppressLogPage = new LogPage( 'suppress' );
1406 if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
1407 $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
1408 LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
1409 }
1410
1411 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1412 # Format the user-visible controls (comment field, submission button)
1413 # in a nice little table
1414 if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
1415 $unsuppressBox =
1416 "<tr>
1417 <td>&#160;</td>
1418 <td class='mw-input'>" .
1419 Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
1420 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) .
1421 "</td>
1422 </tr>";
1423 } else {
1424 $unsuppressBox = '';
1425 }
1426
1427 $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
1428 Xml::openElement( 'table', [ 'id' => 'mw-undelete-table' ] ) .
1429 "<tr>
1430 <td colspan='2' class='mw-undelete-extrahelp'>" .
1431 $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
1432 "</td>
1433 </tr>
1434 <tr>
1435 <td class='mw-label'>" .
1436 Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
1437 "</td>
1438 <td class='mw-input'>" .
1439 Xml::input(
1440 'wpComment',
1441 50,
1442 $this->mComment,
1443 [ 'id' => 'wpComment', 'autofocus' => '' ]
1444 ) .
1445 "</td>
1446 </tr>
1447 <tr>
1448 <td>&#160;</td>
1449 <td class='mw-submit'>" .
1451 $this->msg( 'undeletebtn' )->text(),
1452 [ 'name' => 'restore', 'id' => 'mw-undelete-submit' ]
1453 ) . ' ' .
1455 $this->msg( 'undeleteinvert' )->text(),
1456 [ 'name' => 'invert', 'id' => 'mw-undelete-invert' ]
1457 ) .
1458 "</td>
1459 </tr>" .
1460 $unsuppressBox .
1461 Xml::closeElement( 'table' ) .
1462 Xml::closeElement( 'fieldset' );
1463
1464 $out->addHTML( $table );
1465 }
1466
1467 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
1468
1469 if ( $haveRevisions ) {
1470 # Show the page's stored (deleted) history
1471
1472 if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
1473 $out->addHTML( Html::element(
1474 'button',
1475 [
1476 'name' => 'revdel',
1477 'type' => 'submit',
1478 'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
1479 ],
1480 $this->msg( 'showhideselectedversions' )->text()
1481 ) . "\n" );
1482 }
1483
1484 $out->addHTML( '<ul>' );
1485 $remaining = $revisions->numRows();
1486 $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
1487
1488 foreach ( $revisions as $row ) {
1489 $remaining--;
1490 $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) );
1491 }
1492 $revisions->free();
1493 $out->addHTML( '</ul>' );
1494 } else {
1495 $out->addWikiMsg( 'nohistory' );
1496 }
1497
1498 if ( $haveFiles ) {
1499 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" );
1500 $out->addHTML( '<ul>' );
1501 foreach ( $files as $row ) {
1502 $out->addHTML( $this->formatFileRow( $row ) );
1503 }
1504 $files->free();
1505 $out->addHTML( '</ul>' );
1506 }
1507
1508 if ( $this->mAllowed ) {
1509 # Slip in the hidden controls here
1510 $misc = Html::hidden( 'target', $this->mTarget );
1511 $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
1512 $misc .= Xml::closeElement( 'form' );
1513 $out->addHTML( $misc );
1514 }
1515
1516 return true;
1517 }
1518
1519 protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
1521 [
1522 'title' => $this->mTargetObj
1523 ] );
1524
1525 $revTextSize = '';
1526 $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1527 // Build checkboxen...
1528 if ( $this->mAllowed ) {
1529 if ( $this->mInvert ) {
1530 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1531 $checkBox = Xml::check( "ts$ts" );
1532 } else {
1533 $checkBox = Xml::check( "ts$ts", true );
1534 }
1535 } else {
1536 $checkBox = Xml::check( "ts$ts" );
1537 }
1538 } else {
1539 $checkBox = '';
1540 }
1541
1542 // Build page & diff links...
1543 $user = $this->getUser();
1544 if ( $this->mCanView ) {
1545 $titleObj = $this->getPageTitle();
1546 # Last link
1547 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
1548 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1549 $last = $this->msg( 'diff' )->escaped();
1550 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1551 $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
1553 $titleObj,
1554 $this->msg( 'diff' )->escaped(),
1555 [],
1556 [
1557 'target' => $this->mTargetObj->getPrefixedText(),
1558 'timestamp' => $ts,
1559 'diff' => 'prev'
1560 ]
1561 );
1562 } else {
1563 $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
1564 $last = $this->msg( 'diff' )->escaped();
1565 }
1566 } else {
1567 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1568 $last = $this->msg( 'diff' )->escaped();
1569 }
1570
1571 // User links
1572 $userLink = Linker::revUserTools( $rev );
1573
1574 // Minor edit
1575 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
1576
1577 // Revision text size
1578 $size = $row->ar_len;
1579 if ( !is_null( $size ) ) {
1580 $revTextSize = Linker::formatRevisionSize( $size );
1581 }
1582
1583 // Edit summary
1585
1586 // Tags
1587 $attribs = [];
1588 list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
1589 $row->ts_tags,
1590 'deletedhistory',
1591 $this->getContext()
1592 );
1593 if ( $classes ) {
1594 $attribs['class'] = implode( ' ', $classes );
1595 }
1596
1597 $revisionRow = $this->msg( 'undelete-revision-row2' )
1598 ->rawParams(
1599 $checkBox,
1600 $last,
1601 $pageLink,
1602 $userLink,
1603 $minor,
1604 $revTextSize,
1605 $comment,
1606 $tagSummary
1607 )
1608 ->escaped();
1609
1610 return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1611 }
1612
1613 private function formatFileRow( $row ) {
1614 $file = ArchivedFile::newFromRow( $row );
1615 $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1616 $user = $this->getUser();
1617
1618 $checkBox = '';
1619 if ( $this->mCanView && $row->fa_storage_key ) {
1620 if ( $this->mAllowed ) {
1621 $checkBox = Xml::check( 'fileid' . $row->fa_id );
1622 }
1623 $key = urlencode( $row->fa_storage_key );
1624 $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1625 } else {
1626 $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
1627 }
1628 $userLink = $this->getFileUser( $file );
1629 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1630 $bytes = $this->msg( 'parentheses' )
1631 ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1632 ->plain();
1633 $data = htmlspecialchars( $data . ' ' . $bytes );
1634 $comment = $this->getFileComment( $file );
1635
1636 // Add show/hide deletion links if available
1637 $canHide = $this->isAllowed( 'deleterevision' );
1638 if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1639 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1640 // Revision was hidden from sysops
1641 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1642 } else {
1643 $query = [
1644 'type' => 'filearchive',
1645 'target' => $this->mTargetObj->getPrefixedDBkey(),
1646 'ids' => $row->fa_id
1647 ];
1648 $revdlink = Linker::revDeleteLink( $query,
1649 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1650 }
1651 } else {
1652 $revdlink = '';
1653 }
1654
1655 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1656 }
1657
1666 function getPageLink( $rev, $titleObj, $ts ) {
1667 $user = $this->getUser();
1668 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1669
1670 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
1671 return '<span class="history-deleted">' . $time . '</span>';
1672 }
1673
1675 $titleObj,
1676 htmlspecialchars( $time ),
1677 [],
1678 [
1679 'target' => $this->mTargetObj->getPrefixedText(),
1680 'timestamp' => $ts
1681 ]
1682 );
1683
1684 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
1685 $link = '<span class="history-deleted">' . $link . '</span>';
1686 }
1687
1688 return $link;
1689 }
1690
1701 function getFileLink( $file, $titleObj, $ts, $key ) {
1702 $user = $this->getUser();
1703 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1704
1705 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1706 return '<span class="history-deleted">' . $time . '</span>';
1707 }
1708
1710 $titleObj,
1711 htmlspecialchars( $time ),
1712 [],
1713 [
1714 'target' => $this->mTargetObj->getPrefixedText(),
1715 'file' => $key,
1716 'token' => $user->getEditToken( $key )
1717 ]
1718 );
1719
1720 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1721 $link = '<span class="history-deleted">' . $link . '</span>';
1722 }
1723
1724 return $link;
1725 }
1726
1733 function getFileUser( $file ) {
1734 if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
1735 return '<span class="history-deleted">' .
1736 $this->msg( 'rev-deleted-user' )->escaped() .
1737 '</span>';
1738 }
1739
1740 $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
1741 Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1742
1743 if ( $file->isDeleted( File::DELETED_USER ) ) {
1744 $link = '<span class="history-deleted">' . $link . '</span>';
1745 }
1746
1747 return $link;
1748 }
1749
1756 function getFileComment( $file ) {
1757 if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
1758 return '<span class="history-deleted"><span class="comment">' .
1759 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
1760 }
1761
1762 $link = Linker::commentBlock( $file->getRawDescription() );
1763
1764 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1765 $link = '<span class="history-deleted">' . $link . '</span>';
1766 }
1767
1768 return $link;
1769 }
1770
1771 function undelete() {
1772 if ( $this->getConfig()->get( 'UploadMaintenance' )
1773 && $this->mTargetObj->getNamespace() == NS_FILE
1774 ) {
1775 throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1776 }
1777
1778 $this->checkReadOnly();
1779
1780 $out = $this->getOutput();
1781 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1782 Hooks::run( 'UndeleteForm::undelete', [ &$archive, $this->mTargetObj ] );
1783 $ok = $archive->undelete(
1784 $this->mTargetTimestamp,
1785 $this->mComment,
1786 $this->mFileVersions,
1787 $this->mUnsuppress,
1788 $this->getUser()
1789 );
1790
1791 if ( is_array( $ok ) ) {
1792 if ( $ok[1] ) { // Undeleted file count
1793 Hooks::run( 'FileUndeleteComplete', [
1794 $this->mTargetObj, $this->mFileVersions,
1795 $this->getUser(), $this->mComment ] );
1796 }
1797
1798 $link = Linker::linkKnown( $this->mTargetObj );
1799 $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
1800 } else {
1801 $out->setPageTitle( $this->msg( 'undelete-error' ) );
1802 }
1803
1804 // Show revision undeletion warnings and errors
1805 $status = $archive->getRevisionStatus();
1806 if ( $status && !$status->isGood() ) {
1807 $out->addWikiText( '<div class="error">' .
1808 $status->getWikiText(
1809 'cannotundelete',
1810 'cannotundelete'
1811 ) . '</div>'
1812 );
1813 }
1814
1815 // Show file undeletion warnings and errors
1816 $status = $archive->getFileStatus();
1817 if ( $status && !$status->isGood() ) {
1818 $out->addWikiText( '<div class="error">' .
1819 $status->getWikiText(
1820 'undelete-error-short',
1821 'undelete-error-long'
1822 ) . '</div>'
1823 );
1824 }
1825 }
1826
1835 public function prefixSearchSubpages( $search, $limit, $offset ) {
1836 return $this->prefixSearchString( $search, $limit, $offset );
1837 }
1838
1839 protected function getGroupName() {
1840 return 'pagetools';
1841 }
1842}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfLocalFile( $title)
Get an object referring to a locally registered file.
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,...
$wgUser
Definition Setup.php:806
Class representing a row of the 'filearchive' table.
static selectFields()
Fields in the filearchive table.
static newFromRow( $row)
Loads a file object from the filearchive table.
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag=false)
Applies all tags-related changes to a query.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
An error page which can definitely be safely rendered using the OutputPage.
const DELETED_COMMENT
Definition File.php:53
const DELETED_RESTRICTED
Definition File.php:55
const DELETED_FILE
Definition File.php:52
const DELETED_USER
Definition File.php:54
Class to invalidate the HTML cache of all the pages linking to a given title.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:32
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:203
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:984
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:255
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2166
static revComment(Revision $rev, $local=false, $isPublic=false)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1550
static commentBlock( $comment, $title=null, $local=false, $wikiId=null)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition Linker.php:1525
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:300
static revUserTools( $rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1141
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:1017
static formatRevisionSize( $size)
Definition Linker.php:1573
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2144
static getRevDeleteLink(User $user, Revision $rev, Title $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2103
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
Definition LogPage.php:32
MediaWiki exception.
Class for creating log entries manually, to inject them into the database.
Definition LogEntry.php:394
Used to show archived pages and eventually restore them.
getTextFromRow( $row)
Get the text from an archive row containing ar_text, ar_flags and ar_text_id.
listFiles()
List the deleted file revisions for this page, if it's a file page.
static listPages( $dbr, $condition)
listRevisions()
List the revisions of the given page.
undeleteRevisions( $timestamps, $unsuppress=false, $comment='')
This is the meaty bit – It restores archived revisions of the given page to the revision table.
undelete( $timestamps, $comment='', $fileVersions=[], $unsuppress=false, User $user=null, $tags=null)
Restore the given (or all) text and file revisions for the page.
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix.
static listAllPages()
List all deleted pages recorded in the archive table.
getLastRevisionText()
Fetch (and decompress if necessary) the stored text of the most recently edited deleted revision of t...
getRevision( $timestamp)
Return a Revision object containing data for the deleted revision.
Status $revisionStatus
__construct( $title, Config $config=null)
isDeleted()
Quick check if any archived revisions are present for the page.
getPreviousRevision( $timestamp)
Return the most-previous revision, either live or deleted, against the deleted revision given by time...
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static singleton()
Get a RepoGroup instance.
Definition RepoGroup.php:59
static getRevisionText( $row, $prefix='old_', $wiki=false)
Get revision text associated with an old or archive row $row is usually an object from wfFetchRow(),...
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition Revision.php:183
const DELETED_TEXT
Definition Revision.php:85
const DELETED_RESTRICTED
Definition Revision.php:88
const RAW
Definition Revision.php:94
const FOR_THIS_USER
Definition Revision.php:93
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:110
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.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
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.
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.
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.
msg()
Wrapper around wfMessage that sets the current context.
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.
diffHeader( $rev, $prefix)
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.
getPageLink( $rev, $titleObj, $ts)
Fetch revision text link if it's available to all users.
getFileUser( $file)
Fetch file's user id if it's available to this user.
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.
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...
formatRevisionRow( $row, $earliestLiveTime, $remaining)
showDiff( $previousRev, $currentRev)
Build a diff display between this and the previous either deleted or non-deleted edit.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
Content object implementation for representing flat text.
Represents a title within MediaWiki.
Definition Title.php:36
getNamespace()
Get the namespace index, i.e.
Definition Title.php:921
getDBkey()
Get the main part with underscores.
Definition Title.php:898
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:115
static closeElement( $element)
Shortcut to close an XML element.
Definition Xml.php:118
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition Xml.php:324
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition Xml.php:359
static openElement( $element, $attribs=null)
This opens an XML element.
Definition Xml.php:109
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition Xml.php:275
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition Xml.php:460
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition Xml.php:420
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition Xml.php:131
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:39
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition Xml.php:578
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const NS_USER
Definition Defines.php:58
const NS_FILE
Definition Defines.php:62
const NS_USER_TALK
Definition Defines.php:59
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition hooks.txt:1028
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1937
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition hooks.txt:1094
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:1950
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2685
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1135
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:886
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2900
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:1958
this hook is for auditing only $response
Definition hooks.txt:805
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition hooks.txt:78
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2534
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1595
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1734
$comment
$files
if( $limit) $timestamp
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
$batch
Definition linkcache.txt:23
title
$last
const DB_REPLICA
Definition defines.php:22
const DB_MASTER
Definition defines.php:23
if(!isset( $args[0])) $lang
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11