EBernhardson has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/174861

Change subject: [WIP] Archive and takeover namespaces with Flow
......................................................................

[WIP] Archive and takeover namespaces with Flow

Change-Id: Ie785403748cc22bc28ff52a172bfaafad8aaf18b
---
M autoload.php
M i18n/en.json
M i18n/qqq.json
A includes/Import/Plain/ImportHeader.php
A includes/Import/Plain/ObjectRevision.php
A includes/Import/Wikitext/Converter.php
A includes/Import/Wikitext/ImportSource.php
A includes/Utils/NamespaceIterator.php
A maintenance/convertNamespace.php
9 files changed, 395 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow 
refs/changes/61/174861/1

diff --git a/autoload.php b/autoload.php
index fa96909..36e372a 100644
--- a/autoload.php
+++ b/autoload.php
@@ -160,6 +160,7 @@
 $wgAutoloadClasses['Flow\\Formatter\\TopicListFormatter'] = __DIR__ . 
'/includes/Formatter/TopicListFormatter.php';
 $wgAutoloadClasses['Flow\\Formatter\\TopicListQuery'] = __DIR__ . 
'/includes/Formatter/TopicListQuery.php';
 $wgAutoloadClasses['Flow\\Formatter\\TopicRow'] = __DIR__ . 
'/includes/Formatter/TopicRow.php';
+$wgAutoloadClasses['Flow\\Import\\Basic\\ObjectRevision'] = __DIR__ . 
'/includes/Import/Plain/ObjectRevision.php';
 $wgAutoloadClasses['Flow\\Import\\FileImportSourceStore'] = __DIR__ . 
'/includes/Import/ImportSourceStore.php';
 $wgAutoloadClasses['Flow\\Import\\HistoricalUIDGenerator'] = __DIR__ . 
'/includes/Import/Importer.php';
 $wgAutoloadClasses['Flow\\Import\\IImportHeader'] = __DIR__ . 
'/includes/Import/ImportSource.php';
@@ -194,8 +195,11 @@
 $wgAutoloadClasses['Flow\\Import\\LiquidThreadsApi\\TopicIterator'] = __DIR__ 
. '/includes/Import/LiquidThreadsApi/Iterators.php';
 $wgAutoloadClasses['Flow\\Import\\NullImportSourceStore'] = __DIR__ . 
'/includes/Import/ImportSourceStore.php';
 $wgAutoloadClasses['Flow\\Import\\PageImportState'] = __DIR__ . 
'/includes/Import/Importer.php';
+$wgAutoloadClasses['Flow\\Import\\Plain\\ImportHeader'] = __DIR__ . 
'/includes/Import/Plain/ImportHeader.php';
 $wgAutoloadClasses['Flow\\Import\\TalkpageImportOperation'] = __DIR__ . 
'/includes/Import/Importer.php';
 $wgAutoloadClasses['Flow\\Import\\TopicImportState'] = __DIR__ . 
'/includes/Import/Importer.php';
+$wgAutoloadClasses['Flow\\Import\\Wikitext\\Converter'] = __DIR__ . 
'/includes/Import/Wikitext/Converter.php';
+$wgAutoloadClasses['Flow\\Import\\Wikitext\\ImportSource'] = __DIR__ . 
'/includes/Import/Wikitext/ImportSource.php';
 $wgAutoloadClasses['Flow\\LinksTableUpdater'] = __DIR__ . 
'/includes/LinksTableUpdater.php';
 $wgAutoloadClasses['Flow\\Log\\Formatter'] = __DIR__ . 
'/includes/Log/Formatter.php';
 $wgAutoloadClasses['Flow\\Log\\Logger'] = __DIR__ . '/includes/Log/Logger.php';
@@ -323,6 +327,7 @@
 $wgAutoloadClasses['Flow\\Tests\\UrlGeneratorTest'] = __DIR__ . 
'/tests/phpunit/UrlGeneratorTest.php';
 $wgAutoloadClasses['Flow\\Tests\\WatchedTopicItemTest'] = __DIR__ . 
