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