MediaWiki master
ImageListPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
41use UnexpectedValueException;
46
51
53 protected ?array $mFieldNames = null;
58 protected $mQueryConds = [];
59 protected ?string $mUserName = null;
61 protected ?User $mUser = null;
62 protected ?bool $mIncluding = false;
63 protected bool $mShowAll = false;
64 protected string $mTableName = 'image';
65
66 private CommentStore $commentStore;
67 private LocalRepo $localRepo;
68 private RowCommentFormatter $rowCommentFormatter;
69 private LinkBatchFactory $linkBatchFactory;
70
72 private array $formattedComments = [];
73
74 private int $migrationStage;
75
79 private const INDEX_FIELDS = [
80 'img_timestamp' => [ 'img_timestamp', 'img_name' ],
81 'img_name' => [ 'img_name' ],
82 'img_size' => [ 'img_size', 'img_name' ],
83 ];
84
85 public function __construct(
86 IContextSource $context,
87 CommentStore $commentStore,
88 LinkRenderer $linkRenderer,
89 IConnectionProvider $dbProvider,
90 RepoGroup $repoGroup,
91 UserNameUtils $userNameUtils,
92 RowCommentFormatter $rowCommentFormatter,
93 LinkBatchFactory $linkBatchFactory,
94 ?string $userName,
95 string $search,
96 ?bool $including,
97 bool $showAll
98 ) {
99 $this->setContext( $context );
100
101 $this->mIncluding = $including;
102 $this->mShowAll = $showAll;
103
104 if ( $userName !== null && $userName !== '' ) {
105 $nt = Title::makeTitleSafe( NS_USER, $userName );
106 if ( $nt === null ) {
107 $this->outputUserDoesNotExist( $userName );
108 } else {
109 $this->mUserName = $nt->getText();
110 $user = User::newFromName( $this->mUserName, false );
111 if ( $user ) {
112 $this->mUser = $user;
113 }
114 if ( !$user || ( $user->isAnon() && !$userNameUtils->isIP( $user->getName() ) ) ) {
115 $this->outputUserDoesNotExist( $userName );
116 }
117 }
118 }
119
120 if ( $including ||
121 $this->getRequest()->getText( 'sort', 'img_date' ) === 'img_date'
122 ) {
123 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
124 } else {
125 $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
126 }
127 // Set database before parent constructor to avoid setting it there
128 $this->mDb = $dbProvider->getReplicaDatabase();
129
130 parent::__construct( $context, $linkRenderer );
131 $this->commentStore = $commentStore;
132 $this->localRepo = $repoGroup->getLocalRepo();
133 $this->rowCommentFormatter = $rowCommentFormatter;
134 $this->linkBatchFactory = $linkBatchFactory;
135 $this->migrationStage = $this->getConfig()->get(
137 );
138 }
139
145 public function getRelevantUser() {
146 return $this->mUser;
147 }
148
154 protected function outputUserDoesNotExist( $userName ) {
155 $out = $this->getOutput();
156 $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
157 $out->addHTML(
158 Html::warningBox(
159 $out->msg(
160 'listfiles-userdoesnotexist', wfEscapeWikiText( $userName )
161 )->parse(),
162 'mw-userpage-userdoesnotexist'
163 )
164 );
165 }
166
174 protected function buildQueryCondsOld( $table ) {
175 $conds = [];
176
177 if ( $this->mUserName !== null ) {
178 // getQueryInfoReal() should have handled the tables and joins.
179 $conds['actor_name'] = $this->mUserName;
180 }
181
182 if ( $table === 'oldimage' ) {
183 // Don't want to deal with revdel.
184 // Future fixme: Show partial information as appropriate.
185 // Would have to be careful about filtering by username when username is deleted.
186 $conds['oi_deleted'] = 0;
187 }
188
189 // Add mQueryConds in case anyone was subclassing and using the old variable.
190 return $conds + $this->mQueryConds;
191 }
192
193 private function buildQueryConds() {
194 $conds = [
195 'file_deleted' => 0,
196 'fr_deleted' => 0,
197 ];
198
199 if ( $this->mUserName !== null ) {
200 // getQueryInfoReal() should have handled the tables and joins.
201 $conds['actor_name'] = $this->mUserName;
202 }
203
204 if ( !$this->mShowAll ) {
205 $conds[] = 'file_latest = fr_id';
206 }
207 return $conds;
208 }
209
210 protected function getFieldNames() {
211 if ( !$this->mFieldNames ) {
212 $this->mFieldNames = [
213 'img_timestamp' => $this->msg( 'listfiles_date' )->text(),
214 'img_name' => $this->msg( 'listfiles_name' )->text(),
215 'thumb' => $this->msg( 'listfiles_thumb' )->text(),
216 'img_size' => $this->msg( 'listfiles_size' )->text(),
217 ];
218 if ( $this->mUserName === null ) {
219 // Do not show username if filtering by username
220 $this->mFieldNames['img_actor'] = $this->msg( 'listfiles_user' )->text();
221 }
222 // img_description down here, in order so that its still after the username field.
223 $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text();
224
225 if ( $this->mShowAll ) {
226 $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text();
227 } elseif ( !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
228 $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
229 }
230 }
231
232 return $this->mFieldNames;
233 }
234
235 protected function isFieldSortable( $field ) {
236 if ( $this->mIncluding ) {
237 return false;
238 }
239 /* For reference, the indices we can use for sorting are:
240 * On the image table: img_actor_timestamp, img_size, img_timestamp
241 * On oldimage: oi_actor_timestamp, oi_name_timestamp
242 *
243 * In particular that means we cannot sort by timestamp when not filtering
244 * by user and including old images in the results. Which is sad. (T279982)
245 */
246 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
247 if ( $this->mUserName !== null ) {
248 // If we're sorting by user, the index only supports sorting by time.
249 return $field === 'img_timestamp';
250 } elseif ( $this->mShowAll ) {
251 // no oi_timestamp index, so only alphabetical sorting in this case.
252 return $field === 'img_name';
253 }
254 }
255
256 return isset( self::INDEX_FIELDS[$field] );
257 }
258
259 public function getQueryInfo() {
260 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
261 // Hacky Hacky Hacky - I want to get query info
262 // for two different tables, without reimplementing
263 // the pager class.
264 return $this->getQueryInfoReal( $this->mTableName );
265 }
266 $dbr = $this->getDatabase();
267 $tables = [ 'filerevision', 'file', 'actor' ];
268 $fields = [
269 'img_timestamp' => 'fr_timestamp',
270 'img_name' => 'file_name',
271 'img_size' => 'fr_size',
272 'top' => 'CASE WHEN file_latest = fr_id THEN \'yes\' ELSE \'no\' END',
273 ];
274 $join_conds = [
275 'filerevision' => [ 'JOIN', 'fr_file=file_id' ],
276 'actor' => [ 'JOIN', 'actor_id=fr_actor' ]
277 ];
278
279 # Description field
280 $commentQuery = $this->commentStore->getJoin( 'fr_description' );
281 $tables += $commentQuery['tables'];
282 $fields += $commentQuery['fields'];
283 $join_conds += $commentQuery['joins'];
284 $fields['description_field'] = $dbr->addQuotes( "fr_description" );
285
286 # Actor fields
287 $fields[] = 'actor_user';
288 $fields[] = 'actor_name';
289
290 # Depends on $wgMiserMode
291 # Will also not happen if mShowAll is true.
292 if ( array_key_exists( 'count', $this->getFieldNames() ) ) {
293 $fields['count'] = new Subquery( $dbr->newSelectQueryBuilder()
294 ->select( 'COUNT(fr_archive_name)' )
295 ->from( 'filerevision' )
296 ->where( 'fr_file = file_id' )
297 ->caller( __METHOD__ )
298 ->getSQL()
299 );
300 }
301
302 return [
303 'tables' => $tables,
304 'fields' => $fields,
305 'conds' => $this->buildQueryConds(),
306 'options' => [],
307 'join_conds' => $join_conds
308 ];
309 }
310
321 protected function getQueryInfoReal( $table ) {
322 $dbr = $this->getDatabase();
323 $prefix = $table === 'oldimage' ? 'oi' : 'img';
324
325 $tables = [ $table, 'actor' ];
326 $join_conds = [];
327
328 if ( $table === 'oldimage' ) {
329 $fields = [
330 'img_timestamp' => 'oi_timestamp',
331 'img_name' => 'oi_name',
332 'img_size' => 'oi_size',
333 'top' => $dbr->addQuotes( 'no' )
334 ];
335 $join_conds['actor'] = [ 'JOIN', 'actor_id=oi_actor' ];
336 } else {
337 $fields = [
338 'img_timestamp',
339 'img_name',
340 'img_size',
341 'top' => $dbr->addQuotes( 'yes' )
342 ];
343 $join_conds['actor'] = [ 'JOIN', 'actor_id=img_actor' ];
344 }
345
346 # Description field
347 $commentQuery = $this->commentStore->getJoin( $prefix . '_description' );
348 $tables += $commentQuery['tables'];
349 $fields += $commentQuery['fields'];
350 $join_conds += $commentQuery['joins'];
351 $fields['description_field'] = $dbr->addQuotes( "{$prefix}_description" );
352
353 # Actor fields
354 $fields[] = 'actor_user';
355 $fields[] = 'actor_name';
356
357 # Depends on $wgMiserMode
358 # Will also not happen if mShowAll is true.
359 if ( array_key_exists( 'count', $this->getFieldNames() ) ) {
360 $fields['count'] = new Subquery( $dbr->newSelectQueryBuilder()
361 ->select( 'COUNT(oi_archive_name)' )
362 ->from( 'oldimage' )
363 ->where( 'oi_name = img_name' )
364 ->caller( __METHOD__ )
365 ->getSQL()
366 );
367 }
368
369 return [
370 'tables' => $tables,
371 'fields' => $fields,
372 'conds' => $this->buildQueryCondsOld( $table ),
373 'options' => [],
374 'join_conds' => $join_conds
375 ];
376 }
377
378 public function reallyDoQuery( $offset, $limit, $order ) {
379 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
380 return $this->reallyDoQueryOld( $offset, $limit, $order );
381 } else {
382 return parent::reallyDoQuery( $offset, $limit, $order );
383 }
384 }
385
394 public function reallyDoQueryOld( $offset, $limit, $order ) {
395 $dbr = $this->getDatabase();
396 $prevTableName = $this->mTableName;
397 $this->mTableName = 'image';
398 [ $tables, $fields, $conds, $fname, $options, $join_conds ] =
399 $this->buildQueryInfo( $offset, $limit, $order );
400 $imageRes = $dbr->newSelectQueryBuilder()
401 ->tables( is_array( $tables ) ? $tables : [ $tables ] )
402 ->fields( $fields )
403 ->conds( $conds )
404 ->caller( $fname )
405 ->options( $options )
406 ->joinConds( $join_conds )
407 ->fetchResultSet();
408 $this->mTableName = $prevTableName;
409
410 if ( !$this->mShowAll ) {
411 return $imageRes;
412 }
413
414 $this->mTableName = 'oldimage';
415
416 # Hacky...
417 $oldIndex = $this->mIndexField;
418 foreach ( $this->mIndexField as &$index ) {
419 if ( !str_starts_with( $index, 'img_' ) ) {
420 throw new UnexpectedValueException( "Expected to be sorting on an image table field" );
421 }
422 $index = 'oi_' . substr( $index, 4 );
423 }
424 unset( $index );
425
426 [ $tables, $fields, $conds, $fname, $options, $join_conds ] =
427 $this->buildQueryInfo( $offset, $limit, $order );
428 $oldimageRes = $dbr->newSelectQueryBuilder()
429 ->tables( is_array( $tables ) ? $tables : [ $tables ] )
430 ->fields( $fields )
431 ->conds( $conds )
432 ->caller( $fname )
433 ->options( $options )
434 ->joinConds( $join_conds )
435 ->fetchResultSet();
436
437 $this->mTableName = $prevTableName;
438 $this->mIndexField = $oldIndex;
439
440 return $this->combineResult( $imageRes, $oldimageRes, $limit, $order );
441 }
442
454 protected function combineResult( $res1, $res2, $limit, $order ) {
455 $res1->rewind();
456 $res2->rewind();
457 $topRes1 = $res1->fetchObject();
458 $topRes2 = $res2->fetchObject();
459 $resultArray = [];
460 for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
461 if ( strcmp( $topRes1->{$this->mIndexField[0]}, $topRes2->{$this->mIndexField[0]} ) > 0 ) {
462 if ( $order !== IndexPager::QUERY_ASCENDING ) {
463 $resultArray[] = $topRes1;
464 $topRes1 = $res1->fetchObject();
465 } else {
466 $resultArray[] = $topRes2;
467 $topRes2 = $res2->fetchObject();
468 }
469 } elseif ( $order !== IndexPager::QUERY_ASCENDING ) {
470 $resultArray[] = $topRes2;
471 $topRes2 = $res2->fetchObject();
472 } else {
473 $resultArray[] = $topRes1;
474 $topRes1 = $res1->fetchObject();
475 }
476 }
477
478 for ( ; $i < $limit && $topRes1; $i++ ) {
479 $resultArray[] = $topRes1;
480 $topRes1 = $res1->fetchObject();
481 }
482
483 for ( ; $i < $limit && $topRes2; $i++ ) {
484 $resultArray[] = $topRes2;
485 $topRes2 = $res2->fetchObject();
486 }
487
488 return new FakeResultWrapper( $resultArray );
489 }
490
491 public function getIndexField() {
492 return [ self::INDEX_FIELDS[$this->mSort] ];
493 }
494
495 public function getDefaultSort() {
496 if ( $this->mShowAll &&
497 $this->getConfig()->get( MainConfigNames::MiserMode ) &&
498 $this->mUserName === null
499 ) {
500 // Unfortunately no index on oi_timestamp.
501 return 'img_name';
502 } else {
503 return 'img_timestamp';
504 }
505 }
506
507 protected function doBatchLookups() {
508 $this->mResult->seek( 0 );
509 $batch = $this->linkBatchFactory->newLinkBatch();
510 $rowsWithComments = [ 'img_description' => [], 'oi_description' => [], 'fr_description' => [] ];
511 foreach ( $this->mResult as $i => $row ) {
512 $batch->addUser( new UserIdentityValue( $row->actor_user ?? 0, $row->actor_name ) );
513 $batch->add( NS_FILE, $row->img_name );
514 $rowsWithComments[$row->description_field][$i] = $row;
515 }
516 $batch->execute();
517
518 // Format the comments
519 if ( $rowsWithComments['img_description'] ) {
520 $this->formattedComments += $this->rowCommentFormatter->formatRows(
521 $rowsWithComments['img_description'],
522 'img_description'
523 );
524 }
525 if ( $rowsWithComments['oi_description'] ) {
526 $this->formattedComments += $this->rowCommentFormatter->formatRows(
527 $rowsWithComments['oi_description'],
528 'oi_description'
529 );
530 }
531 if ( $rowsWithComments['fr_description'] ) {
532 $this->formattedComments += $this->rowCommentFormatter->formatRows(
533 $rowsWithComments['fr_description'],
534 'fr_description'
535 );
536 }
537 }
538
544 public function formatValue( $field, $value ) {
545 $linkRenderer = $this->getLinkRenderer();
546 switch ( $field ) {
547 case 'thumb':
548 $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
549 $file = $this->localRepo->findFile( $this->getCurrentRow()->img_name, $opt );
550 // If statement for paranoia
551 if ( $file ) {
552 $thumb = $file->transform( [ 'width' => 180, 'height' => 360 ] );
553 if ( $thumb ) {
554 return $thumb->toHtml( [ 'desc-link' => true, 'loading' => 'lazy' ] );
555 }
556 return $this->msg( 'thumbnail_error', '' )->escaped();
557 } else {
558 return htmlspecialchars( $this->getCurrentRow()->img_name );
559 }
560 case 'img_timestamp':
561 // We may want to make this a link to the "old" version when displaying old files
562 return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) );
563 case 'img_name':
564 static $imgfile = null;
565 $imgfile ??= $this->msg( 'imgfile' )->text();
566
567 // Weird files can maybe exist? T24227
568 $filePage = Title::makeTitleSafe( NS_FILE, $value );
569 if ( $filePage ) {
570 $html = $linkRenderer->makeKnownLink(
571 $filePage,
572 $filePage->getText()
573 );
574 $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
575 $file = $this->localRepo->findFile( $value, $opt );
576 if ( $file ) {
577 $download = Xml::element(
578 'a',
579 [ 'href' => $file->getUrl() ],
580 $imgfile
581 );
582 $html .= ' ' . $this->msg( 'parentheses' )->rawParams( $download )->escaped();
583 }
584
585 // Add delete links if allowed
586 // From https://github.com/Wikia/app/pull/3859
587 if ( $this->getAuthority()->probablyCan( 'delete', $filePage ) ) {
588 $deleteMsg = $this->msg( 'listfiles-delete' )->text();
589
590 $delete = $linkRenderer->makeKnownLink(
591 $filePage, $deleteMsg, [], [ 'action' => 'delete' ]
592 );
593 $html .= ' ' . $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
594 }
595
596 return $html;
597 } else {
598 return htmlspecialchars( $value );
599 }
600 case 'img_actor':
601 $userId = (int)$this->mCurrentRow->actor_user;
602 $userName = $this->mCurrentRow->actor_name;
603 return Linker::userLink( $userId, $userName )
604 . Linker::userToolLinks( $userId, $userName );
605 case 'img_size':
606 return htmlspecialchars( $this->getLanguage()->formatSize( (int)$value ) );
607 case 'img_description':
608 return $this->formattedComments[$this->getResultOffset()];
609 case 'count':
610 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
611 return htmlspecialchars( $this->getLanguage()->formatNum( intval( $value ) + 1 ) );
612 } else {
613 return htmlspecialchars( $this->getLanguage()->formatNum( intval( $value ) ) );
614 }
615 case 'top':
616 // Messages: listfiles-latestversion-yes, listfiles-latestversion-no
617 return $this->msg( 'listfiles-latestversion-' . $value )->escaped();
618 default:
619 throw new UnexpectedValueException( "Unknown field '$field'" );
620 }
621 }
622
626 private function getEscapedLimitSelectList(): array {
627 $list = $this->getLimitSelectList();
628 $result = [];
629 foreach ( $list as $key => $value ) {
630 $result[htmlspecialchars( $key )] = $value;
631 }
632 return $result;
633 }
634
635 public function getForm() {
636 $formDescriptor = [];
637 $formDescriptor['limit'] = [
638 'type' => 'radio',
639 'name' => 'limit',
640 'label-message' => 'table_pager_limit_label',
641 'options' => $this->getEscapedLimitSelectList(),
642 'flatlist' => true,
643 'default' => $this->mLimit
644 ];
645
646 $formDescriptor['user'] = [
647 'type' => 'user',
648 'name' => 'user',
649 'id' => 'mw-listfiles-user',
650 'label-message' => 'username',
651 'default' => $this->mUserName,
652 'size' => '40',
653 'maxlength' => '255',
654 ];
655
656 $formDescriptor['ilshowall'] = [
657 'type' => 'check',
658 'name' => 'ilshowall',
659 'id' => 'mw-listfiles-show-all',
660 'label-message' => 'listfiles-show-all',
661 'default' => $this->mShowAll,
662 ];
663
664 $query = $this->getRequest()->getQueryValues();
665 unset( $query['title'] );
666 unset( $query['limit'] );
667 unset( $query['ilsearch'] );
668 unset( $query['ilshowall'] );
669 unset( $query['user'] );
670
671 HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
672 ->setMethod( 'get' )
673 ->setId( 'mw-listfiles-form' )
674 ->setTitle( $this->getTitle() )
675 ->setSubmitTextMsg( 'listfiles-pager-submit' )
676 ->setWrapperLegendMsg( 'listfiles' )
677 ->addHiddenFields( $query )
678 ->prepareForm()
679 ->displayForm( '' );
680 }
681
682 protected function getTableClass() {
683 return parent::getTableClass() . ' listfiles';
684 }
685
686 protected function getNavClass() {
687 return parent::getNavClass() . ' listfiles_nav';
688 }
689
690 protected function getSortHeaderClass() {
691 return parent::getSortHeaderClass() . ' listfiles_sort';
692 }
693
694 public function getPagingQueries() {
695 $queries = parent::getPagingQueries();
696 if ( $this->mUserName !== null ) {
697 # Append the username to the query string
698 foreach ( $queries as &$query ) {
699 if ( $query !== false ) {
700 $query['user'] = $this->mUserName;
701 }
702 }
703 }
704
705 return $queries;
706 }
707
708 public function getDefaultQuery() {
709 $queries = parent::getDefaultQuery();
710 if ( !isset( $queries['user'] ) && $this->mUserName !== null ) {
711 $queries['user'] = $this->mUserName;
712 }
713
714 return $queries;
715 }
716
717 public function getTitle() {
718 return SpecialPage::getTitleFor( 'Listfiles' );
719 }
720}
721
726class_alias( ImageListPager::class, 'ImageListPager' );
const NS_USER
Definition Defines.php:67
const NS_FILE
Definition Defines.php:71
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:304
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
Handle database storage of comments such as edit summaries and log reasons.
setContext(IContextSource $context)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:57
Prioritized list of file repositories.
Definition RepoGroup.php:38
getLocalRepo()
Get the local repository, i.e.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:210
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:62
A class containing constants representing the names of configuration variables.
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
const FileSchemaMigrationStage
Name constant for the FileSchemaMigrationStage setting, for use with Config::get()
reallyDoQuery( $offset, $limit, $order)
Do a query with specified parameters, rather than using the object context.
__construct(IContextSource $context, CommentStore $commentStore, LinkRenderer $linkRenderer, IConnectionProvider $dbProvider, RepoGroup $repoGroup, UserNameUtils $userNameUtils, RowCommentFormatter $rowCommentFormatter, LinkBatchFactory $linkBatchFactory, ?string $userName, string $search, ?bool $including, bool $showAll)
array string[] null $mFieldNames
getDefaultSort()
The database field name used as a default sort order.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
getIndexField()
Returns the name of the index field.If the pager supports multiple orders, it may return an array of ...
combineResult( $res1, $res2, $limit, $order)
Combine results from 2 tables.
outputUserDoesNotExist( $userName)
Add a message to the output stating that the user doesn't exist.
buildQueryCondsOld( $table)
Build the where clause of the query.
getQueryInfoReal( $table)
Actually get the query info.
reallyDoQueryOld( $offset, $limit, $order)
Override reallyDoQuery to mix together two queries.
isFieldSortable( $field)
Return true if the named field should be sortable by the UI, false otherwise.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
User $mUser
The relevant user.
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
getQueryInfo()
Provides all parameters needed for the main paged query.
getTableClass()
TablePager relies on mw-datatable for styling, see T214208.
getPagingQueries()
Get a URL query array for the prev, next, first and last links.
getRelevantUser()
Get the user relevant to the ImageList.
getDatabase()
Get the Database object in use.
const QUERY_ASCENDING
Backwards-compatible constant for reallyDoQuery() (do not change)
const DIR_ASCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.
string string[] $mIndexField
The index to actually be used for ordering.
Table-based display with a user-selectable sort order.
Parent class for all special pages.
Represents a title within MediaWiki.
Definition Title.php:78
Value object representing a user's identity.
UserNameUtils service.
isIP(string $name)
Does the string match an anonymous IP address?
User class for the MediaWiki software.
Definition User.php:120
Module of static functions for generating XML.
Definition Xml.php:37
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Interface for objects which can provide a MediaWiki context on request.
Provide primary and replica IDatabase connections.
getReplicaDatabase( $domain=false, $group=null)
Get connection to a replica database.
Result wrapper for grabbing data queried from an IDatabase object.