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