jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/337895 )
Change subject: Import dump: support importing a board that exist in the farm ...................................................................... Import dump: support importing a board that exist in the farm When a board already exist, we generate new ids and try to look users up with their global ids. Bug: T154830 Change-Id: Ic493dc132dcf7dc0000d37115b22b0382346c815 --- M includes/Dump/Exporter.php M includes/Dump/Importer.php M includes/Dump/flow-1.0.xsd M includes/WorkflowLoaderFactory.php 4 files changed, 186 insertions(+), 8 deletions(-) Approvals: Mattflaschen: Looks good to me, approved jenkins-bot: Verified diff --git a/includes/Dump/Exporter.php b/includes/Dump/Exporter.php index 75fe0ee..948b07f 100644 --- a/includes/Dump/Exporter.php +++ b/includes/Dump/Exporter.php @@ -73,6 +73,13 @@ protected $changeTypeProperty; /** + * To convert between local and global user ids + * + * @var \CentralIdLookup + */ + protected $lookup; + + /** * {@inheritDoc} */ function __construct( $db, $history = WikiExporter::CURRENT, @@ -84,6 +91,8 @@ $this->changeTypeProperty = new ReflectionProperty( 'Flow\Model\AbstractRevision', 'changeType' ); $this->changeTypeProperty->setAccessible( true ); + + $this->lookup = \CentralIdLookup::factory( 'CentralAuth' ); } public static function schemaVersion() { @@ -388,6 +397,22 @@ $format = $revision->getContentFormat(); $attribs['flags'] = 'utf-8,' . $format; + if ( $this->lookup ) { + $userIdFields = [ 'userid', 'treeoriguserid', 'moduserid', 'edituserid' ]; + foreach ( $userIdFields as $userIdField ) { + if ( isset( $attribs[ $userIdField ] ) ) { + $user = User::newFromId( $attribs[ $userIdField ] ); + $globalUserId = $this->lookup->centralIdFromLocalUser( + $user, + \CentralIdLookup::AUDIENCE_RAW + ); + if ( $globalUserId ) { + $attribs[ 'global' . $userIdField ] = $globalUserId; + } + } + } + } + $output = Xml::element( 'revision', $attribs, diff --git a/includes/Dump/Importer.php b/includes/Dump/Importer.php index 9341017..0c8607c 100644 --- a/includes/Dump/Importer.php +++ b/includes/Dump/Importer.php @@ -4,6 +4,9 @@ use Flow\Container; use Flow\Data\ManagerGroup; +use Flow\DbFactory; +use Flow\Import\HistoricalUIDGenerator; +use Flow\Import\ImportException; use Flow\Model\AbstractRevision; use Flow\Model\Header; use Flow\Model\PostRevision; @@ -42,10 +45,30 @@ protected $topicWorkflow; /** + * @var array Map of old to new IDs + */ + protected $idMap = []; + + /** + * To convert between global and local user ids + * + * @var \CentralIdLookup + */ + protected $lookup; + + /** + * Whether the current board is being imported in trans-wiki mode + * + * @var bool + */ + protected $transWikiMode = false; + + /** * @param WikiImporter $importer */ public function __construct( WikiImporter $importer ) { $this->importer = $importer; + $this->lookup = \CentralIdLookup::factory( 'CentralAuth' ); } /** @@ -77,7 +100,12 @@ } public function handleBoard() { - $id = $this->importer->nodeAttribute( 'id' ); + $this->checkTransWikiMode( + $this->importer->nodeAttribute( 'id' ), + $this->importer->nodeAttribute( 'title' ) + ); + + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter board handler for ' . $id ); $uuid = UUID::create( $id ); @@ -110,7 +138,7 @@ } public function handleHeader() { - $id = $this->importer->nodeAttribute( 'id' ); + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter description handler for ' . $id ); $metadata = array( 'workflow' => $this->boardWorkflow ); @@ -127,10 +155,10 @@ } public function handleTopic() { - $id = $this->importer->nodeAttribute( 'id' ); + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter topic handler for ' . $id ); - $uuid = UUID::create( $this->importer->nodeAttribute( 'id' ) ); + $uuid = UUID::create( $id ); $title = $this->boardWorkflow->getArticleTitle(); $this->topicWorkflow = Workflow::fromStorageRow( array( @@ -150,12 +178,31 @@ // @todo: topic-title & first-post? (used only in NotificationListener) ); + // create page if it does not yet exist + /** @var OccupationController $occupationController */ + $occupationController = Container::get( 'occupation_controller' ); + $creationStatus = $occupationController->safeAllowCreation( + $this->topicWorkflow->getArticleTitle(), + $occupationController->getTalkpageManager() + ); + if ( !$creationStatus->isOK() ) { + throw new MWException( $creationStatus->getWikiText() ); + } + + $ensureStatus = $occupationController->ensureFlowRevision( + new \Article( $this->topicWorkflow->getArticleTitle() ), + $this->topicWorkflow + ); + if ( !$ensureStatus->isOK() ) { + throw new MWException( $ensureStatus->getWikiText() ); + } + $this->put( $this->topicWorkflow, $metadata ); $this->put( $topicListEntry, $metadata ); } public function handlePost() { - $id = $this->importer->nodeAttribute( 'id' ); + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter post handler for ' . $id ); $metadata = array( @@ -175,7 +222,7 @@ } public function handleSummary() { - $id = $this->importer->nodeAttribute( 'id' ); + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter summary handler for ' . $id ); $metadata = array( 'workflow' => $this->topicWorkflow ); @@ -214,7 +261,7 @@ * @return AbstractRevision */ protected function getRevision( $callback ) { - $id = $this->importer->nodeAttribute( 'id' ); + $id = $this->mapId( $this->importer->nodeAttribute( 'id' ) ); $this->importer->debug( 'Enter revision handler for ' . $id ); // isEmptyElement will no longer be valid after we've started iterating @@ -227,6 +274,34 @@ do { $attribs[$this->importer->getReader()->name] = $this->importer->getReader()->value; } while ( $this->importer->getReader()->moveToNextAttribute() ); + + $idFields = [ 'id', 'typeid', 'treedescendantid', 'treerevid', 'parentid', 'treeparentid', 'lasteditid' ]; + foreach ( $idFields as $idField ) { + if ( isset( $attribs[ $idField ] ) ) { + $attribs[ $idField ] = $this->mapId( $attribs[ $idField ] ); + } + } + + if ( $this->transWikiMode && $this->lookup ) { + $userFields = [ 'user', 'treeoriguser', 'moduser', 'edituser' ]; + foreach ( $userFields as $userField ) { + $globalUserIdField = 'global' . $userField . 'id'; + if ( isset( $attribs[ $globalUserIdField ] ) ) { + $localUser = $this->lookup->localUserFromCentralId( + $attribs[ $globalUserIdField ], + \CentralIdLookup::AUDIENCE_RAW + ); + if ( !$localUser ) { + $localUser = $this->createLocalUser( $attribs[ $globalUserIdField ] ); + } + $attribs[ $userField . 'id' ] = $localUser->getId(); + $attribs[ $userField . 'wiki' ] = wfWikiID(); + } else if ( isset( $attribs[ $userField . 'ip' ] ) ) { + // make anons local users + $attribs[ $userField . 'wiki' ] = wfWikiID(); + } + } + } // now that we've moved inside the node (to fetch attributes), // nodeContents() is no longer reliable: is uses isEmptyContent (which @@ -248,4 +323,78 @@ return call_user_func( $callback, $attribs ); } + + /** + * When in trans-wiki mode, return a new id based on the same timestamp + * + * @param string $id + * @return string + */ + private function mapId( $id ) { + if ( !$this->transWikiMode ) { + return $id; + } + + if ( !isset( $this->idMap[ $id ] ) ) { + $this->idMap[ $id ] = UUID::create( HistoricalUIDGenerator::historicalTimestampedUID88( + UUID::hex2timestamp( UUID::create( $id )->getHex() ) + ) )->getAlphadecimal(); + } + return $this->idMap[ $id ]; + } + + /** + * Check if a board already exist and should be imported in trans-wiki mode + * + * @param string $boardWorkflowId + * @param string $title + */ + private function checkTransWikiMode( $boardWorkflowId, $title ) { + /** @var DbFactory $dbFactory */ + $dbFactory = Container::get( 'db.factory' ); + $workflowExist = !!$dbFactory->getDB( DB_MASTER )->selectField( + 'flow_workflow', + 'workflow_id', + [ 'workflow_id' => UUID::create( $boardWorkflowId )->getBinary() ], + __METHOD__ + ); + + if ( $workflowExist ) { + $this->importer->debug( "$title will be imported in trans-wiki mode" ); + } + $this->transWikiMode = $workflowExist; + } + + /** + * Create a local user corresponding to a global id + * + * @param integer $globalUserId + * @return \User Local user + * @throws ImportException + */ + private function createLocalUser( $globalUserId ) { + if ( !( $this->lookup instanceof \CentralAuthIdLookup ) ) { + throw new ImportException( 'Creating local users is not supported with central id provider: ' . get_class( $this->lookup ) ); + } + + $globalUser = \CentralAuthUser::newFromId( $globalUserId ); + $localUser = \User::newFromName( $globalUser->getName() ); + + if ( $localUser->getId() ) { + throw new ImportException( "User '{$localUser->getName()}' already exists" ); + } + + $status = \CentralAuthUtils::autoCreateUser( $localUser ); + if ( !$status->isGood() ) { + throw new ImportException( + "autoCreateUser failed for {$localUser->getName()}: " . print_r( $status->getErrors(), true ) + ); + } + + # Update user count + $ssUpdate = \SiteStatsUpdate::factory( [ 'users' => 1 ] ); + $ssUpdate->doUpdate(); + + return $localUser; + } } diff --git a/includes/Dump/flow-1.0.xsd b/includes/Dump/flow-1.0.xsd index a670f85..275c5a6 100644 --- a/includes/Dump/flow-1.0.xsd +++ b/includes/Dump/flow-1.0.xsd @@ -12,6 +12,7 @@ <extension base="string"> <attribute type="string" name="id" use="required"/> <attribute type="nonNegativeInteger" name="userid" use="required"/> + <attribute type="nonNegativeInteger" name="globaluserid" use="optional"/> <attribute type="string" name="userip" use="optional"/> <attribute type="string" name="userwiki" use="required"/> <attribute type="string" name="parentid" use="optional"/> @@ -20,6 +21,7 @@ <attribute type="string" name="typeid" use="required"/> <attribute type="string" name="flags" use="required"/> <attribute type="string" name="modstate" use="required"/> + <attribute type="nonNegativeInteger" name="globalmoduserid" use="optional"/> <attribute type="string" name="moduserid" use="optional"/> <attribute type="string" name="moduserip" use="optional"/> <attribute type="string" name="moduserwiki" use="optional"/> @@ -27,6 +29,7 @@ <attribute type="string" name="modreason" use="optional"/> <attribute type="string" name="lasteditid" use="optional"/> <attribute type="string" name="edituserid" use="optional"/> + <attribute type="nonNegativeInteger" name="globaledituserid" use="optional"/> <attribute type="string" name="edituserip" use="optional"/> <attribute type="string" name="edituserwiki" use="optional"/> <attribute type="nonNegativeInteger" name="contentlength" use="required"/> @@ -42,6 +45,7 @@ <attribute type="string" name="treedescendantid" use="required"/> <attribute type="string" name="treerevid" use="required"/> <attribute type="nonNegativeInteger" name="treeoriguserid" use="required"/> + <attribute type="nonNegativeInteger" name="globaltreeoriguserid" use="optional"/> <attribute type="string" name="treeoriguserip" use="optional"/> <attribute type="string" name="treeoriguserwiki" use="required"/> </extension> diff --git a/includes/WorkflowLoaderFactory.php b/includes/WorkflowLoaderFactory.php index fd52375..5350e2f 100644 --- a/includes/WorkflowLoaderFactory.php +++ b/includes/WorkflowLoaderFactory.php @@ -110,7 +110,7 @@ * @param Title|false $title * @param UUID $workflowId * @return Workflow - * @throws InvalidInputException + * @throws InvalidDataException * @throws UnknownWorkflowIdException */ public function loadWorkflowById( /* Title or false */ $title, $workflowId ) { -- To view, visit https://gerrit.wikimedia.org/r/337895 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ic493dc132dcf7dc0000d37115b22b0382346c815 Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/extensions/Flow Gerrit-Branch: master Gerrit-Owner: Sbisson <sbis...@wikimedia.org> Gerrit-Reviewer: Catrope <r...@wikimedia.org> Gerrit-Reviewer: Mattflaschen <mflasc...@wikimedia.org> Gerrit-Reviewer: Nikerabbit <niklas.laxst...@gmail.com> Gerrit-Reviewer: Sbisson <sbis...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits