| Current File : /home/jvzmxxx/wiki1/extensions/Flow/maintenance/FlowRestoreLQT.php |
<?php
use Flow\Container;
use Flow\DbFactory;
use Flow\Import\ArchiveNameHelper;
require_once ( getenv( 'MW_INSTALL_PATH' ) !== false
? getenv( 'MW_INSTALL_PATH' ) . '/maintenance/Maintenance.php'
: dirname( __FILE__ ) . '/../../../maintenance/Maintenance.php' );
class FlowRestoreLQT extends Maintenance {
/**
* @var User
*/
protected $talkpageManagerUser;
/**
* @var DbFactory
*/
protected $dbFactory;
/**
* @var bool
*/
protected $dryRun = false;
/**
* @var bool
*/
protected $overwrite = false;
public function __construct() {
parent::__construct();
$this->mDescription = 'Restores LQT boards after a Flow conversion (revert LQT conversion edits & move LQT boards back)';
$this->addOption( 'dryrun', 'Simulate script run, without making actual changes' );
$this->addOption( 'overwrite-flow', 'Removes the Flow board entirely, restoring LQT to its original location' );
$this->setBatchSize( 1 );
}
public function execute() {
$this->talkpageManagerUser = FlowHooks::getOccupationController()->getTalkpageManager();
$this->dbFactory = Container::get( 'db.factory' );
$this->dryRun = $this->getOption( 'dryrun', false );
$this->overwrite = $this->getOption( 'overwrite-flow', false );
$this->output( "Restoring posts...\n" );
$this->restoreLQTThreads();
$this->output( "Restoring boards...\n" );
$this->restoreLQTBoards();
}
/**
* During an import, LQT boards are moved out of the way (archived) to make
* place for the Flow board.
* And after completing an import, LQT boards are disabled with
* {{#useliquidthreads:0}}
* That's all perfectly fine assuming the conversion goes well, but we'll
* want to go back to the original content with this script...
*/
protected function restoreLQTBoards() {
$dbr = $this->dbFactory->getWikiDB( DB_SLAVE );
$startId = 0;
do {
// fetch all LQT boards that have been moved out of the way,
// with their original title & their current title
$rows = $dbr->select(
array( 'logging', 'page', 'revision' ),
// log_namespace & log_title will be the original location
// page_namespace & page_title will be the current location
// rev_id is the first Flow talk page manager edit id
// log_id is the log entry for when importer moved LQT page
array( 'log_namespace', 'log_title', 'page_id', 'page_namespace', 'page_title', 'rev_id' => 'MIN(rev_id)', 'log_id' ),
array(
'log_user' => $this->talkpageManagerUser->getId(),
'log_type' => 'move',
'page_content_model' => 'wikitext',
'page_id > ' . $dbr->addQuotes( $startId ),
),
__METHOD__,
array(
'GROUP BY' => 'rev_page',
'LIMIT' => $this->mBatchSize,
'ORDER BY' => 'log_id ASC',
),
array(
'page' => array(
'INNER JOIN',
array( 'page_id = log_page' ),
),
'revision' => array(
'INNER JOIN',
array( 'rev_page = log_page', 'rev_user = log_user' ),
),
)
);
foreach ( $rows as $row ) {
$from = Title::newFromText( $row->page_title, $row->page_namespace );
$to = Title::newFromText( $row->log_title, $row->log_namespace );
// undo {{#useliquidthreads:0}}
$this->restorePageRevision( $row->page_id, $row->rev_id );
// undo page move to archive location
$this->restoreLQTPage( $from, $to, $row->log_id );
$startId = $row->page_id;
}
wfWaitForSlaves();
} while ( $rows->numRows() >= $this->mBatchSize );
}
/**
* After converting an LQT thread to Flow, it's content is altered to
* redirect to the new Flow topic.
* This finds all last original revisions & restores them.
*/
protected function restoreLQTThreads() {
$dbr = $this->dbFactory->getWikiDB( DB_SLAVE );
$startId = 0;
do {
// for every LQT post, find the first edit by Flow talk page manager
// (to redirect to the new Flow copy)
$rows = $dbr->select(
array( 'page', 'revision' ),
array( 'rev_page', 'rev_id' => ' MIN(rev_id)' ),
array(
'page_namespace' => array( NS_LQT_THREAD, NS_LQT_SUMMARY ),
'rev_user' => $this->talkpageManagerUser->getId(),
'page_id > ' . $dbr->addQuotes( $startId ),
),
__METHOD__,
array(
'GROUP BY' => 'page_id',
'LIMIT' => $this->mBatchSize,
'ORDER BY' => 'page_id ASC',
),
array(
'revision' => array(
'INNER JOIN',
array( 'rev_page = page_id' ),
),
)
);
foreach ( $rows as $row ) {
// undo #REDIRECT edit
$this->restorePageRevision( $row->rev_page, $row->rev_id );
$startId = $row->rev_page;
}
wfWaitForSlaves();
} while ( $rows->numRows() >= $this->mBatchSize );
}
/**
* @param Title $lqt Title of the LQT board
* @param Title $flow Title of the Flow board
* @param int $logId Log id for when LQT board was moved by import
* @return Status
* @throws MWException
*/
protected function restoreLQTPage( Title $lqt, Title $flow, $logId ) {
if ( $lqt->equals( $flow ) ) {
// is at correct location already (probably a rerun of this script)
return Status::newGood();
}
$archiveNameHelper = new ArchiveNameHelper();
if ( !$flow->exists() ) {
$this->movePage( $lqt, $flow, '/* Restore LQT board to original location */' );
} else {
/*
* The importer will query the log table to find the LQT archive
* location. It will assume that Flow talk page manager moved the
* LQT board to its archive location, and will not recognize the
* board if it's been moved by someone else.
* Because of that feature (yes, that is intended), we need to make
* sure that - in order to enable LQT imports to be picked up again
* after this - the move from <original page> to <archive page>
* happens in 1 go, by Flow talk page manager.
*/
if ( !$this->overwrite ) {
/*
* Before we go moving pages around like crazy, let's see if we
* actually need to. While it's certainly possible that the LQT
* pages have been moved since the import and we need to fix
* them, it's very likely that they haven't. In that case, we
* won't have to do the complex moves.
*/
$dbr = $this->dbFactory->getWikiDB( DB_SLAVE );
$count = $dbr->selectRowCount(
array( 'logging' ),
'*',
array(
'log_page' => $lqt->getArticleID(),
'log_type' => 'move',
'log_id > ' . $dbr->addQuotes( $logId ),
),
__METHOD__
);
if ( $count > 0 ) {
$this->output( "Ensuring LQT board '{$lqt->getPrefixedDBkey()}' is recognized as archive of Flow board '{$flow->getPrefixedDBkey()}'.\n" );
// 1: move Flow board out of the way so we can restore LQT to
// its original location
$archive = $archiveNameHelper->decideArchiveTitle( $flow, array( '%s/Flow Archive %d' ) );
$this->movePage( $flow, $archive, '/* Make place to restore LQT board */' );
// 2: move LQT board to the original location
$this->movePage( $lqt, $flow, '/* Restore LQT board to original location */' );
// 3: move LQT board back to archive location
$this->movePage( $flow, $lqt, '/* Restore LQT board to archive location */' );
// 4: move Flow board back to the original location
$this->movePage( $archive, $flow, '/* Restore Flow board to correct location */' );
}
} else {
$this->output( "Deleting '{$flow->getPrefixedDBkey()}' & moving '{$lqt->getPrefixedDBkey()}' there.\n" );
if ( !$this->dryRun ) {
$page = WikiPage::factory( $flow );
$page->doDeleteArticleReal( '/* Make place to restore LQT board */', false, null, null, $error, $this->talkpageManagerUser );
}
$this->movePage( $lqt, $flow, '/* Restore LQT board to original location */' );
}
}
}
/**
* @param Title $from
* @param Title $to
* @param string $reason
* @return Status
*/
protected function movePage( Title $from, Title $to, $reason ) {
$this->output( " Moving '{$from->getPrefixedDBkey()}' to '{$to->getPrefixedDBkey()}'.\n" );
$movePage = new MovePage( $from, $to );
$status = $movePage->isValidMove();
if ( !$status->isGood() ) {
return $status;
}
if ( $this->dryRun ) {
return Status::newGood();
}
return $movePage->move( $this->talkpageManagerUser, $reason, false );
}
/**
* @param int $pageId
* @param int $nextRevisionId Revision of the first *bad* revision
* @return Status
* @throws MWException
*/
protected function restorePageRevision( $pageId, $nextRevisionId ) {
global $wgLang;
$page = WikiPage::newFromID( $pageId );
$revisionId = $page->getTitle()->getPreviousRevisionID( $nextRevisionId );
$revision = Revision::newFromPageId( $pageId, $revisionId );
if ( $page->getContent()->equals( $revision->getContent() ) ) {
// has correct content already (probably a rerun of this script)
return Status::newGood();
}
$content = $revision->getContent()->serialize();
$content = $wgLang->truncate( $content, 150 );
$content = str_replace( "\n", '\n', $content );
$this->output( "Restoring revision {$revisionId} for LQT page {$pageId}: {$content}\n" );
if ( $this->dryRun ) {
return Status::newGood();
} else {
return $page->doEditContent(
$revision->getContent( Revision::RAW ),
'/* Restore LQT topic content */',
EDIT_UPDATE | EDIT_MINOR | EDIT_FORCE_BOT,
$revision->getId(),
$this->talkpageManagerUser
);
}
}
}
$maintClass = 'FlowRestoreLQT';
require_once ( RUN_MAINTENANCE_IF_MAIN );