Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
FlowUpdateRevisionContentLength
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 5
272
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
 getUpdateKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doDBUpdates
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 1
72
 updateRevision
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 calcContentLength
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace Flow\Maintenance;
4
5use BatchRowIterator;
6use Flow\Container;
7use Flow\Data\ManagerGroup;
8use Flow\DbFactory;
9use Flow\Model\AbstractRevision;
10use Flow\Model\UUID;
11use LoggedUpdateMaintenance;
12use MediaWiki\WikiMap\WikiMap;
13use ReflectionProperty;
14
15$IP = getenv( 'MW_INSTALL_PATH' );
16if ( $IP === false ) {
17    $IP = __DIR__ . '/../../..';
18}
19
20require_once "$IP/maintenance/Maintenance.php";
21
22/**
23 * @ingroup Maintenance
24 */
25class FlowUpdateRevisionContentLength extends LoggedUpdateMaintenance {
26    /**
27     * Map from AbstractRevision::getRevisionType() to the class that holds
28     * that type.
29     * @todo seems this should be elsewhere for access by any code
30     *
31     * @var string[]
32     */
33    private static $revisionTypes = [
34        'post' => \Flow\Model\PostRevision::class,
35        'header' => \Flow\Model\Header::class,
36        'post-summary' => \Flow\Model\PostSummary::class,
37    ];
38
39    /**
40     * @var DbFactory
41     */
42    protected $dbFactory;
43
44    /**
45     * @var ManagerGroup
46     */
47    protected $storage;
48
49    /**
50     * @var ReflectionProperty
51     */
52    protected $contentLengthProperty;
53
54    /**
55     * @var ReflectionProperty
56     */
57    protected $previousContentLengthProperty;
58
59    public function __construct() {
60        parent::__construct();
61        $this->addDescription( "Updates content length for revisions with unset content length." );
62        $this->setBatchSize( 300 );
63        $this->requireExtension( 'Flow' );
64    }
65
66    public function getUpdateKey() {
67        return 'FlowUpdateRevisionContentLength:version2';
68    }
69
70    public function doDBUpdates() {
71        // Can't be done in constructor, happens too early in
72        // boot process
73        $this->dbFactory = Container::get( 'db.factory' );
74        $this->storage = Container::get( 'storage' );
75        // Since this is a one-shot maintenance script just reach in via reflection
76        // to change lengths
77        $this->contentLengthProperty = new ReflectionProperty(
78            AbstractRevision::class,
79            'contentLength'
80        );
81        $this->contentLengthProperty->setAccessible( true );
82        $this->previousContentLengthProperty = new ReflectionProperty(
83            AbstractRevision::class,
84            'previousContentLength'
85        );
86        $this->previousContentLengthProperty->setAccessible( true );
87
88        $dbw = $this->dbFactory->getDB( DB_PRIMARY );
89        // Walk through the flow_revision table
90        $it = new BatchRowIterator(
91            $dbw,
92            /* table = */'flow_revision',
93            /* primary key = */'rev_id',
94            $this->getBatchSize()
95        );
96        // Only fetch rows created by users from the current wiki.
97        $it->addConditions( [
98            'rev_user_wiki' => WikiMap::getCurrentWikiId(),
99        ] );
100        // We only need the id and type field
101        $it->setFetchColumns( [ 'rev_id', 'rev_type' ] );
102
103        $it->setCaller( __METHOD__ );
104
105        $total = $fail = 0;
106        foreach ( $it as $batch ) {
107            $this->beginTransaction( $dbw, __METHOD__ );
108            foreach ( $batch as $row ) {
109                $total++;
110                if ( !isset( self::$revisionTypes[$row->rev_type] ) ) {
111                    $this->output( 'Unknown revision type: ' . $row->rev_type );
112                    $fail++;
113                    continue;
114                }
115                $om = $this->storage->getStorage( self::$revisionTypes[$row->rev_type] );
116                $revId = UUID::create( $row->rev_id );
117                $obj = $om->get( $revId );
118                if ( !$obj ) {
119                    $this->output( 'Could not load revision: ' . $revId->getAlphadecimal() );
120                    $fail++;
121                    continue;
122                }
123                if ( $obj->isFirstRevision() ) {
124                    $previous = null;
125                } else {
126                    $previous = $om->get( $obj->getPrevRevisionId() );
127                    if ( !$previous ) {
128                        $this->output( 'Could not locate previous revision: ' .
129                            $obj->getPrevRevisionId()->getAlphadecimal() );
130                        $fail++;
131                        continue;
132                    }
133                }
134
135                $this->updateRevision( $obj, $previous );
136
137                try {
138                    $om->put( $obj );
139                } catch ( \Exception $e ) {
140                    $this->error(
141                        'Failed to update revision ' . $obj->getRevisionId()->getAlphadecimal() .
142                            ': ' . $e->getMessage() . "\n" .
143                        'Please make sure rev_content, rev_content_length, rev_flags & ' .
144                            'rev_previous_content_length are part of RevisionStorage::$allowedUpdateColumns.'
145                    );
146                    throw $e;
147                }
148                $this->output( '.' );
149            }
150            $this->commitTransaction( $dbw, __METHOD__ );
151            $this->storage->clear();
152            $this->dbFactory->waitForReplicas();
153        }
154
155        return true;
156    }
157
158    protected function updateRevision( AbstractRevision $revision, AbstractRevision $previous = null ) {
159        $this->contentLengthProperty->setValue(
160            $revision,
161            $this->calcContentLength( $revision )
162        );
163        if ( $previous !== null ) {
164            $this->previousContentLengthProperty->setValue(
165                $revision,
166                $this->calcContentLength( $previous )
167            );
168        }
169    }
170
171    protected function calcContentLength( AbstractRevision $revision ) {
172        if ( $revision->isModerated() && !$revision->isLocked() ) {
173            return 0;
174        } else {
175            return $revision->getContentLength() ?: $revision->calculateContentLength();
176        }
177    }
178}
179
180$maintClass = FlowUpdateRevisionContentLength::class;
181require_once RUN_MAINTENANCE_IF_MAIN;