Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 205
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProtectedPagesPager
0.00% covered (danger)
0.00%
0 / 204
0.00% covered (danger)
0.00%
0 / 9
1722
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 preprocessResults
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
 getFieldNames
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 formatValue
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 1
420
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
72
 getTableClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIndexField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isFieldSortable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup Pager
20 */
21
22namespace MediaWiki\Pager;
23
24use LogEventsList;
25use LogPage;
26use MediaWiki\Cache\LinkBatchFactory;
27use MediaWiki\CommentFormatter\RowCommentFormatter;
28use MediaWiki\CommentStore\CommentStore;
29use MediaWiki\Context\IContextSource;
30use MediaWiki\Html\Html;
31use MediaWiki\Linker\Linker;
32use MediaWiki\Linker\LinkRenderer;
33use MediaWiki\Title\Title;
34use UnexpectedValueException;
35use Wikimedia\Rdbms\FakeResultWrapper;
36use Wikimedia\Rdbms\IConnectionProvider;
37
38class ProtectedPagesPager extends TablePager {
39
40    /** @var string */
41    private $type;
42    /** @var string */
43    private $level;
44    /** @var int|null */
45    private $namespace;
46    /** @var string */
47    private $sizetype;
48    /** @var int */
49    private $size;
50    /** @var bool */
51    private $indefonly;
52    /** @var bool */
53    private $cascadeonly;
54    /** @var bool */
55    private $noredirect;
56
57    private CommentStore $commentStore;
58    private LinkBatchFactory $linkBatchFactory;
59    private RowCommentFormatter $rowCommentFormatter;
60
61    /** @var string[] */
62    private $formattedComments = [];
63
64    /**
65     * @param IContextSource $context
66     * @param CommentStore $commentStore
67     * @param LinkBatchFactory $linkBatchFactory
68     * @param LinkRenderer $linkRenderer
69     * @param IConnectionProvider $dbProvider
70     * @param RowCommentFormatter $rowCommentFormatter
71     * @param string $type
72     * @param string $level
73     * @param int|null $namespace
74     * @param string $sizetype
75     * @param int|null $size
76     * @param bool $indefonly
77     * @param bool $cascadeonly
78     * @param bool $noredirect
79     */
80    public function __construct(
81        IContextSource $context,
82        CommentStore $commentStore,
83        LinkBatchFactory $linkBatchFactory,
84        LinkRenderer $linkRenderer,
85        IConnectionProvider $dbProvider,
86        RowCommentFormatter $rowCommentFormatter,
87        $type,
88        $level,
89        $namespace,
90        $sizetype,
91        $size,
92        $indefonly,
93        $cascadeonly,
94        $noredirect
95    ) {
96        // Set database before parent constructor to avoid setting it there
97        $this->mDb = $dbProvider->getReplicaDatabase();
98        parent::__construct( $context, $linkRenderer );
99        $this->commentStore = $commentStore;
100        $this->linkBatchFactory = $linkBatchFactory;
101        $this->rowCommentFormatter = $rowCommentFormatter;
102        $this->type = $type ?: 'edit';
103        $this->level = $level;
104        $this->namespace = $namespace;
105        $this->sizetype = $sizetype;
106        $this->size = intval( $size );
107        $this->indefonly = (bool)$indefonly;
108        $this->cascadeonly = (bool)$cascadeonly;
109        $this->noredirect = (bool)$noredirect;
110    }
111
112    public function preprocessResults( $result ) {
113        # Do a link batch query
114        $lb = $this->linkBatchFactory->newLinkBatch();
115        $rowsWithComments = [];
116
117        foreach ( $result as $row ) {
118            $lb->add( $row->page_namespace, $row->page_title );
119            // for old protection rows, user and comment are missing
120            if ( $row->actor_name !== null ) {
121                $lb->add( NS_USER, $row->actor_name );
122                $lb->add( NS_USER_TALK, $row->actor_name );
123            }
124            if ( $row->log_timestamp !== null ) {
125                $rowsWithComments[] = $row;
126            }
127        }
128
129        $lb->execute();
130
131        // Format the comments
132        $this->formattedComments = $this->rowCommentFormatter->formatRows(
133            new FakeResultWrapper( $rowsWithComments ),
134            'log_comment',
135            null,
136            null,
137            'pr_id'
138        );
139    }
140
141    protected function getFieldNames() {
142        static $headers = null;
143
144        if ( $headers === null ) {
145            $headers = [
146                'log_timestamp' => 'protectedpages-timestamp',
147                'pr_page' => 'protectedpages-page',
148                'pr_expiry' => 'protectedpages-expiry',
149                'actor_user' => 'protectedpages-performer',
150                'pr_params' => 'protectedpages-params',
151                'log_comment' => 'protectedpages-reason',
152            ];
153            foreach ( $headers as $key => $val ) {
154                $headers[$key] = $this->msg( $val )->text();
155            }
156        }
157
158        return $headers;
159    }
160
161    /**
162     * @param string $field
163     * @param string|null $value
164     * @return string HTML
165     */
166    public function formatValue( $field, $value ) {
167        /** @var stdClass $row */
168        $row = $this->mCurrentRow;
169        $linkRenderer = $this->getLinkRenderer();
170
171        switch ( $field ) {
172            case 'log_timestamp':
173                // when timestamp is null, this is a old protection row
174                if ( $value === null ) {
175                    $formatted = Html::rawElement(
176                        'span',
177                        [ 'class' => 'mw-protectedpages-unknown' ],
178                        $this->msg( 'protectedpages-unknown-timestamp' )->escaped()
179                    );
180                } else {
181                    $formatted = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
182                        $value, $this->getUser() ) );
183                }
184                break;
185
186            case 'pr_page':
187                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
188                if ( !$title ) {
189                    $formatted = Html::element(
190                        'span',
191                        [ 'class' => 'mw-invalidtitle' ],
192                        Linker::getInvalidTitleDescription(
193                            $this->getContext(),
194                            $row->page_namespace,
195                            $row->page_title
196                        )
197                    );
198                } else {
199                    $formatted = $linkRenderer->makeLink( $title );
200                }
201                $formatted = Html::rawElement( 'bdi', [
202                    'dir' => $this->getLanguage()->getDir()
203                ], $formatted );
204                if ( $row->page_len !== null ) {
205                    $formatted .= ' ' . Html::rawElement(
206                            'span',
207                            [ 'class' => 'mw-protectedpages-length' ],
208                            Linker::formatRevisionSize( $row->page_len )
209                        );
210                }
211                break;
212
213            case 'pr_expiry':
214                $formatted = htmlspecialchars( $this->getLanguage()->formatExpiry(
215                    $value, /* User preference timezone */true, 'infinity', $this->getUser() ) );
216                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
217                if ( $title && $this->getAuthority()->isAllowed( 'protect' ) ) {
218                    $changeProtection = $linkRenderer->makeKnownLink(
219                        $title,
220                        $this->msg( 'protect_change' )->text(),
221                        [],
222                        [ 'action' => 'unprotect' ]
223                    );
224                    $formatted .= ' ' . Html::rawElement(
225                            'span',
226                            [ 'class' => 'mw-protectedpages-actions' ],
227                            $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped()
228                        );
229                }
230                break;
231
232            case 'actor_user':
233                // when timestamp is null, this is a old protection row
234                if ( $row->log_timestamp === null ) {
235                    $formatted = Html::rawElement(
236                        'span',
237                        [ 'class' => 'mw-protectedpages-unknown' ],
238                        $this->msg( 'protectedpages-unknown-performer' )->escaped()
239                    );
240                } else {
241                    $username = $row->actor_name;
242                    if ( LogEventsList::userCanBitfield(
243                        $row->log_deleted,
244                        LogPage::DELETED_USER,
245                        $this->getAuthority()
246                    ) ) {
247                        $formatted = Linker::userLink( (int)$value, $username )
248                            . Linker::userToolLinks( (int)$value, $username );
249                    } else {
250                        $formatted = $this->msg( 'rev-deleted-user' )->escaped();
251                    }
252                    if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
253                        $formatted = '<span class="history-deleted">' . $formatted . '</span>';
254                    }
255                }
256                break;
257
258            case 'pr_params':
259                $params = [];
260                // Messages: restriction-level-sysop, restriction-level-autoconfirmed
261                $params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
262                if ( $row->pr_cascade ) {
263                    $params[] = $this->msg( 'protect-summary-cascade' )->escaped();
264                }
265                $formatted = $this->getLanguage()->commaList( $params );
266                break;
267
268            case 'log_comment':
269                // when timestamp is null, this is an old protection row
270                if ( $row->log_timestamp === null ) {
271                    $formatted = Html::rawElement(
272                        'span',
273                        [ 'class' => 'mw-protectedpages-unknown' ],
274                        $this->msg( 'protectedpages-unknown-reason' )->escaped()
275                    );
276                } else {
277                    if ( LogEventsList::userCanBitfield(
278                        $row->log_deleted,
279                        LogPage::DELETED_COMMENT,
280                        $this->getAuthority()
281                    ) ) {
282                        $formatted = $this->formattedComments[$row->pr_id];
283                    } else {
284                        $formatted = $this->msg( 'rev-deleted-comment' )->escaped();
285                    }
286                    if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
287                        $formatted = '<span class="history-deleted">' . $formatted . '</span>';
288                    }
289                }
290                break;
291
292            default:
293                throw new UnexpectedValueException( "Unknown field '$field'" );
294        }
295
296        return $formatted;
297    }
298
299    public function getQueryInfo() {
300        $dbr = $this->getDatabase();
301        $conds = [
302            $dbr->expr( 'pr_expiry', '>', $dbr->timestamp() )
303                ->or( 'pr_expiry', '=', null ),
304            'page_id=pr_page',
305            $dbr->expr( 'pr_type', '=', $this->type ),
306        ];
307
308        if ( $this->sizetype == 'min' ) {
309            $conds[] = 'page_len>=' . $this->size;
310        } elseif ( $this->sizetype == 'max' ) {
311            $conds[] = 'page_len<=' . $this->size;
312        }
313
314        if ( $this->indefonly ) {
315            $conds['pr_expiry'] = [ $dbr->getInfinity(), null ];
316        }
317        if ( $this->cascadeonly ) {
318            $conds['pr_cascade'] = 1;
319        }
320        if ( $this->noredirect ) {
321            $conds['page_is_redirect'] = 0;
322        }
323
324        if ( $this->level ) {
325            $conds[] = $dbr->expr( 'pr_level', '=', $this->level );
326        }
327        if ( $this->namespace !== null ) {
328            $conds[] = $dbr->expr( 'page_namespace', '=', $this->namespace );
329        }
330
331        $commentQuery = $this->commentStore->getJoin( 'log_comment' );
332
333        return [
334            'tables' => [
335                'page', 'page_restrictions', 'log_search',
336                'logparen' => [ 'logging', 'actor' ] + $commentQuery['tables'],
337            ],
338            'fields' => [
339                'pr_id',
340                'page_namespace',
341                'page_title',
342                'page_len',
343                'pr_type',
344                'pr_level',
345                'pr_expiry',
346                'pr_cascade',
347                'log_timestamp',
348                'log_deleted',
349                'actor_name',
350                'actor_user'
351            ] + $commentQuery['fields'],
352            'conds' => $conds,
353            'join_conds' => [
354                'log_search' => [
355                    'LEFT JOIN', [
356                        'ls_field' => 'pr_id', 'ls_value = ' . $dbr->buildStringCast( 'pr_id' )
357                    ]
358                ],
359                'logparen' => [
360                    'LEFT JOIN', [
361                        'ls_log_id = log_id'
362                    ]
363                ],
364                'actor' => [
365                    'JOIN', [
366                        'actor_id=log_actor'
367                    ]
368                ]
369            ] + $commentQuery['joins']
370        ];
371    }
372
373    protected function getTableClass() {
374        return parent::getTableClass() . ' mw-protectedpages';
375    }
376
377    public function getIndexField() {
378        return 'pr_id';
379    }
380
381    public function getDefaultSort() {
382        return 'pr_id';
383    }
384
385    protected function isFieldSortable( $field ) {
386        // no index for sorting exists
387        return false;
388    }
389}
390
391/**
392 * Retain the old class name for backwards compatibility.
393 * @deprecated since 1.41
394 */
395class_alias( ProtectedPagesPager::class, 'ProtectedPagesPager' );