MediaWiki  master
PageArchive.php
Go to the documentation of this file.
1 <?php
29 use Psr\Log\LoggerInterface;
33 
37 class PageArchive {
38 
40  protected $title;
41 
43  protected $fileStatus;
44 
46  protected $revisionStatus;
47 
49  protected $config;
50 
52  private $hookRunner;
53 
55  private $jobQueueGroup;
56 
58  private $loadBalancer;
59 
61  private $logger;
62 
64  private $readOnlyMode;
65 
67  private $repoGroup;
68 
70  private $revisionStore;
71 
73  private $userFactory;
74 
77 
82  public function __construct( Title $title, Config $config = null ) {
83  $this->title = $title;
84 
85  $this->logger = LoggerFactory::getInstance( 'PageArchive' );
86 
87  $services = MediaWikiServices::getInstance();
88  if ( $config === null ) {
89  // TODO deprecate not passing a Config object, though technically this class is
90  // not @newable / stable to create
91  $this->logger->debug( 'Constructor did not have a Config object passed to it' );
92  $config = $services->getMainConfig();
93  }
94  $this->config = $config;
95 
96  $this->hookRunner = new HookRunner( $services->getHookContainer() );
97  $this->jobQueueGroup = $services->getJobQueueGroup();
98  $this->loadBalancer = $services->getDBLoadBalancer();
99  $this->readOnlyMode = $services->getReadOnlyMode();
100  $this->repoGroup = $services->getRepoGroup();
101 
102  // TODO: Refactor: delete()/undeleteAsUser() should live in a PageStore service;
103  // Methods in PageArchive and RevisionStore that deal with archive revisions
104  // should move into an ArchiveStore service (but could still be implemented
105  // together with RevisionStore).
106  $this->revisionStore = $services->getRevisionStore();
107 
108  $this->userFactory = $services->getUserFactory();
109  $this->wikiPageFactory = $services->getWikiPageFactory();
110  }
111 
112  public function doesWrites() {
113  return true;
114  }
115 
124  public static function listPagesBySearch( $term ) {
125  $title = Title::newFromText( $term );
126  if ( $title ) {
127  $ns = $title->getNamespace();
128  $termMain = $title->getText();
129  $termDb = $title->getDBkey();
130  } else {
131  // Prolly won't work too good
132  // @todo handle bare namespace names cleanly?
133  $ns = 0;
134  $termMain = $termDb = $term;
135  }
136 
137  // Try search engine first
138  $engine = MediaWikiServices::getInstance()->newSearchEngine();
139  $engine->setLimitOffset( 100 );
140  $engine->setNamespaces( [ $ns ] );
141  $results = $engine->searchArchiveTitle( $termMain );
142  if ( !$results->isOK() ) {
143  $results = [];
144  } else {
145  $results = $results->getValue();
146  }
147 
148  if ( !$results ) {
149  // Fall back to regular prefix search
150  return self::listPagesByPrefix( $term );
151  }
152 
153  $dbr = wfGetDB( DB_REPLICA );
154  $condTitles = array_unique( array_map( static function ( Title $t ) {
155  return $t->getDBkey();
156  }, $results ) );
157  $conds = [
158  'ar_namespace' => $ns,
159  $dbr->makeList( [ 'ar_title' => $condTitles ], LIST_OR ) . " OR ar_title " .
160  $dbr->buildLike( $termDb, $dbr->anyString() )
161  ];
162 
163  return self::listPages( $dbr, $conds );
164  }
165 
174  public static function listPagesByPrefix( $prefix ) {
175  $dbr = wfGetDB( DB_REPLICA );
176 
177  $title = Title::newFromText( $prefix );
178  if ( $title ) {
179  $ns = $title->getNamespace();
180  $prefix = $title->getDBkey();
181  } else {
182  // Prolly won't work too good
183  // @todo handle bare namespace names cleanly?
184  $ns = 0;
185  }
186 
187  $conds = [
188  'ar_namespace' => $ns,
189  'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
190  ];
191 
192  return self::listPages( $dbr, $conds );
193  }
194 
200  protected static function listPages( $dbr, $condition ) {
201  return $dbr->select(
202  [ 'archive' ],
203  [
204  'ar_namespace',
205  'ar_title',
206  'count' => 'COUNT(*)'
207  ],
208  $condition,
209  __METHOD__,
210  [
211  'GROUP BY' => [ 'ar_namespace', 'ar_title' ],
212  'ORDER BY' => [ 'ar_namespace', 'ar_title' ],
213  'LIMIT' => 100,
214  ]
215  );
216  }
217 
224  public function listRevisions() {
225  $queryInfo = $this->revisionStore->getArchiveQueryInfo();
226 
227  $conds = [
228  'ar_namespace' => $this->title->getNamespace(),
229  'ar_title' => $this->title->getDBkey(),
230  ];
231 
232  // NOTE: ordering by ar_timestamp and ar_id, to remove ambiguity.
233  // XXX: Ideally, we would be ordering by ar_timestamp and ar_rev_id, but since we
234  // don't have an index on ar_rev_id, that causes a file sort.
235  $options = [ 'ORDER BY' => [ 'ar_timestamp DESC', 'ar_id DESC' ] ];
236 
238  $queryInfo['tables'],
239  $queryInfo['fields'],
240  $conds,
241  $queryInfo['joins'],
242  $options,
243  ''
244  );
245 
246  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
247  return $dbr->select(
248  $queryInfo['tables'],
249  $queryInfo['fields'],
250  $conds,
251  __METHOD__,
252  $options,
253  $queryInfo['joins']
254  );
255  }
256 
265  public function listFiles() {
266  if ( $this->title->getNamespace() !== NS_FILE ) {
267  return null;
268  }
269 
270  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
271  $fileQuery = ArchivedFile::getQueryInfo();
272  return $dbr->select(
273  $fileQuery['tables'],
274  $fileQuery['fields'],
275  [ 'fa_name' => $this->title->getDBkey() ],
276  __METHOD__,
277  [ 'ORDER BY' => 'fa_timestamp DESC' ],
278  $fileQuery['joins']
279  );
280  }
281 
290  public function getRevisionRecordByTimestamp( $timestamp ) {
291  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
292  $rec = $this->getRevisionByConditions(
293  [ 'ar_timestamp' => $dbr->timestamp( $timestamp ) ]
294  );
295  return $rec;
296  }
297 
306  public function getArchivedRevisionRecord( int $revId ) {
307  return $this->getRevisionByConditions( [ 'ar_rev_id' => $revId ] );
308  }
309 
316  private function getRevisionByConditions( array $conditions, array $options = [] ) {
317  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
318  $arQuery = $this->revisionStore->getArchiveQueryInfo();
319 
320  $conditions += [
321  'ar_namespace' => $this->title->getNamespace(),
322  'ar_title' => $this->title->getDBkey(),
323  ];
324 
325  $row = $dbr->selectRow(
326  $arQuery['tables'],
327  $arQuery['fields'],
328  $conditions,
329  __METHOD__,
330  $options,
331  $arQuery['joins']
332  );
333 
334  if ( $row ) {
335  return $this->revisionStore->newRevisionFromArchiveRow( $row, 0, $this->title );
336  }
337 
338  return null;
339  }
340 
353  public function getPreviousRevisionRecord( string $timestamp ) {
354  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
355 
356  // Check the previous deleted revision...
357  $row = $dbr->selectRow( 'archive',
358  [ 'ar_rev_id', 'ar_timestamp' ],
359  [ 'ar_namespace' => $this->title->getNamespace(),
360  'ar_title' => $this->title->getDBkey(),
361  'ar_timestamp < ' .
362  $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
363  __METHOD__,
364  [
365  'ORDER BY' => 'ar_timestamp DESC',
366  ] );
367  $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
368  $prevDeletedId = $row ? intval( $row->ar_rev_id ) : null;
369 
370  $row = $dbr->selectRow( [ 'page', 'revision' ],
371  [ 'rev_id', 'rev_timestamp' ],
372  [
373  'page_namespace' => $this->title->getNamespace(),
374  'page_title' => $this->title->getDBkey(),
375  'page_id = rev_page',
376  'rev_timestamp < ' .
377  $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
378  __METHOD__,
379  [
380  'ORDER BY' => 'rev_timestamp DESC',
381  ] );
382  $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
383  $prevLiveId = $row ? intval( $row->rev_id ) : null;
384 
385  if ( $prevLive && $prevLive > $prevDeleted ) {
386  // Most prior revision was live
387  $rec = $this->revisionStore->getRevisionById( $prevLiveId );
388  } elseif ( $prevDeleted ) {
389  // Most prior revision was deleted
390  $rec = $this->getArchivedRevisionRecord( $prevDeletedId );
391  } else {
392  $rec = null;
393  }
394 
395  return $rec;
396  }
397 
403  public function getLastRevisionId() {
404  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
405  $revId = $dbr->selectField(
406  'archive',
407  'ar_rev_id',
408  [ 'ar_namespace' => $this->title->getNamespace(),
409  'ar_title' => $this->title->getDBkey() ],
410  __METHOD__,
411  [ 'ORDER BY' => [ 'ar_timestamp DESC', 'ar_id DESC' ] ]
412  );
413 
414  return $revId ? intval( $revId ) : false;
415  }
416 
423  public function isDeleted() {
424  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
425  $row = $dbr->selectRow(
426  [ 'archive' ],
427  '1', // We don't care about the value. Allow the database to optimize.
428  [ 'ar_namespace' => $this->title->getNamespace(),
429  'ar_title' => $this->title->getDBkey() ],
430  __METHOD__
431  );
432 
433  return (bool)$row;
434  }
435 
457  public function undeleteAsUser(
458  $timestamps,
459  UserIdentity $user,
460  $comment = '',
461  $fileVersions = [],
462  $unsuppress = false,
463  $tags = null
464  ) {
465  // If both the set of text revisions and file revisions are empty,
466  // restore everything. Otherwise, just restore the requested items.
467  $restoreAll = empty( $timestamps ) && empty( $fileVersions );
468 
469  $restoreText = $restoreAll || !empty( $timestamps );
470  $restoreFiles = $restoreAll || !empty( $fileVersions );
471 
472  if ( $restoreFiles && $this->title->getNamespace() === NS_FILE ) {
474  $img = $this->repoGroup->getLocalRepo()->newFile( $this->title );
475  $img->load( File::READ_LATEST );
476  $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
477  if ( !$this->fileStatus->isOK() ) {
478  return false;
479  }
480  $filesRestored = $this->fileStatus->successCount;
481  } else {
482  $filesRestored = 0;
483  }
484 
485  if ( $restoreText ) {
486  $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
487  if ( !$this->revisionStatus->isOK() ) {
488  return false;
489  }
490 
491  $textRestored = $this->revisionStatus->getValue();
492  } else {
493  $textRestored = 0;
494  }
495 
496  // Touch the log!
497 
498  if ( !$textRestored && !$filesRestored ) {
499  $this->logger->debug( "Undelete: nothing undeleted..." );
500 
501  return false;
502  }
503 
504  $logEntry = new ManualLogEntry( 'delete', 'restore' );
505  $logEntry->setPerformer( $user );
506  $logEntry->setTarget( $this->title );
507  $logEntry->setComment( $comment );
508  $logEntry->addTags( $tags );
509  $logEntry->setParameters( [
510  ':assoc:count' => [
511  'revisions' => $textRestored,
512  'files' => $filesRestored,
513  ],
514  ] );
515 
516  $legacyUser = $this->userFactory->newFromUserIdentity( $user );
517  $this->hookRunner->onArticleUndeleteLogEntry( $this, $logEntry, $legacyUser );
518 
519  $logid = $logEntry->insert();
520  $logEntry->publish( $logid );
521 
522  return [ $textRestored, $filesRestored, $comment ];
523  }
524 
536  private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
537  if ( $this->readOnlyMode->isReadOnly() ) {
538  throw new ReadOnlyError();
539  }
540 
541  $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
542  $dbw->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
543 
544  $restoreAll = empty( $timestamps );
545 
546  $oldWhere = [
547  'ar_namespace' => $this->title->getNamespace(),
548  'ar_title' => $this->title->getDBkey(),
549  ];
550  if ( !$restoreAll ) {
551  $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
552  }
553 
555  $queryInfo = $revisionStore->getArchiveQueryInfo();
556  $queryInfo['tables'][] = 'revision';
557  $queryInfo['fields'][] = 'rev_id';
558  $queryInfo['joins']['revision'] = [ 'LEFT JOIN', 'ar_rev_id=rev_id' ];
559 
563  $result = $dbw->select(
564  $queryInfo['tables'],
565  $queryInfo['fields'],
566  $oldWhere,
567  __METHOD__,
568  /* options */
569  [ 'ORDER BY' => 'ar_timestamp' ],
570  $queryInfo['joins']
571  );
572 
573  $rev_count = $result->numRows();
574  if ( !$rev_count ) {
575  $this->logger->debug( __METHOD__ . ": no revisions to restore" );
576 
577  $status = Status::newGood( 0 );
578  $status->warning( "undelete-no-results" );
579  $dbw->endAtomic( __METHOD__ );
580 
581  return $status;
582  }
583 
584  // We use ar_id because there can be duplicate ar_rev_id even for the same
585  // page. In this case, we may be able to restore the first one.
586  $restoreFailedArIds = [];
587 
588  // Map rev_id to the ar_id that is allowed to use it. When checking later,
589  // if it doesn't match, the current ar_id can not be restored.
590 
591  // Value can be an ar_id or -1 (-1 means no ar_id can use it, since the
592  // rev_id is taken before we even start the restore).
593  $allowedRevIdToArIdMap = [];
594 
595  $latestRestorableRow = null;
596 
597  foreach ( $result as $row ) {
598  if ( $row->ar_rev_id ) {
599  // rev_id is taken even before we start restoring.
600  if ( $row->ar_rev_id === $row->rev_id ) {
601  $restoreFailedArIds[] = $row->ar_id;
602  $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
603  } else {
604  // rev_id is not taken yet in the DB, but it might be taken
605  // by a prior revision in the same restore operation. If
606  // not, we need to reserve it.
607  if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
608  $restoreFailedArIds[] = $row->ar_id;
609  } else {
610  $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
611  $latestRestorableRow = $row;
612  }
613  }
614  } else {
615  // If ar_rev_id is null, there can't be a collision, and a
616  // rev_id will be chosen automatically.
617  $latestRestorableRow = $row;
618  }
619  }
620 
621  $result->seek( 0 ); // move back
622 
623  $article = $this->wikiPageFactory->newFromTitle( $this->title );
624 
625  $oldPageId = 0;
627  $revision = null;
628  $created = true;
629  $oldcountable = false;
630  $updatedCurrentRevision = false;
631  $restored = 0; // number of revisions restored
632  $restoredPages = [];
633 
634  // If there are no restorable revisions, we can skip most of the steps.
635  if ( $latestRestorableRow === null ) {
636  $failedRevisionCount = $rev_count;
637  } else {
638  $oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook
639 
640  // Grab the content to check consistency with global state before restoring the page.
641  // XXX: The only current use case is Wikibase, which tries to enforce uniqueness of
642  // certain things across all pages. There may be a better way to do that.
644  $latestRestorableRow,
645  0,
646  $this->title
647  );
648 
649  // TODO: use UserFactory::newFromUserIdentity from If610c68f4912e
650  // TODO: The User isn't used for anything in prepareSave()! We should drop it.
651  $user = $this->userFactory->newFromName(
652  $revision->getUser( RevisionRecord::RAW )->getName(),
653  UserFactory::RIGOR_NONE
654  );
655 
656  foreach ( $revision->getSlotRoles() as $role ) {
657  $content = $revision->getContent( $role, RevisionRecord::RAW );
658 
659  // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
660  $status = $content->prepareSave( $article, 0, -1, $user );
661  if ( !$status->isOK() ) {
662  $dbw->endAtomic( __METHOD__ );
663 
664  return $status;
665  }
666  }
667 
668  $pageId = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
669  if ( $pageId === false ) {
670  // The page ID is reserved; let's pick another
671  $pageId = $article->insertOn( $dbw );
672  if ( $pageId === false ) {
673  // The page title must be already taken (race condition)
674  $created = false;
675  }
676  }
677 
678  # Does this page already exist? We'll have to update it...
679  if ( !$created ) {
680  # Load latest data for the current page (T33179)
681  $article->loadPageData( WikiPage::READ_EXCLUSIVE );
682  $pageId = $article->getId();
683  $oldcountable = $article->isCountable();
684 
685  $previousTimestamp = false;
686  $latestRevId = $article->getLatest();
687  if ( $latestRevId ) {
688  $previousTimestamp = $revisionStore->getTimestampFromId(
689  $latestRevId,
690  RevisionStore::READ_LATEST
691  );
692  }
693  if ( $previousTimestamp === false ) {
694  $this->logger->debug( __METHOD__ . ": existing page refers to a page_latest that does not exist" );
695 
696  $status = Status::newGood( 0 );
697  $status->warning( 'undeleterevision-missing' );
698  $dbw->cancelAtomic( __METHOD__ );
699 
700  return $status;
701  }
702  } else {
703  $previousTimestamp = 0;
704  }
705 
706  // Check if a deleted revision will become the current revision...
707  if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
708  // Check the state of the newest to-be version...
709  if ( !$unsuppress
710  && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
711  ) {
712  $dbw->cancelAtomic( __METHOD__ );
713 
714  return Status::newFatal( "undeleterevdel" );
715  }
716  $updatedCurrentRevision = true;
717  }
718 
719  foreach ( $result as $row ) {
720  // Check for key dupes due to needed archive integrity.
721  if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
722  continue;
723  }
724  // Insert one revision at a time...maintaining deletion status
725  // unless we are specifically removing all restrictions...
727  $row,
728  0,
729  $this->title,
730  [
731  'page_id' => $pageId,
732  'deleted' => $unsuppress ? 0 : $row->ar_deleted
733  ]
734  );
735 
736  // This will also copy the revision to ip_changes if it was an IP edit.
737  $revisionStore->insertRevisionOn( $revision, $dbw );
738 
739  $restored++;
740 
741  $this->hookRunner->onRevisionUndeleted( $revision, $row->ar_page_id );
742 
743  $restoredPages[$row->ar_page_id] = true;
744  }
745 
746  // Now that it's safely stored, take it out of the archive
747  // Don't delete rows that we failed to restore
748  $toDeleteConds = $oldWhere;
749  $failedRevisionCount = count( $restoreFailedArIds );
750  if ( $failedRevisionCount > 0 ) {
751  $toDeleteConds[] = 'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) . ' )';
752  }
753 
754  $dbw->delete( 'archive',
755  $toDeleteConds,
756  __METHOD__ );
757  }
758 
759  $status = Status::newGood( $restored );
760 
761  if ( $failedRevisionCount > 0 ) {
762  $status->warning(
763  wfMessage( 'undeleterevision-duplicate-revid', $failedRevisionCount ) );
764  }
765 
766  // Was anything restored at all?
767  if ( $restored ) {
768 
769  if ( $updatedCurrentRevision ) {
770  // Attach the latest revision to the page...
771  // XXX: updateRevisionOn should probably move into a PageStore service.
772  $wasnew = $article->updateRevisionOn(
773  $dbw,
774  $revision,
775  $created ? 0 : $article->getLatest()
776  );
777  } else {
778  $wasnew = false;
779  }
780 
781  if ( $created || $wasnew ) {
782  // Update site stats, link tables, etc
783  // TODO: use DerivedPageDataUpdater from If610c68f4912e!
784  // TODO use UserFactory::newFromUserIdentity
785  $article->doEditUpdates(
786  $revision,
787  $revision->getUser( RevisionRecord::RAW ),
788  [
789  'created' => $created,
790  'oldcountable' => $oldcountable,
791  'restored' => true
792  ]
793  );
794  }
795 
796  $this->hookRunner->onArticleUndelete(
797  $this->title, $created, $comment, $oldPageId, $restoredPages );
798 
799  if ( $this->title->getNamespace() === NS_FILE ) {
801  $this->title,
802  'imagelinks',
803  [ 'causeAction' => 'file-restore' ]
804  );
805  $this->jobQueueGroup->lazyPush( $job );
806  }
807  }
808 
809  $dbw->endAtomic( __METHOD__ );
810 
811  return $status;
812  }
813 
817  public function getFileStatus() {
818  return $this->fileStatus;
819  }
820 
824  public function getRevisionStatus() {
825  return $this->revisionStatus;
826  }
827 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:29
PageArchive\getRevisionRecordByTimestamp
getRevisionRecordByTimestamp( $timestamp)
Return a RevisionRecord object containing data for the deleted revision.
Definition: PageArchive.php:290
LIST_OR
const LIST_OR
Definition: Defines.php:46
PageArchive\$fileStatus
Status null $fileStatus
Definition: PageArchive.php:43
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:383
PageArchive\getFileStatus
getFileStatus()
Definition: PageArchive.php:817
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Revision\RevisionStore\newRevisionFromArchiveRow
newRevisionFromArchiveRow( $row, $queryFlags=0, PageIdentity $page=null, array $overrides=[])
Make a fake RevisionRecord object from an archive table row.
Definition: RevisionStore.php:1539
PageArchive\$revisionStatus
Status null $revisionStatus
Definition: PageArchive.php:46
PageArchive\__construct
__construct(Title $title, Config $config=null)
Definition: PageArchive.php:82
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:37
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:193
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
PageArchive\listPages
static listPages( $dbr, $condition)
Definition: PageArchive.php:200
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1691
ReadOnlyMode
A service class for fetching the wiki's current read-only mode.
Definition: ReadOnlyMode.php:11
PageArchive\$config
Config $config
Definition: PageArchive.php:49
MediaWiki\Revision\RevisionStore\getTimestampFromId
getTimestampFromId( $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: RevisionStore.php:2748
PageArchive\listPagesByPrefix
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix.
Definition: PageArchive.php:174
ArchivedFile\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archivedfile object.
Definition: ArchivedFile.php:247
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
PageArchive\listFiles
listFiles()
List the deleted file revisions for this page, if it's a file page.
Definition: PageArchive.php:265
PageArchive\$readOnlyMode
ReadOnlyMode $readOnlyMode
Definition: PageArchive.php:64
PageArchive\listPagesBySearch
static listPagesBySearch( $term)
List deleted pages recorded in the archive matching the given term, using search engine archive.
Definition: PageArchive.php:124
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
PageArchive\$wikiPageFactory
WikiPageFactory $wikiPageFactory
Definition: PageArchive.php:76
PageArchive\isDeleted
isDeleted()
Quick check if any archived revisions are present for the page.
Definition: PageArchive.php:423
$dbr
$dbr
Definition: testCompression.php:54
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
PageArchive\getArchivedRevisionRecord
getArchivedRevisionRecord(int $revId)
Return the archived revision with the given ID.
Definition: PageArchive.php:306
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:891
Config
Interface for configuration instances.
Definition: Config.php:30
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1060
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
PageArchive\$repoGroup
RepoGroup $repoGroup
Definition: PageArchive.php:67
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:26
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1069
PageArchive\undeleteRevisions
undeleteRevisions( $timestamps, $unsuppress=false, $comment='')
This is the meaty bit – It restores archived revisions of the given page to the revision table.
Definition: PageArchive.php:536
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2225
PageArchive\$userFactory
UserFactory $userFactory
Definition: PageArchive.php:73
PageArchive\listRevisions
listRevisions()
List the revisions of the given page.
Definition: PageArchive.php:224
PageArchive\getPreviousRevisionRecord
getPreviousRevisionRecord(string $timestamp)
Return the most-previous revision, either live or deleted, against the deleted revision given by time...
Definition: PageArchive.php:353
Page\WikiPageFactory
Definition: WikiPageFactory.php:20
PageArchive\getRevisionStatus
getRevisionStatus()
Definition: PageArchive.php:824
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
PageArchive\$loadBalancer
ILoadBalancer $loadBalancer
Definition: PageArchive.php:58
PageArchive\$jobQueueGroup
JobQueueGroup $jobQueueGroup
Definition: PageArchive.php:55
PageArchive\getLastRevisionId
getLastRevisionId()
Returns the ID of the latest deleted revision.
Definition: PageArchive.php:403
$content
$content
Definition: router.php:76
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
HTMLCacheUpdateJob\newForBacklinks
static newForBacklinks(PageReference $page, $table, $params=[])
Definition: HTMLCacheUpdateJob.php:61
PageArchive\doesWrites
doesWrites()
Definition: PageArchive.php:112
PageArchive\getRevisionByConditions
getRevisionByConditions(array $conditions, array $options=[])
Definition: PageArchive.php:316
PageArchive\$hookRunner
HookRunner $hookRunner
Definition: PageArchive.php:52
Title
Represents a title within MediaWiki.
Definition: Title.php:48
MediaWiki\Revision\RevisionStore\getArchiveQueryInfo
getArchiveQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new RevisionArchiveRecord o...
Definition: RevisionStore.php:2549
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:49
PageArchive\$revisionStore
RevisionStore $revisionStore
Definition: PageArchive.php:70
RepoGroup
Prioritized list of file repositories.
Definition: RepoGroup.php:32
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:44
PageArchive\$logger
LoggerInterface $logger
Definition: PageArchive.php:61
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:558
$t
$t
Definition: testCompression.php:74
MediaWiki\Revision\RevisionStore\insertRevisionOn
insertRevisionOn(RevisionRecord $rev, IDatabase $dbw)
Insert a new revision into the database, returning the new revision record on success and dies horrib...
Definition: RevisionStore.php:429
NS_FILE
const NS_FILE
Definition: Defines.php:70
PageArchive\$title
Title $title
Definition: PageArchive.php:40
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:41
Title\getText
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:1042
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
PageArchive\undeleteAsUser
undeleteAsUser( $timestamps, UserIdentity $user, $comment='', $fileVersions=[], $unsuppress=false, $tags=null)
Restore the given (or all) text and file revisions for the page.
Definition: PageArchive.php:457
JobQueueGroup
Class to handle enqueueing of background jobs.
Definition: JobQueueGroup.php:32