Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 2
FlowUpdateWorkflowPageId
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 3
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 doDbUpdates
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 getUpdateKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
WorkflowPageIdUpdateGenerator
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 4
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
 createPage
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 report
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Flow\Maintenance;
4
5use Exception;
6use Flow\Container;
7use Flow\Model\UUID;
8use Flow\Model\Workflow;
9use Flow\OccupationController;
10use MediaWiki\Language\Language;
11use MediaWiki\Language\RawMessage;
12use MediaWiki\Maintenance\LoggedUpdateMaintenance;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Status\Status;
15use MediaWiki\StubObject\StubUserLang;
16use MediaWiki\Title\Title;
17use MediaWiki\Utils\BatchRowIterator;
18use MediaWiki\Utils\BatchRowUpdate;
19use MediaWiki\Utils\BatchRowWriter;
20use MediaWiki\Utils\RowUpdateGenerator;
21use MediaWiki\WikiMap\WikiMap;
22use RuntimeException;
23use stdClass;
24use Wikimedia\Rdbms\IDBAccessObject;
25
26$IP = getenv( 'MW_INSTALL_PATH' );
27if ( $IP === false ) {
28    $IP = __DIR__ . '/../../..';
29}
30
31require_once "$IP/maintenance/Maintenance.php";
32
33/**
34 * In some cases we have created workflow instances before the related Title
35 * has an ArticleID assigned to it.  This goes through and sets that value
36 *
37 * @ingroup Maintenance
38 */
39class FlowUpdateWorkflowPageId extends LoggedUpdateMaintenance {
40    public function __construct() {
41        parent::__construct();
42        $this->addDescription( "Update workflow_page_id with the page id of its specified ns/title" );
43        $this->requireExtension( 'Flow' );
44        $this->setBatchSize( 300 );
45    }
46
47    /**
48     * Assembles the update components, runs them, and reports
49     * on what they did
50     * @return true
51     */
52    public function doDbUpdates() {
53        global $wgFlowCluster;
54
55        $dbw = Container::get( 'db.factory' )->getDB( DB_PRIMARY );
56
57        $it = new BatchRowIterator(
58            $dbw,
59            'flow_workflow',
60            'workflow_id',
61            $this->getBatchSize()
62        );
63        $it->setFetchColumns( [ '*' ] );
64        $it->addConditions( [
65            'workflow_wiki' => WikiMap::getCurrentWikiId(),
66        ] );
67        $it->setCaller( __METHOD__ );
68
69        $lang = $this->getServiceContainer()->getContentLanguage();
70        $gen = new WorkflowPageIdUpdateGenerator( $lang );
71        $writer = new BatchRowWriter( $dbw, 'flow_workflow', $wgFlowCluster );
72        $writer->setCaller( __METHOD__ );
73        $updater = new BatchRowUpdate( $it, $writer, $gen );
74
75        $updater->execute();
76
77        $this->output( $gen->report() );
78
79        return true;
80    }
81
82    protected function getUpdateKey() {
83        return 'FlowUpdateWorkflowPageId';
84    }
85}
86
87/**
88 * Looks at rows from the flow_workflow table and returns an update
89 * for the workflow_page_id field if necessary.
90 */
91class WorkflowPageIdUpdateGenerator implements RowUpdateGenerator {
92    /**
93     * @var Language|StubUserLang
94     */
95    protected $lang;
96    /** @var int */
97    protected $fixedCount = 0;
98    /** @var stdClass[] */
99    protected $failures = [];
100    /** @var string[] */
101    protected $warnings = [];
102
103    /**
104     * @param Language|StubUserLang $lang
105     */
106    public function __construct( $lang ) {
107        $this->lang = $lang;
108    }
109
110    public function update( $row ) {
111        $title = Title::makeTitleSafe( $row->workflow_namespace, $row->workflow_title_text );
112        if ( $title === null ) {
113            throw new RuntimeException( sprintf(
114                'Could not create title for %s at %s:%s',
115                UUID::create( $row->workflow_id )->getAlphadecimal(),
116                $this->lang->getNsText( $row->workflow_namespace ) ?: $row->workflow_namespace,
117                $row->workflow_title_text
118            ) );
119        }
120
121        // at some point, we failed to create page entries for new workflows: only
122        // create that page if the workflow was stored with a 0 page id (otherwise,
123        // we could mistake the $title for a deleted page)
124        if ( (int)$row->workflow_page_id === 0 && $title->getArticleID() === 0 ) {
125            $workflow = Workflow::fromStorageRow( (array)$row );
126            $status = $this->createPage( $title, $workflow );
127            if ( !$status->isGood() ) {
128                // just warn when we failed to create the page, but keep this code
129                // going and see if we manage to associate the workflow anyways
130                // (or if that fails, we'll also get an error there)
131                $this->warnings[] = $status->getMessage()->text();
132            }
133        }
134
135        // re-associate the workflow with the correct page; only if a page exists
136        if ( $title->getArticleID() !== 0 && $title->getArticleID() !== (int)$row->workflow_page_id ) {
137            // This makes the assumption the page has not moved or been deleted?
138            ++$this->fixedCount;
139            return [
140                'workflow_page_id' => $title->getArticleID(),
141            ];
142        } elseif ( !$row->workflow_page_id ) {
143            // No id exists for this workflow? (reason should likely show up in $this->warnings)
144            $this->failures[] = $row;
145        }
146
147        return [];
148    }
149
150    /**
151     * @param Title $title
152     * @param Workflow $workflow
153     * @return Status
154     */
155    protected function createPage( Title $title, $workflow ) {
156        /** @var OccupationController $occupationController */
157        $occupationController = Container::get( 'occupation_controller' );
158
159        try {
160            $status = $occupationController->safeAllowCreation( $title, $occupationController->getTalkpageManager() );
161            $status2 = $occupationController->ensureFlowRevision(
162                MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ),
163                $workflow
164            );
165
166            $status->merge( $status2 );
167        } catch ( Exception $e ) {
168            // "convert" exception into Status
169            $message = new RawMessage( $e->getMessage() );
170            $status = Status::newFatal( $message );
171        }
172
173        if ( $status->isGood() ) {
174            // force article id to be refetched from db
175            $title->getArticleID( IDBAccessObject::READ_LATEST );
176        }
177
178        return $status;
179    }
180
181    public function report() {
182        $ret = "Updated {$this->fixedCount} workflows\n\n";
183
184        $warningsCount = count( $this->warnings );
185        $ret .= "Warnings: {$warningsCount}\n";
186        if ( $warningsCount > 0 ) {
187            $ret .= print_r( $this->warnings, true ) . "\n\n";
188        }
189        $failureCount = count( $this->failures );
190        $ret .= "Failed: {$failureCount}\n";
191        if ( $failureCount > 0 ) {
192            $ret .= print_r( $this->failures, true );
193        }
194
195        return $ret;
196    }
197}
198
199$maintClass = FlowUpdateWorkflowPageId::class;
200require_once RUN_MAINTENANCE_IF_MAIN;