Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
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 / 25
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 / 20
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 BatchRowIterator;
6use BatchRowUpdate;
7use BatchRowWriter;
8use Exception;
9use Flow\Container;
10use Flow\Model\UUID;
11use Flow\Model\Workflow;
12use Flow\OccupationController;
13use IDBAccessObject;
14use Language;
15use LoggedUpdateMaintenance;
16use MediaWiki\Language\RawMessage;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Status\Status;
19use MediaWiki\StubObject\StubUserLang;
20use MediaWiki\Title\Title;
21use MediaWiki\WikiMap\WikiMap;
22use RowUpdateGenerator;
23use RuntimeException;
24use stdClass;
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, $wgLang;
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        $gen = new WorkflowPageIdUpdateGenerator( $wgLang );
70        $writer = new BatchRowWriter( $dbw, 'flow_workflow', $wgFlowCluster );
71        $writer->setCaller( __METHOD__ );
72        $updater = new BatchRowUpdate( $it, $writer, $gen );
73
74        $updater->execute();
75
76        $this->output( $gen->report() );
77
78        return true;
79    }
80
81    protected function getUpdateKey() {
82        return 'FlowUpdateWorkflowPageId';
83    }
84}
85
86/**
87 * Looks at rows from the flow_workflow table and returns an update
88 * for the workflow_page_id field if necessary.
89 */
90class WorkflowPageIdUpdateGenerator implements RowUpdateGenerator {
91    /**
92     * @var Language|StubUserLang
93     */
94    protected $lang;
95    /** @var int */
96    protected $fixedCount = 0;
97    /** @var stdClass[] */
98    protected $failures = [];
99    /** @var string[] */
100    protected $warnings = [];
101
102    /**
103     * @param Language|StubUserLang $lang
104     */
105    public function __construct( $lang ) {
106        $this->lang = $lang;
107    }
108
109    public function update( $row ) {
110        $title = Title::makeTitleSafe( $row->workflow_namespace, $row->workflow_title_text );
111        if ( $title === null ) {
112            throw new RuntimeException( sprintf(
113                'Could not create title for %s at %s:%s',
114                UUID::create( $row->workflow_id )->getAlphadecimal(),
115                $this->lang->getNsText( $row->workflow_namespace ) ?: $row->workflow_namespace,
116                $row->workflow_title_text
117            ) );
118        }
119
120        // at some point, we failed to create page entries for new workflows: only
121        // create that page if the workflow was stored with a 0 page id (otherwise,
122        // we could mistake the $title for a deleted page)
123        if ( (int)$row->workflow_page_id === 0 && $title->getArticleID() === 0 ) {
124            $workflow = Workflow::fromStorageRow( (array)$row );
125            $status = $this->createPage( $title, $workflow );
126            if ( !$status->isGood() ) {
127                // just warn when we failed to create the page, but keep this code
128                // going and see if we manage to associate the workflow anyways
129                // (or if that fails, we'll also get an error there)
130                $this->warnings[] = $status->getMessage()->text();
131            }
132        }
133
134        // re-associate the workflow with the correct page; only if a page exists
135        if ( $title->getArticleID() !== 0 && $title->getArticleID() !== (int)$row->workflow_page_id ) {
136            // This makes the assumption the page has not moved or been deleted?
137            ++$this->fixedCount;
138            return [
139                'workflow_page_id' => $title->getArticleID(),
140            ];
141        } elseif ( !$row->workflow_page_id ) {
142            // No id exists for this workflow? (reason should likely show up in $this->warnings)
143            $this->failures[] = $row;
144        }
145
146        return [];
147    }
148
149    /**
150     * @param Title $title
151     * @param Workflow $workflow
152     * @return Status
153     */
154    protected function createPage( Title $title, $workflow ) {
155        /** @var OccupationController $occupationController */
156        $occupationController = Container::get( 'occupation_controller' );
157
158        try {
159            $status = $occupationController->safeAllowCreation( $title, $occupationController->getTalkpageManager() );
160            $status2 = $occupationController->ensureFlowRevision(
161                MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ),
162                $workflow
163            );
164
165            $status->merge( $status2 );
166        } catch ( Exception $e ) {
167            // "convert" exception into Status
168            $message = new RawMessage( $e->getMessage() );
169            $status = Status::newFatal( $message );
170        }
171
172        if ( $status->isGood() ) {
173            // force article id to be refetched from db
174            $title->getArticleID( IDBAccessObject::READ_LATEST );
175        }
176
177        return $status;
178    }
179
180    public function report() {
181        $ret = "Updated {$this->fixedCount} workflows\n\n";
182
183        $warningsCount = count( $this->warnings );
184        $ret .= "Warnings: {$warningsCount}\n";
185        if ( $warningsCount > 0 ) {
186            $ret .= print_r( $this->warnings, true ) . "\n\n";
187        }
188        $failureCount = count( $this->failures );
189        $ret .= "Failed: {$failureCount}\n";
190        if ( $failureCount > 0 ) {
191            $ret .= print_r( $this->failures, true );
192        }
193
194        return $ret;
195    }
196}
197
198$maintClass = FlowUpdateWorkflowPageId::class;
199require_once RUN_MAINTENANCE_IF_MAIN;