Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 2
FlowFixLog
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 5
30
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 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 output
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 error
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
LogRowUpdateGenerator
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 4
306
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 / 29
0.00% covered (danger)
0.00%
0 / 1
156
 loadTopic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadPost
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Flow\Maintenance;
4
5use Flow\Collection\PostCollection;
6use Flow\Container;
7use Flow\Data\ManagerGroup;
8use Flow\Exception\InvalidDataException;
9use Flow\Model\PostRevision;
10use Flow\Model\UUID;
11use MediaWiki\Maintenance\LoggedUpdateMaintenance;
12use MediaWiki\Utils\BatchRowIterator;
13use MediaWiki\Utils\BatchRowUpdate;
14use MediaWiki\Utils\BatchRowWriter;
15use MediaWiki\Utils\RowUpdateGenerator;
16
17$IP = getenv( 'MW_INSTALL_PATH' );
18if ( $IP === false ) {
19    $IP = __DIR__ . '/../../..';
20}
21
22require_once "$IP/maintenance/Maintenance.php";
23
24/**
25 * Fixes Flow log entries.
26 *
27 * @ingroup Maintenance
28 */
29class FlowFixLog extends LoggedUpdateMaintenance {
30    public function __construct() {
31        parent::__construct();
32
33        $this->addDescription( 'Fixes Flow log entries' );
34
35        $this->setBatchSize( 300 );
36
37        $this->requireExtension( 'Flow' );
38    }
39
40    protected function getUpdateKey() {
41        return 'FlowFixLog:version2';
42    }
43
44    protected function doDBUpdates() {
45        $iterator = new BatchRowIterator( $this->getReplicaDB(), 'logging', 'log_id', $this->getBatchSize() );
46        $iterator->setFetchColumns( [ 'log_id', 'log_params' ] );
47        $iterator->addConditions( [
48            'log_type' => [ 'delete', 'suppress' ],
49            'log_action' => [
50                'flow-delete-post', 'flow-suppress-post', 'flow-restore-post',
51                'flow-delete-topic', 'flow-suppress-topic', 'flow-restore-topic',
52            ],
53        ] );
54        $iterator->setCaller( __METHOD__ );
55
56        $writer = new BatchRowWriter( $this->getPrimaryDB(), 'logging' );
57        $writer->setCaller( __METHOD__ );
58
59        $updater = new BatchRowUpdate(
60            $iterator,
61            $writer,
62            new LogRowUpdateGenerator( $this )
63        );
64        $updater->setOutput( [ $this, 'output' ] );
65        $updater->execute();
66
67        return true;
68    }
69
70    /**
71     * parent::output() is a protected method, only way to access it from a
72     * callback in php5.3 is to make a public function. In 5.4 can replace with
73     * a Closure.
74     *
75     * @param string $out
76     * @param string|null $channel
77     */
78    public function output( $out, $channel = null ) {
79        parent::output( $out, $channel );
80    }
81
82    /**
83     * parent::error() is a protected method, only way to access it from the
84     * outside is to make it public.
85     *
86     * @param string $err
87     */
88    public function error( $err ) {
89        parent::error( $err );
90    }
91}
92
93class LogRowUpdateGenerator implements RowUpdateGenerator {
94    /**
95     * @var FlowFixLog
96     */
97    protected $maintenance;
98
99    public function __construct( FlowFixLog $maintenance ) {
100        $this->maintenance = $maintenance;
101    }
102
103    public function update( $row ) {
104        $updates = [];
105        $logId = (int)$row->log_id;
106
107        $params = unserialize( $row->log_params );
108        if ( !$params ) {
109            $this->maintenance->error( "Failed to unserialize log_params for log_id $logId" );
110            return [];
111        }
112
113        $topic = false;
114        $post = false;
115        if ( isset( $params['topicId'] ) ) {
116            $topic = $this->loadTopic( UUID::create( $params['topicId'] ) );
117        }
118        if ( isset( $params['postId'] ) ) {
119            $post = $this->loadPost( UUID::create( $params['postId'] ) );
120            $topic = $topic ?: $post->getRoot();
121        }
122
123        if ( !$topic ) {
124            $this->maintenance->error( "Missing topicId & postId for log_id $logId" );
125            return [];
126        }
127
128        try {
129            // log_namespace & log_title used to be board, should be topic
130            $updates['log_namespace'] = $topic->getTitle()->getNamespace();
131            $updates['log_title'] = $topic->getTitle()->getDBkey();
132        } catch ( \Exception ) {
133            $this->maintenance->error( "Couldn't load Title for log_id $logId" );
134            $updates = [];
135        }
136
137        if ( isset( $params['postId'] ) && $post ) {
138            // posts used to save revision id instead of post id, let's make
139            // sure it's actually the post id that's being saved!...
140            $params['postId'] = $post->getId();
141        }
142
143        if ( !isset( $params['topicId'] ) ) {
144            // posts didn't use to also store topicId, but we'll be using it to
145            // enrich log entries' output - might as well store it right away
146            $params['topicId'] = $topic->getId();
147        }
148
149        // we used to save (serialized) UUID objects; now we just save the
150        // alphanumeric representation
151        foreach ( $params as $key => $value ) {
152            $params[$key] = $value instanceof UUID ? $value->getAlphadecimal() : $value;
153        }
154
155        // re-serialize params (UUID used to serialize more verbose; might
156        // as well shrink that down now that we're updating anyway...)
157        $updates['log_params'] = serialize( $params );
158
159        return $updates;
160    }
161
162    /**
163     * @param UUID $topicId
164     * @return PostCollection
165     */
166    protected function loadTopic( UUID $topicId ) {
167        return PostCollection::newFromId( $topicId );
168    }
169
170    /**
171     * @param UUID $postId
172     * @return PostCollection|false
173     */
174    protected function loadPost( UUID $postId ) {
175        try {
176            $collection = PostCollection::newFromId( $postId );
177
178            // validate collection by attempting to fetch latest revision - if
179            // this fails (likely will for old data), catch will be invoked
180            $collection->getLastRevision();
181            return $collection;
182        } catch ( InvalidDataException ) {
183            // posts used to mistakenly store revision ID instead of post ID
184
185            /** @var ManagerGroup $storage */
186            $storage = Container::get( 'storage' );
187            $result = $storage->find(
188                'PostRevision',
189                [ 'rev_id' => $postId ],
190                [ 'LIMIT' => 1 ]
191            );
192
193            if ( $result ) {
194                /** @var PostRevision $revision */
195                $revision = reset( $result );
196
197                // now build collection from real post ID
198                return $this->loadPost( $revision->getPostId() );
199            }
200        }
201
202        return false;
203    }
204}
205
206$maintClass = FlowFixLog::class;
207require_once RUN_MAINTENANCE_IF_MAIN;