Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
4.55% covered (danger)
4.55%
13 / 286
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImageListPager
4.56% covered (danger)
4.56%
13 / 285
0.00% covered (danger)
0.00%
0 / 22
6071.64
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
110
 getRelevantUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 outputUserDoesNotExist
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 buildQueryConds
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getFieldNames
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 isFieldSortable
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfoReal
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
20
 reallyDoQuery
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
42
 combineResult
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
 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 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 doBatchLookups
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 formatValue
24.07% covered (danger)
24.07%
13 / 54
0.00% covered (danger)
0.00%
0 / 1
113.48
 getEscapedLimitSelectList
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getForm
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
2
 getTableClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNavClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSortHeaderClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPagingQueries
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getDefaultQuery
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getTitle
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 LocalRepo;
25use MediaWiki\Cache\LinkBatchFactory;
26use MediaWiki\CommentFormatter\CommentFormatter;
27use MediaWiki\CommentStore\CommentStore;
28use MediaWiki\Context\IContextSource;
29use MediaWiki\Html\Html;
30use MediaWiki\HTMLForm\HTMLForm;
31use MediaWiki\Linker\Linker;
32use MediaWiki\Linker\LinkRenderer;
33use MediaWiki\MainConfigNames;
34use MediaWiki\SpecialPage\SpecialPage;
35use MediaWiki\Title\Title;
36use MediaWiki\User\User;
37use MediaWiki\User\UserNameUtils;
38use MediaWiki\Xml\Xml;
39use RepoGroup;
40use UnexpectedValueException;
41use Wikimedia\Rdbms\FakeResultWrapper;
42use Wikimedia\Rdbms\IConnectionProvider;
43use Wikimedia\Rdbms\IResultWrapper;
44use Wikimedia\Rdbms\Subquery;
45
46/**
47 * @ingroup Pager
48 */
49class ImageListPager extends TablePager {
50
51    /** @var string[]|null */
52    protected $mFieldNames = null;
53    /**
54     * @deprecated Subclasses should override {@see buildQueryConds} instead
55     * @var array
56     */
57    protected $mQueryConds = [];
58    /** @var string|null */
59    protected $mUserName = null;
60    /** @var User|null The relevant user */
61    protected $mUser = null;
62    /** @var bool */
63    protected $mIncluding = false;
64    /** @var bool */
65    protected $mShowAll = false;
66    /** @var string */
67    protected $mTableName = 'image';
68
69    private CommentStore $commentStore;
70    private LocalRepo $localRepo;
71    private CommentFormatter $commentFormatter;
72    private LinkBatchFactory $linkBatchFactory;
73
74    /**
75     * The unique sort fields for the sort options for unique paginate
76     */
77    private const INDEX_FIELDS = [
78        'img_timestamp' => [ 'img_timestamp', 'img_name' ],
79        'img_name' => [ 'img_name' ],
80        'img_size' => [ 'img_size', 'img_name' ],
81    ];
82
83    /**
84     * @param IContextSource $context
85     * @param CommentStore $commentStore
86     * @param LinkRenderer $linkRenderer
87     * @param IConnectionProvider $dbProvider
88     * @param RepoGroup $repoGroup
89     * @param UserNameUtils $userNameUtils
90     * @param CommentFormatter $commentFormatter
91     * @param LinkBatchFactory $linkBatchFactory
92     * @param string $userName
93     * @param string $search
94     * @param bool $including
95     * @param bool $showAll
96     */
97    public function __construct(
98        IContextSource $context,
99        CommentStore $commentStore,
100        LinkRenderer $linkRenderer,
101        IConnectionProvider $dbProvider,
102        RepoGroup $repoGroup,
103        UserNameUtils $userNameUtils,
104        CommentFormatter $commentFormatter,
105        LinkBatchFactory $linkBatchFactory,
106        $userName,
107        $search,
108        $including,
109        $showAll
110    ) {
111        $this->setContext( $context );
112
113        $this->mIncluding = $including;
114        $this->mShowAll = $showAll;
115
116        if ( $userName !== null && $userName !== '' ) {
117            $nt = Title::makeTitleSafe( NS_USER, $userName );
118            if ( $nt === null ) {
119                $this->outputUserDoesNotExist( $userName );
120            } else {
121                $this->mUserName = $nt->getText();
122                $user = User::newFromName( $this->mUserName, false );
123                if ( $user ) {
124                    $this->mUser = $user;
125                }
126                if ( !$user || ( $user->isAnon() && !$userNameUtils->isIP( $user->getName() ) ) ) {
127                    $this->outputUserDoesNotExist( $userName );
128                }
129            }
130        }
131
132        if ( $including ||
133            $this->getRequest()->getText( 'sort', 'img_date' ) === 'img_date'
134        ) {
135            $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
136        } else {
137            $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
138        }
139        // Set database before parent constructor to avoid setting it there
140        $this->mDb = $dbProvider->getReplicaDatabase();
141
142        parent::__construct( $context, $linkRenderer );
143        $this->commentStore = $commentStore;
144        $this->localRepo = $repoGroup->getLocalRepo();
145        $this->commentFormatter = $commentFormatter;
146        $this->linkBatchFactory = $linkBatchFactory;
147    }
148
149    /**
150     * Get the user relevant to the ImageList
151     *
152     * @return User|null
153     */
154    public function getRelevantUser() {
155        return $this->mUser;
156    }
157
158    /**
159     * Add a message to the output stating that the user doesn't exist
160     *
161     * @param string $userName Unescaped user name
162     */
163    protected function outputUserDoesNotExist( $userName ) {
164        $this->getOutput()->addHTML( Html::warningBox(
165            $this->getOutput()->msg( 'listfiles-userdoesnotexist', wfEscapeWikiText( $userName ) )->parse(),
166            'mw-userpage-userdoesnotexist'
167        ) );
168    }
169
170    /**
171     * Build the where clause of the query.
172     *
173     * Replaces the older mQueryConds member variable.
174     * @param string $table Either "image" or "oldimage"
175     * @return array The query conditions.
176     */
177    protected function buildQueryConds( $table ) {
178        $conds = [];
179
180        if ( $this->mUserName !== null ) {
181            // getQueryInfoReal() should have handled the tables and joins.
182            $conds['actor_name'] = $this->mUserName;
183        }
184
185        if ( $table === 'oldimage' ) {
186            // Don't want to deal with revdel.
187            // Future fixme: Show partial information as appropriate.
188            // Would have to be careful about filtering by username when username is deleted.
189            $conds['oi_deleted'] = 0;
190        }
191
192        // Add mQueryConds in case anyone was subclassing and using the old variable.
193        return $conds + $this->mQueryConds;
194    }
195
196    protected function getFieldNames() {
197        if ( !$this->mFieldNames ) {
198            $this->mFieldNames = [
199                'img_timestamp' => $this->msg( 'listfiles_date' )->text(),
200                'img_name' => $this->msg( 'listfiles_name' )->text(),
201                'thumb' => $this->msg( 'listfiles_thumb' )->text(),
202                'img_size' => $this->msg( 'listfiles_size' )->text(),
203            ];
204            if ( $this->mUserName === null ) {
205                // Do not show username if filtering by username
206                $this->mFieldNames['img_actor'] = $this->msg( 'listfiles_user' )->text();
207            }
208            // img_description down here, in order so that its still after the username field.
209            $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text();
210
211            if ( $this->mShowAll ) {
212                $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text();
213            } elseif ( !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
214                $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
215            }
216        }
217
218        return $this->mFieldNames;
219    }
220
221    protected function isFieldSortable( $field ) {
222        if ( $this->mIncluding ) {
223            return false;
224        }
225        /* For reference, the indices we can use for sorting are:
226         * On the image table: img_actor_timestamp, img_size, img_timestamp
227         * On oldimage: oi_actor_timestamp, oi_name_timestamp
228         *
229         * In particular that means we cannot sort by timestamp when not filtering
230         * by user and including old images in the results. Which is sad. (T279982)
231         */
232        if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
233            if ( $this->mUserName !== null ) {
234                // If we're sorting by user, the index only supports sorting by time.
235                return $field === 'img_timestamp';
236            } elseif ( $this->mShowAll ) {
237                // no oi_timestamp index, so only alphabetical sorting in this case.
238                return $field === 'img_name';
239            }
240        }
241
242        return isset( self::INDEX_FIELDS[$field] );
243    }
244
245    public function getQueryInfo() {
246        // Hacky Hacky Hacky - I want to get query info
247        // for two different tables, without reimplementing
248        // the pager class.
249        return $this->getQueryInfoReal( $this->mTableName );
250    }
251
252    /**
253     * Actually get the query info.
254     *
255     * This is to allow displaying both stuff from image and oldimage table.
256     *
257     * This is a bit hacky.
258     *
259     * @param string $table Either 'image' or 'oldimage'
260     * @return array Query info
261     */
262    protected function getQueryInfoReal( $table ) {
263        $dbr = $this->getDatabase();
264        $prefix = $table === 'oldimage' ? 'oi' : 'img';
265
266        $tables = [ $table, 'actor' ];
267        $join_conds = [];
268
269        if ( $table === 'oldimage' ) {
270            $fields = [
271                'img_timestamp' => 'oi_timestamp',
272                'img_name' => 'oi_name',
273                'img_size' => 'oi_size',
274                'top' => $dbr->addQuotes( 'no' )
275            ];
276            $join_conds['actor'] = [ 'JOIN', 'actor_id=oi_actor' ];
277        } else {
278            $fields = [
279                'img_timestamp',
280                'img_name',
281                'img_size',
282                'top' => $dbr->addQuotes( 'yes' )
283            ];
284            $join_conds['actor'] = [ 'JOIN', 'actor_id=img_actor' ];
285        }
286
287        # Description field
288        $commentQuery = $this->commentStore->getJoin( $prefix . '_description' );
289        $tables += $commentQuery['tables'];
290        $fields += $commentQuery['fields'];
291        $join_conds += $commentQuery['joins'];
292        $fields['description_field'] = $dbr->addQuotes( "{$prefix}_description" );
293
294        # Actor fields
295        $fields[] = 'actor_user';
296        $fields[] = 'actor_name';
297
298        # Depends on $wgMiserMode
299        # Will also not happen if mShowAll is true.
300        if ( array_key_exists( 'count', $this->getFieldNames() ) ) {
301            $fields['count'] = new Subquery( $dbr->newSelectQueryBuilder()
302                ->select( 'COUNT(oi_archive_name)' )
303                ->from( 'oldimage' )
304                ->where( 'oi_name = img_name' )
305                ->caller( __METHOD__ )
306                ->getSQL()
307            );
308        }
309
310        return [
311            'tables' => $tables,
312            'fields' => $fields,
313            'conds' => $this->buildQueryConds( $table ),
314            'options' => [],
315            'join_conds' => $join_conds
316        ];
317    }
318
319    /**
320     * Override reallyDoQuery to mix together two queries.
321     *
322     * @param string $offset
323     * @param int $limit
324     * @param bool $order IndexPager::QUERY_ASCENDING or IndexPager::QUERY_DESCENDING
325     * @return IResultWrapper
326     */
327    public function reallyDoQuery( $offset, $limit, $order ) {
328        $dbr = $this->getDatabase();
329        $prevTableName = $this->mTableName;
330        $this->mTableName = 'image';
331        [ $tables, $fields, $conds, $fname, $options, $join_conds ] =
332            $this->buildQueryInfo( $offset, $limit, $order );
333        $imageRes = $dbr->newSelectQueryBuilder()
334            ->tables( is_array( $tables ) ? $tables : [ $tables ] )
335            ->fields( $fields )
336            ->conds( $conds )
337            ->caller( $fname )
338            ->options( $options )
339            ->joinConds( $join_conds )
340            ->fetchResultSet();
341        $this->mTableName = $prevTableName;
342
343        if ( !$this->mShowAll ) {
344            return $imageRes;
345        }
346
347        $this->mTableName = 'oldimage';
348
349        # Hacky...
350        $oldIndex = $this->mIndexField;
351        foreach ( $this->mIndexField as &$index ) {
352            if ( !str_starts_with( $index, 'img_' ) ) {
353                throw new UnexpectedValueException( "Expected to be sorting on an image table field" );
354            }
355            $index = 'oi_' . substr( $index, 4 );
356        }
357        unset( $index );
358
359        [ $tables, $fields, $conds, $fname, $options, $join_conds ] =
360            $this->buildQueryInfo( $offset, $limit, $order );
361        $oldimageRes = $dbr->newSelectQueryBuilder()
362            ->tables( is_array( $tables ) ? $tables : [ $tables ] )
363            ->fields( $fields )
364            ->conds( $conds )
365            ->caller( $fname )
366            ->options( $options )
367            ->joinConds( $join_conds )
368            ->fetchResultSet();
369
370        $this->mTableName = $prevTableName;
371        $this->mIndexField = $oldIndex;
372
373        return $this->combineResult( $imageRes, $oldimageRes, $limit, $order );
374    }
375
376    /**
377     * Combine results from 2 tables.
378     *
379     * Note: This will throw away some results
380     *
381     * @param IResultWrapper $res1
382     * @param IResultWrapper $res2
383     * @param int $limit
384     * @param bool $order IndexPager::QUERY_ASCENDING or IndexPager::QUERY_DESCENDING
385     * @return IResultWrapper $res1 and $res2 combined
386     */
387    protected function combineResult( $res1, $res2, $limit, $order ) {
388        $res1->rewind();
389        $res2->rewind();
390        $topRes1 = $res1->fetchObject();
391        $topRes2 = $res2->fetchObject();
392        $resultArray = [];
393        for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
394            if ( strcmp( $topRes1->{$this->mIndexField[0]}, $topRes2->{$this->mIndexField[0]} ) > 0 ) {
395                if ( $order !== IndexPager::QUERY_ASCENDING ) {
396                    $resultArray[] = $topRes1;
397                    $topRes1 = $res1->fetchObject();
398                } else {
399                    $resultArray[] = $topRes2;
400                    $topRes2 = $res2->fetchObject();
401                }
402            } elseif ( $order !== IndexPager::QUERY_ASCENDING ) {
403                $resultArray[] = $topRes2;
404                $topRes2 = $res2->fetchObject();
405            } else {
406                $resultArray[] = $topRes1;
407                $topRes1 = $res1->fetchObject();
408            }
409        }
410
411        for ( ; $i < $limit && $topRes1; $i++ ) {
412            $resultArray[] = $topRes1;
413            $topRes1 = $res1->fetchObject();
414        }
415
416        for ( ; $i < $limit && $topRes2; $i++ ) {
417            $resultArray[] = $topRes2;
418            $topRes2 = $res2->fetchObject();
419        }
420
421        return new FakeResultWrapper( $resultArray );
422    }
423
424    public function getIndexField() {
425        return [ self::INDEX_FIELDS[$this->mSort] ];
426    }
427
428    public function getDefaultSort() {
429        if ( $this->mShowAll &&
430            $this->getConfig()->get( MainConfigNames::MiserMode ) &&
431            $this->mUserName === null
432        ) {
433            // Unfortunately no index on oi_timestamp.
434            return 'img_name';
435        } else {
436            return 'img_timestamp';
437        }
438    }
439
440    protected function doBatchLookups() {
441        $this->mResult->seek( 0 );
442        $batch = $this->linkBatchFactory->newLinkBatch();
443        foreach ( $this->mResult as $row ) {
444            $batch->add( NS_USER, $row->actor_name );
445            $batch->add( NS_USER_TALK, $row->actor_name );
446            $batch->add( NS_FILE, $row->img_name );
447        }
448        $batch->execute();
449    }
450
451    /**
452     * @param string $field
453     * @param string|null $value
454     * @return string
455     */
456    public function formatValue( $field, $value ) {
457        $linkRenderer = $this->getLinkRenderer();
458        switch ( $field ) {
459            case 'thumb':
460                $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
461                $file = $this->localRepo->findFile( $this->getCurrentRow()->img_name, $opt );
462                // If statement for paranoia
463                if ( $file ) {
464                    $thumb = $file->transform( [ 'width' => 180, 'height' => 360 ] );
465                    if ( $thumb ) {
466                        return $thumb->toHtml( [ 'desc-link' => true, 'loading' => 'lazy' ] );
467                    }
468                    return $this->msg( 'thumbnail_error', '' )->escaped();
469                } else {
470                    return htmlspecialchars( $this->getCurrentRow()->img_name );
471                }
472            case 'img_timestamp':
473                // We may want to make this a link to the "old" version when displaying old files
474                return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) );
475            case 'img_name':
476                static $imgfile = null;
477                $imgfile ??= $this->msg( 'imgfile' )->text();
478
479                // Weird files can maybe exist? T24227
480                $filePage = Title::makeTitleSafe( NS_FILE, $value );
481                if ( $filePage ) {
482                    $html = $linkRenderer->makeKnownLink(
483                        $filePage,
484                        $filePage->getText()
485                    );
486                    $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
487                    $file = $this->localRepo->findFile( $value, $opt );
488                    if ( $file ) {
489                        $download = Xml::element(
490                            'a',
491                            [ 'href' => $file->getUrl() ],
492                            $imgfile
493                        );
494                        $html .= ' ' . $this->msg( 'parentheses' )->rawParams( $download )->escaped();
495                    }
496
497                    // Add delete links if allowed
498                    // From https://github.com/Wikia/app/pull/3859
499                    if ( $this->getAuthority()->probablyCan( 'delete', $filePage ) ) {
500                        $deleteMsg = $this->msg( 'listfiles-delete' )->text();
501
502                        $delete = $linkRenderer->makeKnownLink(
503                            $filePage, $deleteMsg, [], [ 'action' => 'delete' ]
504                        );
505                        $html .= ' ' . $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
506                    }
507
508                    return $html;
509                } else {
510                    return htmlspecialchars( $value );
511                }
512            case 'img_actor':
513                $userId = (int)$this->mCurrentRow->actor_user;
514                $userName = $this->mCurrentRow->actor_name;
515                return Linker::userLink( $userId, $userName )
516                    . Linker::userToolLinks( $userId, $userName );
517            case 'img_size':
518                return htmlspecialchars( $this->getLanguage()->formatSize( (int)$value ) );
519            case 'img_description':
520                $field = $this->mCurrentRow->description_field;
521                $value = $this->commentStore->getComment( $field, $this->mCurrentRow )->text;
522                return $this->commentFormatter->format( $value );
523            case 'count':
524                return htmlspecialchars( $this->getLanguage()->formatNum( intval( $value ) + 1 ) );
525            case 'top':
526                // Messages: listfiles-latestversion-yes, listfiles-latestversion-no
527                return $this->msg( 'listfiles-latestversion-' . $value )->escaped();
528            default:
529                throw new UnexpectedValueException( "Unknown field '$field'" );
530        }
531    }
532
533    /**
534     * Escape the options list
535     * @return array
536     */
537    private function getEscapedLimitSelectList(): array {
538        $list = $this->getLimitSelectList();
539        $result = [];
540        foreach ( $list as $key => $value ) {
541            $result[htmlspecialchars( $key )] = $value;
542        }
543        return $result;
544    }
545
546    public function getForm() {
547        $formDescriptor = [];
548        $formDescriptor['limit'] = [
549            'type' => 'radio',
550            'name' => 'limit',
551            'label-message' => 'table_pager_limit_label',
552            'options' => $this->getEscapedLimitSelectList(),
553            'flatlist' => true,
554            'default' => $this->mLimit
555        ];
556
557        $formDescriptor['user'] = [
558            'type' => 'user',
559            'name' => 'user',
560            'id' => 'mw-listfiles-user',
561            'label-message' => 'username',
562            'default' => $this->mUserName,
563            'size' => '40',
564            'maxlength' => '255',
565        ];
566
567        $formDescriptor['ilshowall'] = [
568            'type' => 'check',
569            'name' => 'ilshowall',
570            'id' => 'mw-listfiles-show-all',
571            'label-message' => 'listfiles-show-all',
572            'default' => $this->mShowAll,
573        ];
574
575        $query = $this->getRequest()->getQueryValues();
576        unset( $query['title'] );
577        unset( $query['limit'] );
578        unset( $query['ilsearch'] );
579        unset( $query['ilshowall'] );
580        unset( $query['user'] );
581
582        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
583            ->setMethod( 'get' )
584            ->setId( 'mw-listfiles-form' )
585            ->setTitle( $this->getTitle() )
586            ->setSubmitTextMsg( 'listfiles-pager-submit' )
587            ->setWrapperLegendMsg( 'listfiles' )
588            ->addHiddenFields( $query )
589            ->prepareForm()
590            ->displayForm( '' );
591    }
592
593    protected function getTableClass() {
594        return parent::getTableClass() . ' listfiles';
595    }
596
597    protected function getNavClass() {
598        return parent::getNavClass() . ' listfiles_nav';
599    }
600
601    protected function getSortHeaderClass() {
602        return parent::getSortHeaderClass() . ' listfiles_sort';
603    }
604
605    public function getPagingQueries() {
606        $queries = parent::getPagingQueries();
607        if ( $this->mUserName !== null ) {
608            # Append the username to the query string
609            foreach ( $queries as &$query ) {
610                if ( $query !== false ) {
611                    $query['user'] = $this->mUserName;
612                }
613            }
614        }
615
616        return $queries;
617    }
618
619    public function getDefaultQuery() {
620        $queries = parent::getDefaultQuery();
621        if ( !isset( $queries['user'] ) && $this->mUserName !== null ) {
622            $queries['user'] = $this->mUserName;
623        }
624
625        return $queries;
626    }
627
628    public function getTitle() {
629        return SpecialPage::getTitleFor( 'Listfiles' );
630    }
631}
632
633/**
634 * Retain the old class name for backwards compatibility.
635 * @deprecated since 1.41
636 */
637class_alias( ImageListPager::class, 'ImageListPager' );