Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 78 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 2 |
FlowFixLog | |
0.00% |
0 / 27 |
|
0.00% |
0 / 5 |
30 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getUpdateKey | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doDBUpdates | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
2 | |||
output | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
error | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
LogRowUpdateGenerator | |
0.00% |
0 / 45 |
|
0.00% |
0 / 4 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
update | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
156 | |||
loadTopic | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
loadPost | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace Flow\Maintenance; |
4 | |
5 | use BatchRowIterator; |
6 | use BatchRowUpdate; |
7 | use BatchRowWriter; |
8 | use Flow\Collection\PostCollection; |
9 | use Flow\Container; |
10 | use Flow\Data\ManagerGroup; |
11 | use Flow\Exception\InvalidDataException; |
12 | use Flow\Model\PostRevision; |
13 | use Flow\Model\UUID; |
14 | use MediaWiki\Maintenance\LoggedUpdateMaintenance; |
15 | use RowUpdateGenerator; |
16 | |
17 | $IP = getenv( 'MW_INSTALL_PATH' ); |
18 | if ( $IP === false ) { |
19 | $IP = __DIR__ . '/../../..'; |
20 | } |
21 | |
22 | require_once "$IP/maintenance/Maintenance.php"; |
23 | |
24 | /** |
25 | * Fixes Flow log entries. |
26 | * |
27 | * @ingroup Maintenance |
28 | */ |
29 | class 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 | |
94 | class 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; |
211 | require_once RUN_MAINTENANCE_IF_MAIN; |