'/tests/phpunit/WatchedTopicItemsTest.php';
 $wgAutoloadClasses['Flow\\UrlGenerator'] = __DIR__ . 
'/includes/UrlGenerator.php';
+$wgAutoloadClasses['Flow\\Utils\\NamespaceIterator'] = __DIR__ . 
'/includes/Utils/NamespaceIterator.php';
 $wgAutoloadClasses['Flow\\Utils\\PagesWithPropertyIterator'] = __DIR__ . 
'/includes/Utils/PagesWithPropertyIterator.php';
 $wgAutoloadClasses['Flow\\View'] = __DIR__ . '/includes/View.php';
 $wgAutoloadClasses['Flow\\WatchedTopicItems'] = __DIR__ . 
'/includes/WatchedTopicItems.php';
diff --git a/i18n/en.json b/i18n/en.json
index 369a75c..d18cd38 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -457,5 +457,6 @@
        "apihelp-query+flowinfo-description": "Get basic Flow information about 
a page.",
        "apihelp-query+flowinfo-example-1": "Fetch Flow information about 
[[Talk:Sandbox]], [[Main Page]], and [[Talk:Flow]]",
        "flow-edited": "Edited",
-       "flow-edited-by": "Edited by $1"
+       "flow-edited-by": "Edited by $1",
+       "flow-importer-wt-converted-template": "Wikitext talk page converted to 
Flow"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index cfd195b..045f4e5 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -461,5 +461,6 @@
        "apihelp-query+flowinfo-description": 
"{{doc-apihelp-description|query+flowinfo}}",
        "apihelp-query+flowinfo-example-1": 
"{{doc-apihelp-example|query+flowinfo}}",
        "flow-edited": "Message displayed below a post to indicate it has last 
been edited by the original author\n{{Identical|Edited}}",
-       "flow-edited-by": "Message displayed below a post to indicate it has 
last been edited by a user other than the original author"
+       "flow-edited-by": "Message displayed below a post to indicate it has 
last been edited by a user other than the original author",
+       "flow-importer-wt-converted-template": "Name of a wikitext template 
that is added to the header of flow boards that were converted from wikitext"
 }
