24 use Wikimedia\Assert\Assert;
46 throw new MWException( __METHOD__ .
' given a null title.' );
50 wfDebug( __METHOD__ .
' did not have a Config object passed to it' );
51 $config = MediaWikiServices::getInstance()->getMainConfig();
64 return MediaWikiServices::getInstance()->getRevisionStore();
89 $termMain = $termDb = $term;
93 $engine = MediaWikiServices::getInstance()->newSearchEngine();
94 $engine->setLimitOffset( 100 );
95 $engine->setNamespaces( [ $ns ] );
96 $results = $engine->searchArchiveTitle( $termMain );
97 if ( !$results->isOK() ) {
100 $results = $results->getValue();
109 $condTitles = array_unique( array_map(
function (
Title $t ) {
110 return $t->getDBkey();
113 'ar_namespace' => $ns,
114 $dbr->makeList( [
'ar_title' => $condTitles ],
LIST_OR ) .
" OR ar_title " .
115 $dbr->buildLike( $termDb,
$dbr->anyString() )
143 'ar_namespace' => $ns,
144 'ar_title' .
$dbr->buildLike( $prefix,
$dbr->anyString() ),
161 'count' =>
'COUNT(*)'
166 'GROUP BY' => [
'ar_namespace',
'ar_title' ],
167 'ORDER BY' => [
'ar_namespace',
'ar_title' ],
181 $queryInfo = $revisionStore->getArchiveQueryInfo();
184 'ar_namespace' => $this->title->getNamespace(),
185 'ar_title' => $this->title->getDBkey(),
191 $options = [
'ORDER BY' =>
'ar_timestamp DESC, ar_id DESC' ];
194 $queryInfo[
'tables'],
195 $queryInfo[
'fields'],
204 $queryInfo[
'tables'],
205 $queryInfo[
'fields'],
222 if ( $this->title->getNamespace() !=
NS_FILE ) {
229 $fileQuery[
'tables'],
230 $fileQuery[
'fields'],
231 [
'fa_name' => $this->title->getDBkey() ],
233 [
'ORDER BY' =>
'fa_timestamp DESC' ],
249 [
'ar_timestamp' =>
$dbr->timestamp( $timestamp ) ]
251 return $rec ?
new Revision( $rec ) :
null;
262 Assert::parameterType(
'integer', $revId,
'$revId' );
265 return $rec ?
new Revision( $rec ) :
null;
278 $conditions = $conditions + [
279 'ar_namespace' => $this->title->getNamespace(),
280 'ar_title' => $this->title->getDBkey(),
283 $row =
$dbr->selectRow(
293 return $this->
getRevisionStore()->newRevisionFromArchiveRow( $row, 0, $this->title );
313 $row =
$dbr->selectRow(
'archive',
314 [
'ar_rev_id',
'ar_timestamp' ],
315 [
'ar_namespace' => $this->title->getNamespace(),
316 'ar_title' => $this->title->getDBkey(),
318 $dbr->addQuotes(
$dbr->timestamp( $timestamp ) ) ],
321 'ORDER BY' =>
'ar_timestamp DESC',
323 $prevDeleted = $row ?
wfTimestamp( TS_MW, $row->ar_timestamp ) :
false;
324 $prevDeletedId = $row ? intval( $row->ar_rev_id ) :
null;
326 $row =
$dbr->selectRow( [
'page',
'revision' ],
327 [
'rev_id',
'rev_timestamp' ],
329 'page_namespace' => $this->title->getNamespace(),
330 'page_title' => $this->title->getDBkey(),
331 'page_id = rev_page',
333 $dbr->addQuotes(
$dbr->timestamp( $timestamp ) ) ],
336 'ORDER BY' =>
'rev_timestamp DESC',
338 $prevLive = $row ?
wfTimestamp( TS_MW, $row->rev_timestamp ) :
false;
339 $prevLiveId = $row ? intval( $row->rev_id ) :
null;
341 if ( $prevLive && $prevLive > $prevDeleted ) {
344 $rec = $rec ?
new Revision( $rec ) :
null;
345 } elseif ( $prevDeleted ) {
362 $revId =
$dbr->selectField(
365 [
'ar_namespace' => $this->title->getNamespace(),
366 'ar_title' => $this->title->getDBkey() ],
368 [
'ORDER BY' =>
'ar_timestamp DESC, ar_id DESC' ]
371 return $revId ? intval( $revId ) :
false;
382 $row =
$dbr->selectRow(
385 [
'ar_namespace' => $this->title->getNamespace(),
386 'ar_title' => $this->title->getDBkey() ],
412 public function undelete( $timestamps, $comment =
'', $fileVersions = [],
413 $unsuppress =
false,
User $user =
null, $tags =
null
417 $restoreAll = empty( $timestamps ) && empty( $fileVersions );
419 $restoreText = $restoreAll || !empty( $timestamps );
420 $restoreFiles = $restoreAll || !empty( $fileVersions );
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() ) {
431 $filesRestored = $this->fileStatus->successCount;
436 if ( $restoreText ) {
437 $this->revisionStatus = $this->
undeleteRevisions( $timestamps, $unsuppress, $comment );
438 if ( !$this->revisionStatus->isOK() ) {
442 $textRestored = $this->revisionStatus->getValue();
449 if ( !$textRestored && !$filesRestored ) {
450 wfDebug(
"Undelete: nothing undeleted...\n" );
455 if ( $user ===
null ) {
461 $logEntry->setPerformer( $user );
462 $logEntry->setTarget( $this->title );
463 $logEntry->setComment( $comment );
464 $logEntry->addTags( $tags );
465 $logEntry->setParameters( [
467 'revisions' => $textRestored,
468 'files' => $filesRestored,
472 Hooks::run(
'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] );
474 $logid = $logEntry->insert();
475 $logEntry->publish( $logid );
477 return [ $textRestored, $filesRestored, $comment ];
497 $dbw->startAtomic( __METHOD__ );
499 $restoreAll = empty( $timestamps );
501 # Does this page already exist? We'll have to update it...
503 # Load latest data for the current page (T33179)
504 $article->loadPageData(
'fromdbmaster' );
505 $oldcountable = $article->isCountable();
507 $page = $dbw->selectRow(
'page',
508 [
'page_id',
'page_latest' ],
509 [
'page_namespace' => $this->title->getNamespace(),
510 'page_title' => $this->title->getDBkey() ],
517 # Page already exists. Import the history, and if necessary
518 # we'll update the latest revision field in the record.
520 # Get the time span of this page
521 $previousTimestamp = $dbw->selectField(
'revision',
'rev_timestamp',
522 [
'rev_id' => $page->page_latest ],
525 if ( $previousTimestamp ===
false ) {
526 wfDebug( __METHOD__ .
": existing page refers to a page_latest that does not exist\n" );
529 $status->warning(
'undeleterevision-missing' );
530 $dbw->endAtomic( __METHOD__ );
535 # Have to create a new article...
537 $previousTimestamp = 0;
541 'ar_namespace' => $this->title->getNamespace(),
542 'ar_title' => $this->title->getDBkey(),
544 if ( !$restoreAll ) {
545 $oldWhere[
'ar_timestamp'] = array_map( [ &$dbw,
'timestamp' ], $timestamps );
549 $queryInfo = $revisionStore->getArchiveQueryInfo();
550 $queryInfo[
'tables'][] =
'revision';
551 $queryInfo[
'fields'][] =
'rev_id';
552 $queryInfo[
'joins'][
'revision'] = [
'LEFT JOIN',
'ar_rev_id=rev_id' ];
557 $result = $dbw->select(
558 $queryInfo[
'tables'],
559 $queryInfo[
'fields'],
563 [
'ORDER BY' =>
'ar_timestamp' ],
567 $rev_count = $result->numRows();
569 wfDebug( __METHOD__ .
": no revisions to restore\n" );
572 $status->warning(
"undelete-no-results" );
573 $dbw->endAtomic( __METHOD__ );
580 $restoreFailedArIds = [];
587 $allowedRevIdToArIdMap = [];
589 $latestRestorableRow =
null;
591 foreach ( $result as $row ) {
592 if ( $row->ar_rev_id ) {
594 if ( $row->ar_rev_id === $row->rev_id ) {
595 $restoreFailedArIds[] = $row->ar_id;
596 $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
601 if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
602 $restoreFailedArIds[] = $row->ar_id;
604 $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
605 $latestRestorableRow = $row;
611 $latestRestorableRow = $row;
618 if ( $latestRestorableRow !==
null ) {
619 $oldPageId = (int)$latestRestorableRow->ar_page_id;
624 $revision = $revisionStore->newRevisionFromArchiveRow(
625 $latestRestorableRow,
632 $user =
User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false );
634 foreach ( $revision->getSlotRoles() as $role ) {
635 $content = $revision->getContent( $role, RevisionRecord::RAW );
640 $dbw->endAtomic( __METHOD__ );
653 if ( $latestRestorableRow ===
null ) {
654 $failedRevisionCount = $rev_count;
659 && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
661 $dbw->endAtomic( __METHOD__ );
666 $newid = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
667 if ( $newid ===
false ) {
669 $newid = $article->insertOn( $dbw );
674 if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
677 && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
679 $dbw->endAtomic( __METHOD__ );
686 $pageId = $article->getId();
689 foreach ( $result as $row ) {
691 if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
696 $revision = $revisionStore->newRevisionFromArchiveRow(
701 'page_id' => $pageId,
702 'deleted' => $unsuppress ? 0 : $row->ar_deleted
707 $revisionStore->insertRevisionOn( $revision, $dbw );
711 $legacyRevision =
new Revision( $revision );
713 [ &$this->title, $legacyRevision, $row->ar_page_id ] );
714 $restoredPages[$row->ar_page_id] =
true;
719 $toDeleteConds = $oldWhere;
720 $failedRevisionCount = count( $restoreFailedArIds );
721 if ( $failedRevisionCount > 0 ) {
722 $toDeleteConds[] =
'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) .
' )';
725 $dbw->delete(
'archive',
732 if ( $failedRevisionCount > 0 ) {
734 wfMessage(
'undeleterevision-duplicate-revid', $failedRevisionCount ) );
739 $created = (bool)$newid;
742 $wasnew = $article->updateIfNewerOn( $dbw, $legacyRevision );
743 if ( $created || $wasnew ) {
746 $article->doEditUpdates(
748 User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false ),
750 'created' => $created,
751 'oldcountable' => $oldcountable,
758 [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
760 if ( $this->title->getNamespace() ==
NS_FILE ) {
764 [
'causeAction' =>
'file-restore' ]
770 $dbw->endAtomic( __METHOD__ );