Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 84
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 / 78
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 / 61
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 MediaWiki\Maintenance\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->previousContentLengthProperty = new ReflectionProperty(
82            AbstractRevision::class,
83            'previousContentLength'
84        );
85
86        $dbw = $this->dbFactory->getDB( DB_PRIMARY );
87        // Walk through the flow_revision table
88        $it = new BatchRowIterator(
89            $dbw,
90            /* table = */'flow_revision',
91            /* primary key = */'rev_id',
92            $this->getBatchSize()
93        );
94        // Only fetch rows created by users from the current wiki.
95        $it->addConditions( [
96            'rev_user_wiki' => WikiMap::getCurrentWikiId(),
97        ] );
98        // We only need the id and type field
99        $it->setFetchColumns( [ 'rev_id', 'rev_type' ] );
100
101        $it->setCaller( __METHOD__ );
102
103        $total = $fail = 0;
104        foreach ( $it as $batch ) {
105            $this->beginTransaction( $dbw, __METHOD__ );
106            foreach ( $batch as $row ) {
107                $total++;
108                if ( !isset( self::$revisionTypes[$row->rev_type] ) ) {
109                    $this->output( 'Unknown revision type: ' . $row->rev_type );
110                    $fail++;
111                    continue;
112                }
113                $om = $this->storage->getStorage( self::$revisionTypes[$row->rev_type] );
114                $revId = UUID::create( $row->rev_id );
115                $obj = $om->get( $revId );
116                if ( !$obj ) {
117                    $this->output( 'Could not load revision: ' . $revId->getAlphadecimal() );
118                    $fail++;
119                    continue;
120                }
121                if ( $obj->isFirstRevision() ) {
122                    $previous = null;
123                } else {
124                    $previous = $om->get( $obj->getPrevRevisionId() );
125                    if ( !$previous ) {
126                        $this->output( 'Could not locate previous revision: ' .
127                            $obj->getPrevRevisionId()->getAlphadecimal() );
128                        $fail++;
129                        continue;
130                    }
131                }
132
133                $this->updateRevision( $obj, $previous );
134
135                try {
136                    $om->put( $obj );
137                } catch ( \Exception $e ) {
138                    $this->error(
139                        'Failed to update revision ' . $obj->getRevisionId()->getAlphadecimal() .
140                            ': ' . $e->getMessage() . "\n" .
141                        'Please make sure rev_content, rev_content_length, rev_flags & ' .
142                            'rev_previous_content_length are part of RevisionStorage::$allowedUpdateColumns.'
143                    );
144                    throw $e;
145                }
146                $this->output( '.' );
147            }
148            $this->commitTransaction( $dbw, __METHOD__ );
149            $this->storage->clear();
150            $this->dbFactory->waitForReplicas();
151        }
152
153        return true;
154    }
155
156    protected function updateRevision( AbstractRevision $revision, ?AbstractRevision $previous = null ) {
157        $this->contentLengthProperty->setValue(
158            $revision,
159            $this->calcContentLength( $revision )
160        );
161        if ( $previous !== null ) {
162            $this->previousContentLengthProperty->setValue(
163                $revision,
164                $this->calcContentLength( $previous )
165            );
166        }
167    }
168
169    protected function calcContentLength( AbstractRevision $revision ) {
170        if ( $revision->isModerated() && !$revision->isLocked() ) {
171            return 0;
172        } else {
173            return $revision->getContentLength() ?: $revision->calculateContentLength();
174        }
175    }
176}
177
178$maintClass = FlowUpdateRevisionContentLength::class;
179require_once RUN_MAINTENANCE_IF_MAIN;