Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 130
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
FlowUpdateUserWiki
0.00% covered (danger)
0.00%
0 / 124
0.00% covered (danger)
0.00%
0 / 9
702
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 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 updateHeader
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
20
 updateTopicList
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
 updatePost
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 updateHistory
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 updateRevision
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
12
 checkForReplica
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getUpdateKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow\Maintenance;
4
5use Flow\Container;
6use Flow\Model\PostRevision;
7use Flow\Model\UUID;
8use Flow\Model\Workflow;
9use LoggedUpdateMaintenance;
10
11$IP = getenv( 'MW_INSTALL_PATH' );
12if ( $IP === false ) {
13    $IP = __DIR__ . '/../../..';
14}
15
16require_once "$IP/maintenance/Maintenance.php";
17
18/**
19 * Update all xxx_user_wiki field to have the correct wiki name
20 *
21 * @ingroup Maintenance
22 */
23class FlowUpdateUserWiki extends LoggedUpdateMaintenance {
24
25    /**
26     * Used to track the number of current updated count
27     *
28     * @var int
29     */
30    private $updatedCount = 0;
31
32    public function __construct() {
33        parent::__construct();
34        $this->addDescription( "Update xxx_user_wiki field in tables: flow_workflow, flow_tree_revision, flow_revision" );
35        $this->requireExtension( 'Flow' );
36        $this->setBatchSize( 300 );
37    }
38
39    /**
40     * This is a top-to-bottom update, the process is like this:
41     * workflow -> header -> header revision -> history
42     * workflow -> topic list -> post tree revision -> post revision -> history
43     *
44     * Some side effect, the script will also update those *_user_wiki fields with
45     * empty *_user_id and *_user_ip, but this doesn't hurt. Alternatively, we could
46     * add a check user_id != 0 and user_ip is not null to the query, but this will
47     * result in more db queries
48     * @return true
49     */
50    protected function doDBUpdates() {
51        $id = '';
52        $batchSize = $this->getBatchSize();
53        $count = $batchSize;
54        $dbr = Container::get( 'db.factory' )->getDB( DB_REPLICA );
55
56        // If table flow_header_revision does not exist, that means the wiki
57        // has run the data migration before or the wiki starts from scratch,
58        // there is no point to run the script againt invalid tables
59        if ( !$dbr->tableExists( 'flow_header_revision', __METHOD__ ) ) {
60            return true;
61        }
62
63        while ( $count == $this->mBatchSize ) {
64            $count = 0;
65            $res = $dbr->newSelectQueryBuilder()
66                ->select( [ 'workflow_wiki', 'workflow_id', 'workflow_type' ] )
67                ->from( 'flow_workflow' )
68                ->where( $dbr->expr( 'workflow_id', '>', $id ) )
69                ->orderBy( 'workflow_id' )
70                ->limit( $batchSize )
71                ->caller( __METHOD__ )
72                ->fetchResultSet();
73            foreach ( $res as $row ) {
74                $count++;
75                $id = $row->workflow_id;
76                $uuid = UUID::create( $row->workflow_id );
77                $workflow = Container::get( 'storage.workflow' )->get( $uuid );
78                if ( $workflow ) {
79                    // definition type 'topic' is always under a 'discussion' and they
80                    // will be handled while processing 'discussion'
81                    if ( $row->workflow_type == 'discussion' ) {
82                        $this->updateHeader( $workflow, $row->workflow_wiki );
83                        $this->updateTopicList( $workflow, $row->workflow_wiki );
84                    }
85                }
86            }
87        }
88
89        return true;
90    }
91
92    /**
93     * Update header
94     * @param Workflow $workflow
95     * @param string $wiki
96     */
97    private function updateHeader( $workflow, $wiki ) {
98        $id = '';
99        $batchSize = $this->getBatchSize();
100        $count = $batchSize;
101        $dbr = Container::get( 'db.factory' )->getDB( DB_REPLICA );
102
103        while ( $count == $batchSize ) {
104            $count = 0;
105            $res = $dbr->newSelectQueryBuilder()
106                ->select( [ 'rev_id', 'rev_type' ] )
107                ->from( 'flow_header_revision' )
108                ->join( 'flow_revision', null, 'header_rev_id = rev_id' )
109                ->where( [
110                    $dbr->expr( 'rev_id', '>', $id ),
111                    'header_workflow_id' => $workflow->getId()->getBinary()
112                ] )
113                ->orderBy( 'header_rev_id' )
114                ->limit( $batchSize )
115                ->caller( __METHOD__ )
116                ->fetchResultset();
117            foreach ( $res as $row ) {
118                $count++;
119                $id = $row->rev_id;
120                $revision = Container::get( 'storage.header' )->get( UUID::create( $row->rev_id ) );
121                if ( $revision ) {
122                    $this->updateRevision( $revision, $wiki );
123                }
124            }
125        }
126    }
127
128    /**
129     * Update topic list
130     * @param Workflow $workflow
131     * @param string $wiki
132     */
133    private function updateTopicList( $workflow, $wiki ) {
134        $id = '';
135        $batchSize = $this->getBatchSize();
136        $count = $batchSize;
137        $dbr = Container::get( 'db.factory' )->getDB( DB_REPLICA );
138
139        while ( $count == $batchSize ) {
140            $count = 0;
141            $res = $dbr->newSelectQueryBuilder()
142                ->select( 'topic_id' )
143                ->from( 'flow_topic_list' )
144                ->where( [
145                    'topic_list_id' => $workflow->getId()->getBinary(),
146                    $dbr->expr( 'topic_id', '>', $id ),
147                ] )
148                ->orderBy( 'topic_id' )
149                ->limit( $batchSize )
150                ->caller( __METHOD__ )
151                ->fetchResultSet();
152            $index = 0;
153            foreach ( $res as $row ) {
154                $count++;
155                $index++;
156                $id = $row->topic_id;
157                $post = Container::get( 'loader.root_post' )->get( UUID::create( $row->topic_id ) );
158                if ( $post ) {
159                    $this->updatePost( $post, $wiki );
160                }
161            }
162        }
163    }
164
165    /**
166     * Update post
167     * @param PostRevision $post
168     * @param string $wiki
169     */
170    private function updatePost( $post, $wiki ) {
171        $this->updateHistory( $post, $wiki );
172        $this->updateRevision( $post, $wiki );
173        foreach ( $post->getChildren() as $child ) {
174            $this->updatePost( $child, $wiki );
175        }
176    }
177
178    /**
179     * Update history revision
180     * @param PostRevision $post
181     * @param string $wiki
182     */
183    private function updateHistory( PostRevision $post, $wiki ) {
184        if ( $post->getPrevRevisionId() ) {
185            $parent = Container::get( 'storage.post' )->get( UUID::create( $post->getPrevRevisionId() ) );
186            if ( $parent ) {
187                $this->updateRevision( $parent, $wiki );
188                $this->updateHistory( $parent, $wiki );
189            }
190        }
191    }
192
193    /**
194     * Update either header or post revision
195     * @param PostRevision $revision
196     * @param string $wiki
197     */
198    private function updateRevision( $revision, $wiki ) {
199        if ( !$revision ) {
200            return;
201        }
202        $type = $revision->getRevisionType();
203
204        $dbw = Container::get( 'db.factory' )->getDB( DB_PRIMARY );
205        $dbw->newUpdateQueryBuilder()
206            ->update( 'flow_revision' )
207            ->set( [
208                'rev_user_wiki' => $wiki,
209                'rev_mod_user_wiki' => $wiki,
210                'rev_edit_user_wiki' => $wiki,
211            ] )
212            ->where( [
213                'rev_id' => $revision->getRevisionId()->getBinary(),
214            ] )
215            ->caller( __METHOD__ )
216            ->execute();
217        $this->checkForReplica();
218
219        if ( $type === 'post' ) {
220            $dbw->newUpdateQueryBuilder()
221                ->update( 'flow_tree_revision' )
222                ->set( [
223                    'tree_orig_user_wiki' => $wiki,
224                ] )
225                ->where( [
226                    'tree_rev_id' => $revision->getRevisionId()->getBinary(),
227                ] )
228                ->caller( __METHOD__ )
229                ->execute();
230            $this->checkForReplica();
231        }
232    }
233
234    private function checkForReplica() {
235        global $wgFlowCluster;
236
237        $this->updatedCount++;
238        if ( $this->updatedCount > $this->getBatchSize() ) {
239            $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
240            $lbFactory->waitForReplication( [ 'cluster' => $wgFlowCluster ] );
241            $this->updatedCount = 0;
242        }
243    }
244
245    /**
246     * Get the update key name to go in the update log table
247     *
248     * @return string
249     */
250    protected function getUpdateKey() {
251        return 'FlowUpdateUserWiki';
252    }
253}
254
255$maintClass = FlowUpdateUserWiki::class;
256require_once RUN_MAINTENANCE_IF_MAIN;