Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.76% covered (warning)
56.76%
105 / 185
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteLogFormatter
56.76% covered (warning)
56.76%
105 / 185
83.33% covered (warning)
83.33%
5 / 6
340.49
0.00% covered (danger)
0.00%
0 / 1
 getMessageKey
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getMessageParameters
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
18
 parseBitField
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getActionLinks
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
420
 getParametersForApi
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
12
 formatParametersForApi
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Formatter for delete log entries.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @author Niklas Laxström
22 * @license GPL-2.0-or-later
23 * @since 1.22
24 */
25
26use MediaWiki\Revision\RevisionRecord;
27use MediaWiki\SpecialPage\SpecialPage;
28
29/**
30 * This class formats delete log entries.
31 *
32 * @since 1.19
33 */
34class DeleteLogFormatter extends LogFormatter {
35    /** @var array|null */
36    private $parsedParametersDeleteLog;
37
38    /**
39     * @inheritDoc
40     */
41    protected function getMessageKey() {
42        $key = parent::getMessageKey();
43        if ( in_array( $this->entry->getSubtype(), [ 'event', 'revision' ] ) ) {
44            if ( count( $this->getMessageParameters() ) < 5 ) {
45                // Messages: logentry-delete-event-legacy, logentry-delete-revision-legacy,
46                // logentry-suppress-event-legacy, logentry-suppress-revision-legacy
47                return "$key-legacy";
48            }
49        } elseif ( $this->entry->getSubtype() === 'restore' ) {
50            $rawParams = $this->entry->getParameters();
51            if ( !isset( $rawParams[':assoc:count'] ) ) {
52                // Message: logentry-delete-restore-nocount
53                return $key . '-nocount';
54            }
55        }
56
57        return $key;
58    }
59
60    /**
61     * @inheritDoc
62     */
63    protected function getMessageParameters() {
64        if ( $this->parsedParametersDeleteLog !== null ) {
65            return $this->parsedParametersDeleteLog;
66        }
67
68        $params = parent::getMessageParameters();
69        $subtype = $this->entry->getSubtype();
70        if ( in_array( $subtype, [ 'event', 'revision' ] ) ) {
71            // $params[3] here is 'revision' or 'archive' for page revisions, 'oldimage' or
72            // 'filearchive' for file versions, or a comma-separated list of log_ids for log
73            // entries. $subtype here is 'revision' for page revisions and file
74            // versions, or 'event' for log entries.
75            if (
76                ( $subtype === 'event' && count( $params ) === 6 )
77                || (
78                    $subtype === 'revision' && isset( $params[3] ) && count( $params ) === 7
79                    && in_array( $params[3], [ 'revision', 'archive', 'oldimage', 'filearchive' ] )
80                )
81            ) {
82                // See RevDelList::getLogParams()/RevDelLogList::getLogParams()
83                $paramStart = $subtype === 'revision' ? 4 : 3;
84
85                $old = $this->parseBitField( $params[$paramStart + 1] );
86                $new = $this->parseBitField( $params[$paramStart + 2] );
87                [ $hid, $unhid, $extra ] = RevisionDeleter::getChanges( $new, $old );
88                $changes = [];
89                // messages used: revdelete-content-hid, revdelete-summary-hid, revdelete-uname-hid
90                foreach ( $hid as $v ) {
91                    $changes[] = $this->msg( "$v-hid" )->plain();
92                }
93                // messages used: revdelete-content-unhid, revdelete-summary-unhid,
94                // revdelete-uname-unhid
95                foreach ( $unhid as $v ) {
96                    $changes[] = $this->msg( "$v-unhid" )->plain();
97                }
98                foreach ( $extra as $v ) {
99                    $changes[] = $this->msg( $v )->plain();
100                }
101                $changeText = $this->context->getLanguage()->listToText( $changes );
102
103                $newParams = array_slice( $params, 0, 3 );
104                $newParams[3] = $changeText;
105                $ids = is_array( $params[$paramStart] )
106                    ? $params[$paramStart]
107                    : explode( ',', $params[$paramStart] );
108                $newParams[4] = $this->context->getLanguage()->formatNum( count( $ids ) );
109
110                $this->parsedParametersDeleteLog = $newParams;
111                return $this->parsedParametersDeleteLog;
112            } else {
113                $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
114                return $this->parsedParametersDeleteLog;
115            }
116        } elseif ( $subtype === 'restore' ) {
117            $rawParams = $this->entry->getParameters();
118            if ( isset( $rawParams[':assoc:count'] ) ) {
119                $countList = [];
120                foreach ( $rawParams[':assoc:count'] as $type => $count ) {
121                    if ( $count ) {
122                        // Messages: restore-count-revisions, restore-count-files
123                        $countList[] = $this->context->msg( 'restore-count-' . $type )
124                            ->numParams( $count )->plain();
125                    }
126                }
127                $params[3] = $this->context->getLanguage()->listToText( $countList );
128            }
129        }
130
131        $this->parsedParametersDeleteLog = $params;
132        return $this->parsedParametersDeleteLog;
133    }
134
135    protected function parseBitField( $string ) {
136        // Input is like ofield=2134 or just the number
137        if ( strpos( $string, 'field=' ) === 1 ) {
138            [ , $field ] = explode( '=', $string );
139
140            return (int)$field;
141        } else {
142            return (int)$string;
143        }
144    }
145
146    public function getActionLinks() {
147        $linkRenderer = $this->getLinkRenderer();
148        if ( !$this->context->getAuthority()->isAllowed( 'deletedhistory' )
149            || $this->entry->isDeleted( LogPage::DELETED_ACTION )
150        ) {
151            return '';
152        }
153
154        switch ( $this->entry->getSubtype() ) {
155            case 'delete': // Show undelete link
156            case 'delete_redir':
157            case 'delete_redir2':
158                if ( $this->context->getAuthority()->isAllowed( 'undelete' ) ) {
159                    $message = 'undeletelink';
160                } else {
161                    $message = 'undeleteviewlink';
162                }
163                $revert = $linkRenderer->makeKnownLink(
164                    SpecialPage::getTitleFor( 'Undelete' ),
165                    $this->msg( $message )->text(),
166                    [],
167                    [ 'target' => $this->entry->getTarget()->getPrefixedDBkey() ]
168                );
169
170                return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
171
172            case 'revision': // If an edit was hidden from a page give a review link to the history
173                $params = $this->extractParameters();
174                if ( !isset( $params[3] ) || !isset( $params[4] ) ) {
175                    return '';
176                }
177
178                // Different revision types use different URL params...
179                $key = $params[3];
180                // This is a array or CSV of the IDs
181                $ids = is_array( $params[4] )
182                    ? $params[4]
183                    : explode( ',', $params[4] );
184
185                $links = [];
186
187                // If there's only one item, we can show a diff link
188                if ( count( $ids ) == 1 ) {
189                    // Live revision diffs...
190                    if ( $key == 'oldid' || $key == 'revision' ) {
191                        $links[] = $linkRenderer->makeKnownLink(
192                            $this->entry->getTarget(),
193                            $this->msg( 'diff' )->text(),
194                            [],
195                            [
196                                'diff' => intval( $ids[0] ),
197                                'unhide' => 1
198                            ]
199                        );
200                        // Deleted revision diffs...
201                    } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
202                        $links[] = $linkRenderer->makeKnownLink(
203                            SpecialPage::getTitleFor( 'Undelete' ),
204                            $this->msg( 'diff' )->text(),
205                            [],
206                            [
207                                'target' => $this->entry->getTarget()->getPrefixedDBkey(),
208                                'diff' => 'prev',
209                                'timestamp' => $ids[0]
210                            ]
211                        );
212                    }
213                }
214
215                // View/modify link...
216                $links[] = $linkRenderer->makeKnownLink(
217                    SpecialPage::getTitleFor( 'Revisiondelete' ),
218                    $this->msg( 'revdel-restore' )->text(),
219                    [],
220                    [
221                        'target' => $this->entry->getTarget()->getPrefixedText(),
222                        'type' => $key,
223                        'ids' => implode( ',', $ids ),
224                    ]
225                );
226
227                return $this->msg( 'parentheses' )->rawParams(
228                    $this->context->getLanguage()->pipeList( $links ) )->escaped();
229
230            case 'event': // Hidden log items, give review link
231                $params = $this->extractParameters();
232                if ( !isset( $params[3] ) ) {
233                    return '';
234                }
235                // This is a CSV of the IDs
236                $query = $params[3];
237                if ( is_array( $query ) ) {
238                    $query = implode( ',', $query );
239                }
240                // Link to each hidden object ID, $params[1] is the url param
241                $revert = $linkRenderer->makeKnownLink(
242                    SpecialPage::getTitleFor( 'Revisiondelete' ),
243                    $this->msg( 'revdel-restore' )->text(),
244                    [],
245                    [
246                        'target' => $this->entry->getTarget()->getPrefixedText(),
247                        'type' => 'logging',
248                        'ids' => $query
249                    ]
250                );
251
252                return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
253            default:
254                return '';
255        }
256    }
257
258    protected function getParametersForApi() {
259        $entry = $this->entry;
260        $params = [];
261
262        $subtype = $this->entry->getSubtype();
263        if ( in_array( $subtype, [ 'event', 'revision' ] ) ) {
264            $rawParams = $entry->getParameters();
265            if ( $subtype === 'event' ) {
266                array_unshift( $rawParams, 'logging' );
267            }
268
269            static $map = [
270                '4::type',
271                '5::ids',
272                '6::ofield',
273                '7::nfield',
274                '4::ids' => '5::ids',
275                '5::ofield' => '6::ofield',
276                '6::nfield' => '7::nfield',
277            ];
278            foreach ( $map as $index => $key ) {
279                if ( isset( $rawParams[$index] ) ) {
280                    $rawParams[$key] = $rawParams[$index];
281                    unset( $rawParams[$index] );
282                }
283            }
284
285            if ( !is_array( $rawParams['5::ids'] ) ) {
286                $rawParams['5::ids'] = explode( ',', $rawParams['5::ids'] );
287            }
288
289            $params = [
290                '::type' => $rawParams['4::type'],
291                ':array:ids' => $rawParams['5::ids'],
292            ];
293
294            static $fields = [
295                RevisionRecord::DELETED_TEXT => 'content',
296                RevisionRecord::DELETED_COMMENT => 'comment',
297                RevisionRecord::DELETED_USER => 'user',
298                RevisionRecord::DELETED_RESTRICTED => 'restricted',
299            ];
300
301            if ( isset( $rawParams['6::ofield'] ) ) {
302                $old = $this->parseBitField( $rawParams['6::ofield'] );
303                $params[':assoc:old'] = [ 'bitmask' => $old ];
304                foreach ( $fields as $bit => $key ) {
305                    $params[':assoc:old'][$key] = (bool)( $old & $bit );
306                }
307            }
308            if ( isset( $rawParams['7::nfield'] ) ) {
309                $new = $this->parseBitField( $rawParams['7::nfield'] );
310                $params[':assoc:new'] = [ 'bitmask' => $new ];
311                foreach ( $fields as $bit => $key ) {
312                    $params[':assoc:new'][$key] = (bool)( $new & $bit );
313                }
314            }
315        } elseif ( $subtype === 'restore' ) {
316            $rawParams = $entry->getParameters();
317            if ( isset( $rawParams[':assoc:count'] ) ) {
318                $params[':assoc:count'] = $rawParams[':assoc:count'];
319            }
320        }
321
322        return $params;
323    }
324
325    public function formatParametersForApi() {
326        $ret = parent::formatParametersForApi();
327        if ( isset( $ret['ids'] ) ) {
328            ApiResult::setIndexedTagName( $ret['ids'], 'id' );
329        }
330        return $ret;
331    }
332}