diff --git a/includes/Import/Plain/ImportHeader.php 
b/includes/Import/Plain/ImportHeader.php
new file mode 100644
index 0000000..45d7a07
--- /dev/null
+++ b/includes/Import/Plain/ImportHeader.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Flow\Import\Plain;
+
+use ArrayIterator;
+use Flow\Import\IImportHeader;
+
+class ImportHeader implements IImportHeader {
+       public function __construct( array $revisions ) {
+               $this->revisions = $revisions;
+       }
+
+       public function getRevisions() {
+               return new ArrayIterator( $this->revisions );
+       }
+}
diff --git a/includes/Import/Plain/ObjectRevision.php 
b/includes/Import/Plain/ObjectRevision.php
new file mode 100644
index 0000000..b077326
--- /dev/null
+++ b/includes/Import/Plain/ObjectRevision.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Flow\Import\Basic;
+
+use Flow\Import\IObjectRevision;
+
+class ObjectRevision implements IObjectRevision {
+       protected $text;
+       protected $timestamp;
+       protected $author;
+
+       public function __construct( $text, $timestamp, $author ) {
+               $this->text = $text;
+               $this->timestamp = $timestamp;
+               $this->author = $author;
+       }
+
+       public function getText() {
+               return $this->text;
+       }
+
+       public function getTimestamp() {
+               return $this->timestamp;
+       }
+
+       public function getAuthor() {
+               return $this->author;
+       }
+}
diff --git a/includes/Import/Wikitext/Converter.php 
b/includes/Import/Wikitext/Converter.php
new file mode 100644
index 0000000..6cb5696
--- /dev/null
+++ b/includes/Import/Wikitext/Converter.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Flow\Import\Wikitext;
+
+use DateTime;
+use DateTimeZone;
+use Flow\Exception\FlowException;
+use MovePage;
+use MWExceptionHandler;
+use Psr\Log\LoggerInterface;
+use Revision;
+use Title;
+use User;
+use WikiPage;
+use WikitextContent;
+
+/**
+ * Does not really convert. Archives wikitext pages out of the way and puts
+ * a new flow board in place. No flow revision is created, after conversion
+ * the namespace must be configured with flow-board as the default content
+ * model.
+ *
+ * It is plausible something with the EchoDiscussionParser could be worked up
+ * to do an import. We know it wont work for everything, but we don't know if
+ * it works for 90%, 99%, or 99.99% of topics.
+ */
+class Converter {
+       /**
+        * @var User
+        */
+       protected $user;
+
+       /**
+        * @var LoggerInterface
+        */
+       protected $logger;
+
+       public function __construct( User $user, LoggerInterface $logger ) {
+               $this->user = $user;
+               $this->logger = $logger;
+       }
+
+       /**
+        * @param Traversable<Title> $titles
+        */
+       public function convert( $titles ) {
+               /** @var Title $title */
+               foreach ( $titles as $title ) {
+                       if ( $title->isSubpage() ) {
+                               continue;
+                       }
+                       if ( $title->getContentModel() !== 'wikitext' ) {
+                               continue;
+                       }
+                       // @todo check for lqt
+
+                       $titleText = $title->getPrefixedText();
+                       try {
+                               $archiveTitle = $this->decideArchiveTitle( 
$title );
+                               $archiveTitleText = 
$archiveTitle->getPrefixedText();
+                               $this->logger->info( "Archiving page from 
$titleText to $archiveTitleText" );
+                               $this->movePage( $title, $archiveTitle );
+                               $this->createArchiveRevision( $title, 
$archiveTitle );
+                       } catch ( \Exception $e ) {
+                               MWExceptionHandler::logException( $e );
+                               $this->logger->error( "Exception while 
importing: $titleText" );
+                               $this->logger->error( (string)$e );
+                       }
+               }
+       }
+
+       protected function movePage( Title $from, Title $to ) {
+               $mp = new MovePage( $from, $to );
+               $valid = $mp->isValidMove();
+               if ( !$valid->isOK() ) {
+                       throw new FlowException( "It is not valid to move 
{$from->getPrefixedText()} to {$to->getPrefixedText()}" );
+               }
+
+               $status = $mp->move(
+                       /* user */ $this->user,
+                       /* reason */ "Conversion of wikitext talk to Flow from: 
{$from->getPrefixedText()}",
+                       /* create redirect */ false
+               );
+
+               if ( !$status->isGood() ) {
+                       throw new FlowException( "Failed moving 
{$from->getPrefixedText()} to {$to->getPrefixedText()}" );
+               }
+       }
+
+       protected function decideArchiveTitle( Title $source ) {
+               // @todo i18n.  Would bes
+               $formats = array(
+                       '%s/Archive %d',
+                       '%s/Archive%d',
+                       '%s/archive %d',
+                       '%s/archive%d',
+               );
+
+               $format = false;
+               $n = 1;
+               $text = $source->getPrefixedText();
+               foreach ( $formats as $potential ) {
+                       $title = Title::newFromText( sprintf( $potential, 
$text, $n ) );
+                       if ( $title && $title->exists() ) {
+                               $format = $potential;
+                               break;
+                       }
+               }
+               if ( $format === false ) {
+                       // assumes this creates a valid title
+                       return Title::newFromText( sprintf( $formats[0], $text, 
$n ) );
+               }
+               for ( ++$n; $n < 20; ++$n ) {
+                       $title = Title::newFromText( sprintf( $format, $n ) );
+                       if ( $title && !$title->exists() ) {
+                               return $title;
+                       }
+               }
+
+               throw new FlowException( "All titles 1 through 20 exist for 
format: $format" );
+       }
+
+       protected function createArchiveRevision( Title $title, Title 
$archiveTitle ) {
+               $page = WikiPage::factory( $archiveTitle );
+               $revision = $page->getRevision();
+               if ( $revision === null ) {
+                       throw new FlowException( "Expected a revision at 
{$archiveTitle->getPrefixedText()}." );
+               }
+
+               $content = $revision->getContent( Revision::RAW );
+               if ( !$content instanceof WikitextContent ) {
+                       throw new FlowException( "Expected wikitext content at 
{$archiveTitle->getPrefixedText()}." );
+               }
+               $status = $page->doEditContent(
+                       $this->createArchiveRevisionContent( $content, $title ),
+                       'Wikitext talk to Flow conversion',
+                       EDIT_FORCE_BOT | EDIT_SUPPRESS_RC,
+                       false,
+                       $this->user
+               );
+
+               if ( !$status->isGood() ) {
+                       throw new FlowException( "Failed creating archive 
revision at {$archiveTitle->getPrefixedText()}" );
+               }
+       }
+
+       protected function createArchiveRevisionContent( WikitextContent 
$content, Title $title ) {
+               $now = new DateTime( "now", new DateTimeZone( "GMT" ) );
+               $arguments = implode( '|', array(
+                       'from=' . $title->getPrefixedText(),
+                       'date=' . $now->format( 'Y-m-d' ),
+               ) );
+
+               $newWikitext = $content->getNativeData() . "\n\n{{Archive of 
wikitext talk page converted to Flow|$arguments}}";
+
+               return new WikitextContent( $newWikitext );
+       }
+}
diff --git a/includes/Import/Wikitext/ImportSource.php 
b/includes/Import/Wikitext/ImportSource.php
new file mode 100644
index 0000000..8c1753e
--- /dev/null
+++ b/includes/Import/Wikitext/ImportSource.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Flow\Import\Wikitext;
+
+use DateTime;
+use DateTimeZone;
+use FlowHooks;
+use Flow\Import\Basic\ImportHeader;
+use Flow\Import\Basic\ObjectRevision;
+use ParserOptions;
+use Title;
+
+class ImportSource implements IImportSource {
+
+       public function __construct( Title $title ) {
+               if ( !$title->isLocal() ) {
+                       throw new \MWException( "Invalid non-local title: 
{$title->getPrefixedText()}" );
+               }
+               $this->title = $title;
+       }
+
+       public function getHeader() {
+               global $wgParser;
+
+               $rev = $this->getHeaderRevision();
+               if ( $rev === null ) {
+                       return null;
+               }
+
+               return new ImportHeader( array(
+                       $rev,
+                       $this->generateNextRevision( $rev ),
+               ) );
+       }
+
+       /**
+        * @return IObjectRevision|null
+        */
+       protected function getHeaderRevision() {
+               $revision = Revision::newFromTitle( $this->title );
+               if ( !$revision ) {
+                       return null;
+               }
+
+               $options = new ParserOptions;
+               $options->setTidy( true );
+               $options->setEditSection( false );
+               $content = $revision->getContent();
+
+               // If sections exist only take the content from the top of the 
page
+               // to the first section.
+               $output = $parser->parse( $content, $this->title, $options );
+               $sections = $output->getSections();
+               if ( $sections ) {
+                       $content = substr( $content, 0, 
$sections[0]['byteoffset'] );
+               }
+
+               $templateName = wfMessage( 
'flow-importer-wt-converted-template' )->inContentLanguage()->plain();
+
+               $now = new DateTime( "now", new DateTimeZone( "GMT" ) );
+               $arguments = implode( '|', array(
+                       'from=' . $this->title->getPrefixedText(),
+                       'date=' . $now->format( 'Y-m-d' ),
+               ) );
+               $content .= "\n\n{{{$templateName}|$arguments}}";
+
+               return new ObjectRevision(
+                       $content,
+                       wfTimestampNow(),
+                       
FlowHooks::getOccupationController()->getTalkpageManager()
+               );
+       }
+
+       public function getTopics() {
+               return new ArrayIterator( array() );
+       }
+}
+
diff --git a/includes/Utils/NamespaceIterator.php 
b/includes/Utils/NamespaceIterator.php
new file mode 100644
index 0000000..f0fe0e3
--- /dev/null
+++ b/includes/Utils/NamespaceIterator.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Flow\Utils;
+
+use DatabaseBase;
+use EchoBatchRowIterator;
+use EchoCallbackIterator;
+use IteratorAggregate;
+use RecursiveIteratorIterator;
+use Title;
+
+/**
+ * Iterates over all titles that have the specified page property
+ */
+class NamespaceIterator implements IteratorAggregate {
+       /**
+        * @var DatabaseBase
+        */
+       protected $db;
+
+       /**
+        * @var int
+        */
+       protected $namespace;
+
+       /**
+        * @param DatabaseBase $db
+        * @param int $namespace
+        */
+       public function __construct( DatabaseBase $db, $namespace ) {
+               $this->db = $db;
+               $this->namespace = $namespace;
+       }
+
+       /**
+        * @return Iterator<Title>
+        */
+       public function getIterator() {
+               $it = new EchoBatchRowIterator(
+                       $this->db,
+                       /* tables */ array( 'page' ),
+                       /* pk */ 'page_id',
+                       /* rows per batch */ 500
+               );
+               $it->addConditions( array(
+                       'page_namespace' => $this->namespace,
+               ) );
+               $it->setFetchColumns( array( 'page_title' ) );
+               $it = new RecursiveIteratorIterator( $it );
+
+               $namespace = $this->namespace;
+               return new EchoCallbackIterator( $it, function( $row ) use ( 
$namespace ) {
+                       return Title::makeTitle( $namespace, $row->page_title );
+               } );
+       }
+}
diff --git a/maintenance/convertNamespace.php b/maintenance/convertNamespace.php
new file mode 100644
index 0000000..f2b99a1
--- /dev/null
+++ b/maintenance/convertNamespace.php
@@ -0,0 +1,49 @@
+<?php
+
+use Flow\Utils\NamespaceIterator;
+use Psr\Log\NullLogger;
+
+require_once ( getenv( 'MW_INSTALL_PATH' ) !== false
+       ? getenv( 'MW_INSTALL_PATH' ) . '/maintenance/Maintenance.php'
+       : dirname( __FILE__ ) . '/../../../maintenance/Maintenance.php' );
+
+/**
+ * Converts a single namespace from wikitext talk pages to flow talk pages.  
Does not
+ * modify liquid threads pages it comes across, use convertLqt.php for that.  
Does not
+ * modify sub-pages.
+ */
+class ConvertNamespace extends Maintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->mDescription = "Converts LiquidThreads data to Flow 
data";
+               $this->addArg( 'namespace', 'Name of the namespace to convert' 
);
+               $this->addOption( 'verbose', 'Report on import progress to 
stdout' );
+       }
+
+       public function execute() {
+               global $wgLang;
+
+               $logger = $this->getOption( 'verbose' )
+                       ? new MaintenanceDebugLogger( $this )
+                       : new NullLogger;
+
+               $provided = $this->getArg( 0 );
+               $namespace = $wgLang->getNsIndex( $provided );
+               if ( !$namespace ) {
+                       $this->error( "Invalid namespace provided: $provided" );
+                       return;
+               }
+               $namespaceName = $wgLang->getNsText( $namespace );
+
+               $logger->info( "Starting conversion of $namespaceName 
namespace" );
+
+               $user = 
FlowHooks::getOccupationController()->getTalkpageManager();
+               $converter = new Flow\Import\Wikitext\Converter( $user, $logger 
);
+               $it = new NamespaceIterator( wfGetDB( DB_SLAVE ), $namespace );
+               $converter->convert( $it );
+       }
+}
+
+$maintClass = "ConvertNamespace";
+require_once ( RUN_MAINTENANCE_IF_MAIN );
+

-- 
To view, visit https://gerrit.wikimedia.org/r/174861
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ie785403748cc22bc28ff52a172bfaafad8aaf18b
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: EBernhardson <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to