357 $row =
$dbr->selectRow(
'archive',
358 [
'ar_rev_id',
'ar_timestamp' ],
359 [
'ar_namespace' => $this->title->getNamespace(),
360 'ar_title' => $this->title->getDBkey(),
362 $dbr->addQuotes(
$dbr->timestamp( $timestamp ) ) ],
365 'ORDER BY' =>
'ar_timestamp DESC',
367 $prevDeleted = $row ?
wfTimestamp( TS_MW, $row->ar_timestamp ) :
false;
368 $prevDeletedId = $row ? intval( $row->ar_rev_id ) :
null;
370 $row =
$dbr->selectRow( [
'page',
'revision' ],
371 [
'rev_id',
'rev_timestamp' ],
373 'page_namespace' => $this->title->getNamespace(),
374 'page_title' => $this->title->getDBkey(),
375 'page_id = rev_page',
377 $dbr->addQuotes(
$dbr->timestamp( $timestamp ) ) ],
380 'ORDER BY' =>
'rev_timestamp DESC',
382 $prevLive = $row ?
wfTimestamp( TS_MW, $row->rev_timestamp ) :
false;
383 $prevLiveId = $row ? intval( $row->rev_id ) :
null;
385 if ( $prevLive && $prevLive > $prevDeleted ) {
387 $rec = $this->revisionStore->getRevisionById( $prevLiveId );
388 } elseif ( $prevDeleted ) {
465 $hookStatus = StatusValue::newGood();
466 $hookRes = $this->hookRunner->onPageUndelete(
467 $this->wikiPageFactory->newFromTitle( $this->title ),
468 $this->userFactory->newFromUserIdentity( $user ),
475 if ( !$hookRes && !$hookStatus->isGood() ) {
481 $restoreAll = empty( $timestamps ) && empty( $fileVersions );
483 $restoreText = $restoreAll || !empty( $timestamps );
484 $restoreFiles = $restoreAll || !empty( $fileVersions );
486 if ( $restoreFiles && $this->title->getNamespace() ===
NS_FILE ) {
488 $img = $this->repoGroup->getLocalRepo()->newFile( $this->title );
489 $img->load( File::READ_LATEST );
490 $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
491 if ( !$this->fileStatus->isOK() ) {
494 $filesRestored = $this->fileStatus->successCount;
499 if ( $restoreText ) {
500 $this->revisionStatus = $this->
undeleteRevisions( $timestamps, $unsuppress, $comment );
501 if ( !$this->revisionStatus->isOK() ) {
505 $textRestored = $this->revisionStatus->getValue();
512 if ( !$textRestored && !$filesRestored ) {
513 $this->logger->debug(
"Undelete: nothing undeleted..." );
519 $logEntry->setPerformer( $user );
520 $logEntry->setTarget( $this->title );
521 $logEntry->setComment( $comment );
522 $logEntry->addTags( $tags );
523 $logEntry->setParameters( [
525 'revisions' => $textRestored,
526 'files' => $filesRestored,
530 $legacyUser = $this->userFactory->newFromUserIdentity( $user );
531 $this->hookRunner->onArticleUndeleteLogEntry( $this, $logEntry, $legacyUser );
533 $logid = $logEntry->insert();
534 $logEntry->publish( $logid );
536 return [ $textRestored, $filesRestored, $comment ];
551 if ( $this->readOnlyMode->isReadOnly() ) {
555 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY );
556 $dbw->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
558 $restoreAll = empty( $timestamps );
561 'ar_namespace' => $this->title->getNamespace(),
562 'ar_title' => $this->title->getDBkey(),
564 if ( !$restoreAll ) {
565 $oldWhere[
'ar_timestamp'] = array_map( [ &$dbw,
'timestamp' ], $timestamps );
570 $queryInfo[
'tables'][] =
'revision';
571 $queryInfo[
'fields'][] =
'rev_id';
572 $queryInfo[
'joins'][
'revision'] = [
'LEFT JOIN',
'ar_rev_id=rev_id' ];
577 $result = $dbw->select(
578 $queryInfo[
'tables'],
579 $queryInfo[
'fields'],
583 [
'ORDER BY' =>
'ar_timestamp' ],
587 $rev_count = $result->numRows();
589 $this->logger->debug( __METHOD__ .
": no revisions to restore" );
591 $status = Status::newGood( 0 );
592 $status->warning(
"undelete-no-results" );
593 $dbw->endAtomic( __METHOD__ );
600 $restoreFailedArIds = [];
607 $allowedRevIdToArIdMap = [];
609 $latestRestorableRow =
null;
611 foreach ( $result as $row ) {
612 if ( $row->ar_rev_id ) {
614 if ( $row->ar_rev_id === $row->rev_id ) {
615 $restoreFailedArIds[] = $row->ar_id;
616 $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
621 if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
622 $restoreFailedArIds[] = $row->ar_id;
624 $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
625 $latestRestorableRow = $row;
631 $latestRestorableRow = $row;
637 $article = $this->wikiPageFactory->newFromTitle( $this->title );
643 $oldcountable =
false;
644 $updatedCurrentRevision =
false;
649 if ( $latestRestorableRow ===
null ) {
650 $failedRevisionCount = $rev_count;
652 $oldPageId = (int)$latestRestorableRow->ar_page_id;
658 $latestRestorableRow,
665 $user = $this->userFactory->newFromName(
666 $revision->getUser( RevisionRecord::RAW )->getName(),
667 UserFactory::RIGOR_NONE
670 foreach ( $revision->getSlotRoles() as $role ) {
671 $content = $revision->getContent( $role, RevisionRecord::RAW );
674 $status =
$content->prepareSave( $article, 0, -1, $user );
675 if ( !$status->isOK() ) {
676 $dbw->endAtomic( __METHOD__ );
682 $pageId = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
683 if ( $pageId ===
false ) {
685 $pageId = $article->insertOn( $dbw );
686 if ( $pageId ===
false ) {
692 # Does this page already exist? We'll have to update it...
694 # Load latest data for the current page (T33179)
695 $article->loadPageData( WikiPage::READ_EXCLUSIVE );
696 $pageId = $article->getId();
697 $oldcountable = $article->isCountable();
699 $previousTimestamp =
false;
700 $latestRevId = $article->getLatest();
701 if ( $latestRevId ) {
704 RevisionStore::READ_LATEST
707 if ( $previousTimestamp ===
false ) {
708 $this->logger->debug( __METHOD__ .
": existing page refers to a page_latest that does not exist" );
710 $status = Status::newGood( 0 );
711 $status->warning(
'undeleterevision-missing' );
712 $dbw->cancelAtomic( __METHOD__ );
717 $previousTimestamp = 0;
721 if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
724 && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
726 $dbw->cancelAtomic( __METHOD__ );
728 return Status::newFatal(
"undeleterevdel" );
730 $updatedCurrentRevision =
true;
733 foreach ( $result as $row ) {
735 if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
745 'page_id' => $pageId,
746 'deleted' => $unsuppress ? 0 : $row->ar_deleted
755 $this->hookRunner->onRevisionUndeleted( $revision, $row->ar_page_id );
757 $restoredPages[$row->ar_page_id] =
true;
762 $toDeleteConds = $oldWhere;
763 $failedRevisionCount = count( $restoreFailedArIds );
764 if ( $failedRevisionCount > 0 ) {
765 $toDeleteConds[] =
'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) .
' )';
768 $dbw->delete(
'archive',
773 $status = Status::newGood( $restored );
775 if ( $failedRevisionCount > 0 ) {
777 wfMessage(
'undeleterevision-duplicate-revid', $failedRevisionCount ) );
783 if ( $updatedCurrentRevision ) {
786 $wasnew = $article->updateRevisionOn(
789 $created ? 0 : $article->getLatest()
795 if ( $created || $wasnew ) {
799 $article->doEditUpdates(
801 $revision->getUser( RevisionRecord::RAW ),
803 'created' => $created,
804 'oldcountable' => $oldcountable,
810 $this->hookRunner->onArticleUndelete(
811 $this->title, $created, $comment, $oldPageId, $restoredPages );
813 if ( $this->title->getNamespace() ===
NS_FILE ) {
817 [
'causeAction' =>
'file-restore' ]
819 $this->jobQueueGroup->lazyPush(
$job );
823 $dbw->endAtomic( __METHOD__ );