MediaWiki  master
PageArchive.php
Go to the documentation of this file.
1 <?php
27 
31 class PageArchive {
33  protected $title;
34 
36  protected $fileStatus;
37 
39  protected $revisionStatus;
40 
42  protected $config;
43 
44  public function __construct( $title, Config $config = null ) {
45  if ( is_null( $title ) ) {
46  throw new MWException( __METHOD__ . ' given a null title.' );
47  }
48  $this->title = $title;
49  if ( $config === null ) {
50  wfDebug( __METHOD__ . ' did not have a Config object passed to it' );
51  $config = MediaWikiServices::getInstance()->getMainConfig();
52  }
53  $this->config = $config;
54  }
55 
59  private function getRevisionStore() {
60  // TODO: Refactor: delete()/undelete() should live in a PageStore service;
61  // Methods in PageArchive and RevisionStore that deal with archive revisions
62  // should move into an ArchiveStore service (but could still be implemented
63  // together with RevisionStore).
64  return MediaWikiServices::getInstance()->getRevisionStore();
65  }
66 
67  public function doesWrites() {
68  return true;
69  }
70 
79  public static function listPagesBySearch( $term ) {
80  $title = Title::newFromText( $term );
81  if ( $title ) {
82  $ns = $title->getNamespace();
83  $termMain = $title->getText();
84  $termDb = $title->getDBkey();
85  } else {
86  // Prolly won't work too good
87  // @todo handle bare namespace names cleanly?
88  $ns = 0;
89  $termMain = $termDb = $term;
90  }
91 
92  // Try search engine first
93  $engine = MediaWikiServices::getInstance()->newSearchEngine();
94  $engine->setLimitOffset( 100 );
95  $engine->setNamespaces( [ $ns ] );
96  $results = $engine->searchArchiveTitle( $termMain );
97  if ( !$results->isOK() ) {
98  $results = [];
99  } else {
100  $results = $results->getValue();
101  }
102 
103  if ( !$results ) {
104  // Fall back to regular prefix search
105  return self::listPagesByPrefix( $term );
106  }
107 
108  $dbr = wfGetDB( DB_REPLICA );
109  $condTitles = array_unique( array_map( function ( Title $t ) {
110  return $t->getDBkey();
111  }, $results ) );
112  $conds = [
113  'ar_namespace' => $ns,
114  $dbr->makeList( [ 'ar_title' => $condTitles ], LIST_OR ) . " OR ar_title " .
115  $dbr->buildLike( $termDb, $dbr->anyString() )
116  ];
117 
118  return self::listPages( $dbr, $conds );
119  }
120 
129  public static function listPagesByPrefix( $prefix ) {
130  $dbr = wfGetDB( DB_REPLICA );
131 
132  $title = Title::newFromText( $prefix );
133  if ( $title ) {
134  $ns = $title->getNamespace();
135  $prefix = $title->getDBkey();
136  } else {
137  // Prolly won't work too good
138  // @todo handle bare namespace names cleanly?
139  $ns = 0;
140  }
141 
142  $conds = [
143  'ar_namespace' => $ns,
144  'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
145  ];
146 
147  return self::listPages( $dbr, $conds );
148  }
149 
155  protected static function listPages( $dbr, $condition ) {
156  return $dbr->select(
157  [ 'archive' ],
158  [
159  'ar_namespace',
160  'ar_title',
161  'count' => 'COUNT(*)'
162  ],
163  $condition,
164  __METHOD__,
165  [
166  'GROUP BY' => [ 'ar_namespace', 'ar_title' ],
167  'ORDER BY' => [ 'ar_namespace', 'ar_title' ],
168  'LIMIT' => 100,
169  ]
170  );
171  }
172 
179  public function listRevisions() {
180  $revisionStore = $this->getRevisionStore();
181  $queryInfo = $revisionStore->getArchiveQueryInfo();
182 
183  $conds = [
184  'ar_namespace' => $this->title->getNamespace(),
185  'ar_title' => $this->title->getDBkey(),
186  ];
187 
188  // NOTE: ordering by ar_timestamp and ar_id, to remove ambiguity.
189  // XXX: Ideally, we would be ordering by ar_timestamp and ar_rev_id, but since we
190  // don't have an index on ar_rev_id, that causes a file sort.
191  $options = [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ];
192 
194  $queryInfo['tables'],
195  $queryInfo['fields'],
196  $conds,
197  $queryInfo['joins'],
198  $options,
199  ''
200  );
201 
202  $dbr = wfGetDB( DB_REPLICA );
203  return $dbr->select(
204  $queryInfo['tables'],
205  $queryInfo['fields'],
206  $conds,
207  __METHOD__,
208  $options,
209  $queryInfo['joins']
210  );
211  }
212 
221  public function listFiles() {
222  if ( $this->title->getNamespace() != NS_FILE ) {
223  return null;
224  }
225 
226  $dbr = wfGetDB( DB_REPLICA );
227  $fileQuery = ArchivedFile::getQueryInfo();
228  return $dbr->select(
229  $fileQuery['tables'],
230  $fileQuery['fields'],
231  [ 'fa_name' => $this->title->getDBkey() ],
232  __METHOD__,
233  [ 'ORDER BY' => 'fa_timestamp DESC' ],
234  $fileQuery['joins']
235  );
236  }
237 
246  public function getRevision( $timestamp ) {
247  $dbr = wfGetDB( DB_REPLICA );
248  $rec = $this->getRevisionByConditions(
249  [ 'ar_timestamp' => $dbr->timestamp( $timestamp ) ]
250  );
251  return $rec ? new Revision( $rec ) : null;
252  }
253 
260  public function getArchivedRevision( $revId ) {
261  // Protect against code switching from getRevision() passing in a timestamp.
262  Assert::parameterType( 'integer', $revId, '$revId' );
263 
264  $rec = $this->getRevisionByConditions( [ 'ar_rev_id' => $revId ] );
265  return $rec ? new Revision( $rec ) : null;
266  }
267 
274  private function getRevisionByConditions( array $conditions, array $options = [] ) {
275  $dbr = wfGetDB( DB_REPLICA );
276  $arQuery = $this->getRevisionStore()->getArchiveQueryInfo();
277 
278  $conditions = $conditions + [
279  'ar_namespace' => $this->title->getNamespace(),
280  'ar_title' => $this->title->getDBkey(),
281  ];
282 
283  $row = $dbr->selectRow(
284  $arQuery['tables'],
285  $arQuery['fields'],
286  $conditions,
287  __METHOD__,
288  $options,
289  $arQuery['joins']
290  );
291 
292  if ( $row ) {
293  return $this->getRevisionStore()->newRevisionFromArchiveRow( $row, 0, $this->title );
294  }
295 
296  return null;
297  }
298 
309  public function getPreviousRevision( $timestamp ) {
310  $dbr = wfGetDB( DB_REPLICA );
311 
312  // Check the previous deleted revision...
313  $row = $dbr->selectRow( 'archive',
314  [ 'ar_rev_id', 'ar_timestamp' ],
315  [ 'ar_namespace' => $this->title->getNamespace(),
316  'ar_title' => $this->title->getDBkey(),
317  'ar_timestamp < ' .
318  $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
319  __METHOD__,
320  [
321  'ORDER BY' => 'ar_timestamp DESC',
322  'LIMIT' => 1 ] );
323  $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
324  $prevDeletedId = $row ? intval( $row->ar_rev_id ) : null;
325 
326  $row = $dbr->selectRow( [ 'page', 'revision' ],
327  [ 'rev_id', 'rev_timestamp' ],
328  [
329  'page_namespace' => $this->title->getNamespace(),
330  'page_title' => $this->title->getDBkey(),
331  'page_id = rev_page',
332  'rev_timestamp < ' .
333  $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
334  __METHOD__,
335  [
336  'ORDER BY' => 'rev_timestamp DESC',
337  'LIMIT' => 1 ] );
338  $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
339  $prevLiveId = $row ? intval( $row->rev_id ) : null;
340 
341  if ( $prevLive && $prevLive > $prevDeleted ) {
342  // Most prior revision was live
343  $rec = $this->getRevisionStore()->getRevisionById( $prevLiveId );
344  $rec = $rec ? new Revision( $rec ) : null;
345  } elseif ( $prevDeleted ) {
346  // Most prior revision was deleted
347  $rec = $this->getArchivedRevision( $prevDeletedId );
348  } else {
349  $rec = null;
350  }
351 
352  return $rec;
353  }
354 
360  public function getLastRevisionId() {
361  $dbr = wfGetDB( DB_REPLICA );
362  $revId = $dbr->selectField(
363  'archive',
364  'ar_rev_id',
365  [ 'ar_namespace' => $this->title->getNamespace(),
366  'ar_title' => $this->title->getDBkey() ],
367  __METHOD__,
368  [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ]
369  );
370 
371  return $revId ? intval( $revId ) : false;
372  }
373 
380  public function isDeleted() {
381  $dbr = wfGetDB( DB_REPLICA );
382  $row = $dbr->selectRow(
383  [ 'archive' ],
384  '1', // We don't care about the value. Allow the database to optimize.
385  [ 'ar_namespace' => $this->title->getNamespace(),
386  'ar_title' => $this->title->getDBkey() ],
387  __METHOD__
388  );
389 
390  return (bool)$row;
391  }
392 
412  public function undelete( $timestamps, $comment = '', $fileVersions = [],
413  $unsuppress = false, User $user = null, $tags = null
414  ) {
415  // If both the set of text revisions and file revisions are empty,
416  // restore everything. Otherwise, just restore the requested items.
417  $restoreAll = empty( $timestamps ) && empty( $fileVersions );
418 
419  $restoreText = $restoreAll || !empty( $timestamps );
420  $restoreFiles = $restoreAll || !empty( $fileVersions );
421 
422  if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
424  $img = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
425  ->newFile( $this->title );
426  $img->load( File::READ_LATEST );
427  $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
428  if ( !$this->fileStatus->isOK() ) {
429  return false;
430  }
431  $filesRestored = $this->fileStatus->successCount;
432  } else {
433  $filesRestored = 0;
434  }
435 
436  if ( $restoreText ) {
437  $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
438  if ( !$this->revisionStatus->isOK() ) {
439  return false;
440  }
441 
442  $textRestored = $this->revisionStatus->getValue();
443  } else {
444  $textRestored = 0;
445  }
446 
447  // Touch the log!
448 
449  if ( !$textRestored && !$filesRestored ) {
450  wfDebug( "Undelete: nothing undeleted...\n" );
451 
452  return false;
453  }
454 
455  if ( $user === null ) {
456  global $wgUser;
457  $user = $wgUser;
458  }
459 
460  $logEntry = new ManualLogEntry( 'delete', 'restore' );
461  $logEntry->setPerformer( $user );
462  $logEntry->setTarget( $this->title );
463  $logEntry->setComment( $comment );
464  $logEntry->addTags( $tags );
465  $logEntry->setParameters( [
466  ':assoc:count' => [
467  'revisions' => $textRestored,
468  'files' => $filesRestored,
469  ],
470  ] );
471 
472  Hooks::run( 'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] );
473 
474  $logid = $logEntry->insert();
475  $logEntry->publish( $logid );
476 
477  return [ $textRestored, $filesRestored, $comment ];
478  }
479 
491  private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
492  if ( wfReadOnly() ) {
493  throw new ReadOnlyError();
494  }
495 
496  $dbw = wfGetDB( DB_MASTER );
497  $dbw->startAtomic( __METHOD__ );
498 
499  $restoreAll = empty( $timestamps );
500 
501  # Does this page already exist? We'll have to update it...
502  $article = WikiPage::factory( $this->title );
503  # Load latest data for the current page (T33179)
504  $article->loadPageData( 'fromdbmaster' );
505  $oldcountable = $article->isCountable();
506 
507  $page = $dbw->selectRow( 'page',
508  [ 'page_id', 'page_latest' ],
509  [ 'page_namespace' => $this->title->getNamespace(),
510  'page_title' => $this->title->getDBkey() ],
511  __METHOD__,
512  [ 'FOR UPDATE' ] // lock page
513  );
514 
515  if ( $page ) {
516  $makepage = false;
517  # Page already exists. Import the history, and if necessary
518  # we'll update the latest revision field in the record.
519 
520  # Get the time span of this page
521  $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
522  [ 'rev_id' => $page->page_latest ],
523  __METHOD__ );
524 
525  if ( $previousTimestamp === false ) {
526  wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" );
527 
528  $status = Status::newGood( 0 );
529  $status->warning( 'undeleterevision-missing' );
530  $dbw->endAtomic( __METHOD__ );
531 
532  return $status;
533  }
534  } else {
535  # Have to create a new article...
536  $makepage = true;
537  $previousTimestamp = 0;
538  }
539 
540  $oldWhere = [
541  'ar_namespace' => $this->title->getNamespace(),
542  'ar_title' => $this->title->getDBkey(),
543  ];
544  if ( !$restoreAll ) {
545  $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
546  }
547 
548  $revisionStore = $this->getRevisionStore();
549  $queryInfo = $revisionStore->getArchiveQueryInfo();
550  $queryInfo['tables'][] = 'revision';
551  $queryInfo['fields'][] = 'rev_id';
552  $queryInfo['joins']['revision'] = [ 'LEFT JOIN', 'ar_rev_id=rev_id' ];
553 
557  $result = $dbw->select(
558  $queryInfo['tables'],
559  $queryInfo['fields'],
560  $oldWhere,
561  __METHOD__,
562  /* options */
563  [ 'ORDER BY' => 'ar_timestamp' ],
564  $queryInfo['joins']
565  );
566 
567  $rev_count = $result->numRows();
568  if ( !$rev_count ) {
569  wfDebug( __METHOD__ . ": no revisions to restore\n" );
570 
571  $status = Status::newGood( 0 );
572  $status->warning( "undelete-no-results" );
573  $dbw->endAtomic( __METHOD__ );
574 
575  return $status;
576  }
577 
578  // We use ar_id because there can be duplicate ar_rev_id even for the same
579  // page. In this case, we may be able to restore the first one.
580  $restoreFailedArIds = [];
581 
582  // Map rev_id to the ar_id that is allowed to use it. When checking later,
583  // if it doesn't match, the current ar_id can not be restored.
584 
585  // Value can be an ar_id or -1 (-1 means no ar_id can use it, since the
586  // rev_id is taken before we even start the restore).
587  $allowedRevIdToArIdMap = [];
588 
589  $latestRestorableRow = null;
590 
591  foreach ( $result as $row ) {
592  if ( $row->ar_rev_id ) {
593  // rev_id is taken even before we start restoring.
594  if ( $row->ar_rev_id === $row->rev_id ) {
595  $restoreFailedArIds[] = $row->ar_id;
596  $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
597  } else {
598  // rev_id is not taken yet in the DB, but it might be taken
599  // by a prior revision in the same restore operation. If
600  // not, we need to reserve it.
601  if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
602  $restoreFailedArIds[] = $row->ar_id;
603  } else {
604  $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
605  $latestRestorableRow = $row;
606  }
607  }
608  } else {
609  // If ar_rev_id is null, there can't be a collision, and a
610  // rev_id will be chosen automatically.
611  $latestRestorableRow = $row;
612  }
613  }
614 
615  $result->seek( 0 ); // move back
616 
617  $oldPageId = 0;
618  if ( $latestRestorableRow !== null ) {
619  $oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook
620 
621  // Grab the content to check consistency with global state before restoring the page.
622  // XXX: The only current use case is Wikibase, which tries to enforce uniqueness of
623  // certain things across all pages. There may be a better way to do that.
624  $revision = $revisionStore->newRevisionFromArchiveRow(
625  $latestRestorableRow,
626  0,
627  $this->title
628  );
629 
630  // TODO: use User::newFromUserIdentity from If610c68f4912e
631  // TODO: The User isn't used for anything in prepareSave()! We should drop it.
632  $user = User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false );
633 
634  foreach ( $revision->getSlotRoles() as $role ) {
635  $content = $revision->getContent( $role, RevisionRecord::RAW );
636 
637  // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
638  $status = $content->prepareSave( $article, 0, -1, $user );
639  if ( !$status->isOK() ) {
640  $dbw->endAtomic( __METHOD__ );
641 
642  return $status;
643  }
644  }
645  }
646 
647  $newid = false; // newly created page ID
648  $restored = 0; // number of revisions restored
650  $revision = null;
651  $restoredPages = [];
652  // If there are no restorable revisions, we can skip most of the steps.
653  if ( $latestRestorableRow === null ) {
654  $failedRevisionCount = $rev_count;
655  } else {
656  if ( $makepage ) {
657  // Check the state of the newest to-be version...
658  if ( !$unsuppress
659  && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
660  ) {
661  $dbw->endAtomic( __METHOD__ );
662 
663  return Status::newFatal( "undeleterevdel" );
664  }
665  // Safe to insert now...
666  $newid = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
667  if ( $newid === false ) {
668  // The old ID is reserved; let's pick another
669  $newid = $article->insertOn( $dbw );
670  }
671  $pageId = $newid;
672  } else {
673  // Check if a deleted revision will become the current revision...
674  if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
675  // Check the state of the newest to-be version...
676  if ( !$unsuppress
677  && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
678  ) {
679  $dbw->endAtomic( __METHOD__ );
680 
681  return Status::newFatal( "undeleterevdel" );
682  }
683  }
684 
685  $newid = false;
686  $pageId = $article->getId();
687  }
688 
689  foreach ( $result as $row ) {
690  // Check for key dupes due to needed archive integrity.
691  if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
692  continue;
693  }
694  // Insert one revision at a time...maintaining deletion status
695  // unless we are specifically removing all restrictions...
696  $revision = $revisionStore->newRevisionFromArchiveRow(
697  $row,
698  0,
699  $this->title,
700  [
701  'page_id' => $pageId,
702  'deleted' => $unsuppress ? 0 : $row->ar_deleted
703  ]
704  );
705 
706  // This will also copy the revision to ip_changes if it was an IP edit.
707  $revisionStore->insertRevisionOn( $revision, $dbw );
708 
709  $restored++;
710 
711  $legacyRevision = new Revision( $revision );
712  Hooks::run( 'ArticleRevisionUndeleted',
713  [ &$this->title, $legacyRevision, $row->ar_page_id ] );
714  $restoredPages[$row->ar_page_id] = true;
715  }
716 
717  // Now that it's safely stored, take it out of the archive
718  // Don't delete rows that we failed to restore
719  $toDeleteConds = $oldWhere;
720  $failedRevisionCount = count( $restoreFailedArIds );
721  if ( $failedRevisionCount > 0 ) {
722  $toDeleteConds[] = 'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) . ' )';
723  }
724 
725  $dbw->delete( 'archive',
726  $toDeleteConds,
727  __METHOD__ );
728  }
729 
730  $status = Status::newGood( $restored );
731 
732  if ( $failedRevisionCount > 0 ) {
733  $status->warning(
734  wfMessage( 'undeleterevision-duplicate-revid', $failedRevisionCount ) );
735  }
736 
737  // Was anything restored at all?
738  if ( $restored ) {
739  $created = (bool)$newid;
740  // Attach the latest revision to the page...
741  // XXX: updateRevisionOn should probably move into a PageStore service.
742  $wasnew = $article->updateIfNewerOn( $dbw, $legacyRevision );
743  if ( $created || $wasnew ) {
744  // Update site stats, link tables, etc
745  // TODO: use DerivedPageDataUpdater from If610c68f4912e!
746  $article->doEditUpdates(
747  $legacyRevision,
748  User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false ),
749  [
750  'created' => $created,
751  'oldcountable' => $oldcountable,
752  'restored' => true
753  ]
754  );
755  }
756 
757  Hooks::run( 'ArticleUndelete',
758  [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
759 
760  if ( $this->title->getNamespace() == NS_FILE ) {
762  $this->title,
763  'imagelinks',
764  [ 'causeAction' => 'file-restore' ]
765  );
766  JobQueueGroup::singleton()->lazyPush( $job );
767  }
768  }
769 
770  $dbw->endAtomic( __METHOD__ );
771 
772  return $status;
773  }
774 
778  public function getFileStatus() {
779  return $this->fileStatus;
780  }
781 
785  public function getRevisionStatus() {
786  return $this->revisionStatus;
787  }
788 }
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:772
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:998
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archivedfile object...
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static listPages( $dbr, $condition)
static listPagesBySearch( $term)
List deleted pages recorded in the archive matching the given term, using search engine archive...
Definition: PageArchive.php:79
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix. ...
listRevisions()
List the revisions of the given page.
const DB_MASTER
Definition: defines.php:26
Status $revisionStatus
Definition: PageArchive.php:39
Config $config
Definition: PageArchive.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
listFiles()
List the deleted file revisions for this page, if it&#39;s a file page.
wfReadOnly()
Check whether the wiki is in read-only mode.
Interface for configuration instances.
Definition: Config.php:28
Status $fileStatus
Definition: PageArchive.php:36
getDBkey()
Get the main part with underscores.
Definition: Title.php:1016
static newForBacklinks(Title $title, $table, $params=[])
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
isDeleted()
Quick check if any archived revisions are present for the page.
undeleteRevisions( $timestamps, $unsuppress=false, $comment='')
This is the meaty bit – It restores archived revisions of the given page to the revision table...
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1040
const NS_FILE
Definition: Defines.php:66
const LIST_OR
Definition: Defines.php:42
getLastRevisionId()
Returns the ID of the latest deleted revision.
getRevision( $timestamp)
Return a Revision object containing data for the deleted revision.
getArchivedRevision( $revId)
Return the archived revision with the given ID.
__construct( $title, Config $config=null)
Definition: PageArchive.php:44
if(count( $args)< 1) $job
Title $title
Definition: PageArchive.php:33
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:31
static singleton( $domain=false)
getPreviousRevision( $timestamp)
Return the most-previous revision, either live or deleted, against the deleted revision given by time...
const DB_REPLICA
Definition: defines.php:25
$content
Definition: router.php:78
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
getRevisionByConditions(array $conditions, array $options=[])
undelete( $timestamps, $comment='', $fileVersions=[], $unsuppress=false, User $user=null, $tags=null)
Restore the given (or all) text and file revisions for the page.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319