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 BatchRowIterator;
6use BatchRowUpdate;
7use BatchRowWriter;
8use Flow\Collection\PostCollection;
9use Flow\Container;
10use Flow\Data\ManagerGroup;
11use Flow\Exception\InvalidDataException;
12use Flow\Model\PostRevision;
13use Flow\Model\UUID;
14use LoggedUpdateMaintenance;
15use 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 mixed|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     * @param int $die
88     */
89    public function error( $err, $die = 0 ) {
90        parent::error( $err, $die );
91    }
92}
93
94class LogRowUpdateGenerator implements RowUpdateGenerator {
95    /**
96     * @var FlowFixLog
97     */
98    protected $maintenance;
99
100    /**
101     * @param FlowFixLog $maintenance
102     */
103    public function __construct( FlowFixLog $maintenance ) {
104        $this->maintenance = $maintenance;
105    }
106
107    public function update( $row ) {
108        $updates = [];
109        $logId = (int)$row->log_id;
110
111        $params = unserialize( $row->log_params );
112        if ( !$params ) {
113            $this->maintenance->error( "Failed to unserialize log_params for log_id $logId" );
114            return [];
115        }
116
117        $topic = false;
118        $post = false;
119        if ( isset( $params['topicId'] ) ) {
120            $topic = $this->loadTopic( UUID::create( $params['topicId'] ) );
121        }
122        if ( isset( $params['postId'] ) ) {
123            $post = $this->loadPost( UUID::create( $params['postId'] ) );
124            $topic = $topic ?: $post->getRoot();
125        }
126
127        if ( !$topic ) {
128            $this->maintenance->error( "Missing topicId & postId for log_id $logId" );
129            return [];
130        }
131
132        try {
133            // log_namespace & log_title used to be board, should be topic
134            $updates['log_namespace'] = $topic->getTitle()->getNamespace();
135            $updates['log_title'] = $topic->getTitle()->getDBkey();
136        } catch ( \Exception $e ) {
137            $this->maintenance->error( "Couldn't load Title for log_id $logId" );
138            $updates = [];
139        }
140
141        if ( isset( $params['postId'] ) && $post ) {
142            // posts used to save revision id instead of post id, let's make
143            // sure it's actually the post id that's being saved!...
144            $params['postId'] = $post->getId();
145        }
146
147        if ( !isset( $params['topicId'] ) ) {
148            // posts didn't use to also store topicId, but we'll be using it to
149            // enrich log entries' output - might as well store it right away
150            $params['topicId'] = $topic->getId();
151        }
152
153        // we used to save (serialized) UUID objects; now we just save the
154        // alphanumeric representation
155        foreach ( $params as $key => $value ) {
156            $params[$key] = $value instanceof UUID ? $value->getAlphadecimal() : $value;
157        }
158
159        // re-serialize params (UUID used to serialize more verbose; might
160        // as well shrink that down now that we're updating anyway...)
161        $updates['log_params'] = serialize( $params );
162
163        return $updates;
164    }
165
166    /**
167     * @param UUID $topicId
168     * @return PostCollection
169     */
170    protected function loadTopic( UUID $topicId ) {
171        return PostCollection::newFromId( $topicId );
172    }
173
174    /**
175     * @param UUID $postId
176     * @return PostCollection|false
177     */
178    protected function loadPost( UUID $postId ) {
179        try {
180            $collection = PostCollection::newFromId( $postId );
181
182            // validate collection by attempting to fetch latest revision - if
183            // this fails (likely will for old data), catch will be invoked
184            $collection->getLastRevision();
185            return $collection;
186        } catch ( InvalidDataException $e ) {
187            // posts used to mistakenly store revision ID instead of post ID
188
189            /** @var ManagerGroup $storage */
190            $storage = Container::get( 'storage' );
191            $result = $storage->find(
192                'PostRevision',
193                [ 'rev_id' => $postId ],
194                [ 'LIMIT' => 1 ]
195            );
196
197            if ( $result ) {
198                /** @var PostRevision $revision */
199                $revision = reset( $result );
200
201                // now build collection from real post ID
202                return $this->loadPost( $revision->getPostId() );
203            }
204        }
205
206        return false;
207    }
208}
209
210$maintClass = FlowFixLog::class;
211require_once RUN_MAINTENANCE_IF_MAIN;