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