MediaWiki REL1_39
ImageListPager.php
Go to the documentation of this file.
1<?php
28
33
34 protected $mFieldNames = null;
35
36 // Subclasses should override buildQueryConds instead of using $mQueryConds variable.
37 protected $mQueryConds = [];
38
39 protected $mUserName = null;
40
46 protected $mUser = null;
47
48 protected $mIncluding = false;
49
50 protected $mShowAll = false;
51
52 protected $mTableName = 'image';
53
55 private $commentStore;
56
58 private $localRepo;
59
61 private $userCache;
62
66 private const INDEX_FIELDS = [
67 'img_timestamp' => [ 'img_timestamp', 'img_name' ],
68 'img_name' => [ 'img_name' ],
69 'img_size' => [ 'img_size', 'img_name' ],
70 ];
71
85 public function __construct(
86 IContextSource $context,
87 CommentStore $commentStore,
88 LinkRenderer $linkRenderer,
89 ILoadBalancer $loadBalancer,
90 RepoGroup $repoGroup,
91 UserCache $userCache,
92 UserNameUtils $userNameUtils,
93 $userName,
94 $search,
95 $including,
96 $showAll
97 ) {
98 $this->setContext( $context );
99
100 $this->mIncluding = $including;
101 $this->mShowAll = $showAll;
102 $dbr = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
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 if ( $this->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
122 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
123 } else {
124 $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
125 }
126 } else {
127 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
128 }
129 // Set database before parent constructor to avoid setting it there with wfGetDB
130 $this->mDb = $dbr;
131
132 parent::__construct( $context, $linkRenderer );
133 $this->commentStore = $commentStore;
134 $this->localRepo = $repoGroup->getLocalRepo();
135 $this->userCache = $userCache;
136 }
137
143 public function getRelevantUser() {
144 return $this->mUser;
145 }
146
152 protected function outputUserDoesNotExist( $userName ) {
153 $this->getOutput()->addHtml( Html::warningBox(
154 $this->getOutput()->msg( 'listfiles-userdoesnotexist', wfEscapeWikiText( $userName ) )->parse(),
155 'mw-userpage-userdoesnotexist'
156 ) );
157 }
158
166 protected function buildQueryConds( $table ) {
167 $conds = [];
168
169 if ( $this->mUserName !== null ) {
170 // getQueryInfoReal() should have handled the tables and joins.
171 $conds['actor_name'] = $this->mUserName;
172 }
173
174 if ( $table === 'oldimage' ) {
175 // Don't want to deal with revdel.
176 // Future fixme: Show partial information as appropriate.
177 // Would have to be careful about filtering by username when username is deleted.
178 $conds['oi_deleted'] = 0;
179 }
180
181 // Add mQueryConds in case anyone was subclassing and using the old variable.
182 return $conds + $this->mQueryConds;
183 }
184
185 protected function getFieldNames() {
186 if ( !$this->mFieldNames ) {
187 $this->mFieldNames = [
188 'img_timestamp' => $this->msg( 'listfiles_date' )->text(),
189 'img_name' => $this->msg( 'listfiles_name' )->text(),
190 'thumb' => $this->msg( 'listfiles_thumb' )->text(),
191 'img_size' => $this->msg( 'listfiles_size' )->text(),
192 ];
193 if ( $this->mUserName === null ) {
194 // Do not show username if filtering by username
195 $this->mFieldNames['img_actor'] = $this->msg( 'listfiles_user' )->text();
196 }
197 // img_description down here, in order so that its still after the username field.
198 $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text();
199
200 if ( !$this->getConfig()->get( MainConfigNames::MiserMode ) && !$this->mShowAll ) {
201 $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
202 }
203 if ( $this->mShowAll ) {
204 $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text();
205 }
206 }
207
208 return $this->mFieldNames;
209 }
210
211 protected function isFieldSortable( $field ) {
212 if ( $this->mIncluding ) {
213 return false;
214 }
215 $sortable = array_keys( self::INDEX_FIELDS );
216 /* For reference, the indices we can use for sorting are:
217 * On the image table: img_actor_timestamp, img_size, img_timestamp
218 * On oldimage: oi_actor_timestamp, oi_name_timestamp
219 *
220 * In particular that means we cannot sort by timestamp when not filtering
221 * by user and including old images in the results. Which is sad. (T279982)
222 */
223 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) && $this->mUserName !== null ) {
224 // If we're sorting by user, the index only supports sorting by time.
225 return $field === 'img_timestamp';
226 } elseif ( $this->getConfig()->get( MainConfigNames::MiserMode )
227 && $this->mShowAll /* && mUserName === null */
228 ) {
229 // no oi_timestamp index, so only alphabetical sorting in this case.
230 return $field === 'img_name';
231 }
232
233 return in_array( $field, $sortable );
234 }
235
236 public function getQueryInfo() {
237 // Hacky Hacky Hacky - I want to get query info
238 // for two different tables, without reimplementing
239 // the pager class.
240 $qi = $this->getQueryInfoReal( $this->mTableName );
241
242 return $qi;
243 }
244
255 protected function getQueryInfoReal( $table ) {
256 $dbr = $this->getDatabase();
257 $prefix = $table === 'oldimage' ? 'oi' : 'img';
258
259 $tables = [ $table, 'actor' ];
260 $join_conds = [];
261
262 if ( $table === 'oldimage' ) {
263 $fields = [
264 'img_timestamp' => 'oi_timestamp',
265 'img_name' => 'oi_name',
266 'img_size' => 'oi_size',
267 'top' => $dbr->addQuotes( 'no' )
268 ];
269 $join_conds['actor'] = [ 'JOIN', 'actor_id=oi_actor' ];
270 } else {
271 $fields = [
272 'img_timestamp',
273 'img_name',
274 'img_size',
275 'top' => $dbr->addQuotes( 'yes' )
276 ];
277 $join_conds['actor'] = [ 'JOIN', 'actor_id=img_actor' ];
278 }
279
280 $options = [];
281
282 # Description field
283 $commentQuery = $this->commentStore->getJoin( $prefix . '_description' );
284 $tables += $commentQuery['tables'];
285 $fields += $commentQuery['fields'];
286 $join_conds += $commentQuery['joins'];
287 $fields['description_field'] = $dbr->addQuotes( "{$prefix}_description" );
288
289 # Actor fields
290 $fields[] = 'actor_user';
291 $fields[] = 'actor_name';
292
293 # Depends on $wgMiserMode
294 # Will also not happen if mShowAll is true.
295 if ( array_key_exists( 'count', $this->getFieldNames() ) ) {
296 $fields['count'] = $dbr->buildSelectSubquery(
297 'oldimage',
298 'COUNT(oi_archive_name)',
299 'oi_name = img_name',
300 __METHOD__
301 );
302 }
303
304 return [
305 'tables' => $tables,
306 'fields' => $fields,
307 'conds' => $this->buildQueryConds( $table ),
308 'options' => $options,
309 'join_conds' => $join_conds
310 ];
311 }
312
322 public function reallyDoQuery( $offset, $limit, $order ) {
323 $dbr = $this->getDatabase();
324 $prevTableName = $this->mTableName;
325 $this->mTableName = 'image';
326 list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
327 $this->buildQueryInfo( $offset, $limit, $order );
328 $imageRes = $dbr->select( $tables, $fields, $conds, $fname, $options, $join_conds );
329 $this->mTableName = $prevTableName;
330
331 if ( !$this->mShowAll ) {
332 return $imageRes;
333 }
334
335 $this->mTableName = 'oldimage';
336
337 # Hacky...
338 $oldIndex = $this->mIndexField;
339 foreach ( $this->mIndexField as &$index ) {
340 if ( substr( $index, 0, 4 ) !== 'img_' ) {
341 throw new MWException( "Expected to be sorting on an image table field" );
342 }
343 $index = 'oi_' . substr( $index, 4 );
344 }
345
346 list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
347 $this->buildQueryInfo( $offset, $limit, $order );
348 $oldimageRes = $dbr->select( $tables, $fields, $conds, $fname, $options, $join_conds );
349
350 $this->mTableName = $prevTableName;
351 $this->mIndexField = $oldIndex;
352
353 return $this->combineResult( $imageRes, $oldimageRes, $limit, $order );
354 }
355
367 protected function combineResult( $res1, $res2, $limit, $order ) {
368 $res1->rewind();
369 $res2->rewind();
370 $topRes1 = $res1->fetchObject();
371 $topRes2 = $res2->fetchObject();
372 $resultArray = [];
373 for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
374 if ( strcmp( $topRes1->{$this->mIndexField[0]}, $topRes2->{$this->mIndexField[0]} ) > 0 ) {
375 if ( $order !== IndexPager::QUERY_ASCENDING ) {
376 $resultArray[] = $topRes1;
377 $topRes1 = $res1->fetchObject();
378 } else {
379 $resultArray[] = $topRes2;
380 $topRes2 = $res2->fetchObject();
381 }
382 } elseif ( $order !== IndexPager::QUERY_ASCENDING ) {
383 $resultArray[] = $topRes2;
384 $topRes2 = $res2->fetchObject();
385 } else {
386 $resultArray[] = $topRes1;
387 $topRes1 = $res1->fetchObject();
388 }
389 }
390
391 for ( ; $i < $limit && $topRes1; $i++ ) {
392 $resultArray[] = $topRes1;
393 $topRes1 = $res1->fetchObject();
394 }
395
396 for ( ; $i < $limit && $topRes2; $i++ ) {
397 $resultArray[] = $topRes2;
398 $topRes2 = $res2->fetchObject();
399 }
400
401 return new FakeResultWrapper( $resultArray );
402 }
403
404 public function getIndexField() {
405 return [ self::INDEX_FIELDS[$this->mSort] ];
406 }
407
408 public function getDefaultSort() {
409 if ( $this->mShowAll && $this->getConfig()->get( MainConfigNames::MiserMode ) &&
410 $this->mUserName === null ) {
411 // Unfortunately no index on oi_timestamp.
412 return 'img_name';
413 } else {
414 return 'img_timestamp';
415 }
416 }
417
418 protected function doBatchLookups() {
419 $userIds = [];
420 $this->mResult->seek( 0 );
421 foreach ( $this->mResult as $row ) {
422 if ( $row->actor_user ) {
423 $userIds[] = $row->actor_user;
424 }
425 }
426 # Do a link batch query for names and userpages
427 $this->userCache->doQuery( $userIds, [ 'userpage' ], __METHOD__ );
428 }
429
436 public function formatValue( $field, $value ) {
437 $linkRenderer = $this->getLinkRenderer();
438 switch ( $field ) {
439 case 'thumb':
440 $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
441 $file = $this->localRepo->findFile( $this->getCurrentRow()->img_name, $opt );
442 // If statement for paranoia
443 if ( $file ) {
444 $thumb = $file->transform( [ 'width' => 180, 'height' => 360 ] );
445 if ( $thumb ) {
446 return $thumb->toHtml( [ 'desc-link' => true ] );
447 } else {
448 return $this->msg( 'thumbnail_error', '' )->escaped();
449 }
450 } else {
451 return htmlspecialchars( $this->getCurrentRow()->img_name );
452 }
453 case 'img_timestamp':
454 // We may want to make this a link to the "old" version when displaying old files
455 return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) );
456 case 'img_name':
457 static $imgfile = null;
458 if ( $imgfile === null ) {
459 $imgfile = $this->msg( 'imgfile' )->text();
460 }
461
462 // Weird files can maybe exist? T24227
463 $filePage = Title::makeTitleSafe( NS_FILE, $value );
464 if ( $filePage ) {
465 $link = $linkRenderer->makeKnownLink(
466 $filePage,
467 $filePage->getText()
468 );
469 $download = Xml::element(
470 'a',
471 [ 'href' => $this->localRepo->newFile( $filePage )->getUrl() ],
472 $imgfile
473 );
474 $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
475
476 // Add delete links if allowed
477 // From https://github.com/Wikia/app/pull/3859
478 if ( $this->getAuthority()->probablyCan( 'delete', $filePage ) ) {
479 $deleteMsg = $this->msg( 'listfiles-delete' )->text();
480
481 $delete = $linkRenderer->makeKnownLink(
482 $filePage, $deleteMsg, [], [ 'action' => 'delete' ]
483 );
484 $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
485
486 return "$link $download $delete";
487 }
488
489 return "$link $download";
490 } else {
491 return htmlspecialchars( $value );
492 }
493 case 'img_actor':
494 if ( $this->mCurrentRow->actor_user ) {
495 $name = $this->mCurrentRow->actor_name;
496 $link = $linkRenderer->makeLink(
497 Title::makeTitle( NS_USER, $name ),
498 $name
499 );
500 } else {
501 $link = $value !== null ? htmlspecialchars( $value ) : '';
502 }
503
504 return $link;
505 case 'img_size':
506 return htmlspecialchars( $this->getLanguage()->formatSize( (int)$value ) );
507 case 'img_description':
508 $field = $this->mCurrentRow->description_field;
509 $value = $this->commentStore->getComment( $field, $this->mCurrentRow )->text;
510 return Linker::formatComment( $value );
511 case 'count':
512 return htmlspecialchars( $this->getLanguage()->formatNum( intval( $value ) + 1 ) );
513 case 'top':
514 // Messages: listfiles-latestversion-yes, listfiles-latestversion-no
515 return $this->msg( 'listfiles-latestversion-' . $value )->escaped();
516 default:
517 throw new MWException( "Unknown field '$field'" );
518 }
519 }
520
521 public function getForm() {
522 $formDescriptor = [];
523 $formDescriptor['limit'] = [
524 'type' => 'select',
525 'name' => 'limit',
526 'label-message' => 'table_pager_limit_label',
527 'options' => $this->getLimitSelectList(),
528 'default' => $this->mLimit,
529 ];
530
531 $formDescriptor['user'] = [
532 'type' => 'user',
533 'name' => 'user',
534 'id' => 'mw-listfiles-user',
535 'label-message' => 'username',
536 'default' => $this->mUserName,
537 'size' => '40',
538 'maxlength' => '255',
539 ];
540
541 $formDescriptor['ilshowall'] = [
542 'type' => 'check',
543 'name' => 'ilshowall',
544 'id' => 'mw-listfiles-show-all',
545 'label-message' => 'listfiles-show-all',
546 'default' => $this->mShowAll,
547 ];
548
549 $query = $this->getRequest()->getQueryValues();
550 unset( $query['title'] );
551 unset( $query['limit'] );
552 unset( $query['ilsearch'] );
553 unset( $query['ilshowall'] );
554 unset( $query['user'] );
555
556 HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
557 ->setMethod( 'get' )
558 ->setId( 'mw-listfiles-form' )
559 ->setTitle( $this->getTitle() )
560 ->setSubmitTextMsg( 'table_pager_limit_submit' )
561 ->setWrapperLegendMsg( 'listfiles' )
562 ->addHiddenFields( $query )
563 ->prepareForm()
564 ->displayForm( '' );
565 }
566
567 protected function getTableClass() {
568 return parent::getTableClass() . ' listfiles';
569 }
570
571 protected function getNavClass() {
572 return parent::getNavClass() . ' listfiles_nav';
573 }
574
575 protected function getSortHeaderClass() {
576 return parent::getSortHeaderClass() . ' listfiles_sort';
577 }
578
579 public function getPagingQueries() {
580 $queries = parent::getPagingQueries();
581 if ( $this->mUserName !== null ) {
582 # Append the username to the query string
583 foreach ( $queries as &$query ) {
584 if ( $query !== false ) {
585 $query['user'] = $this->mUserName;
586 }
587 }
588 }
589
590 return $queries;
591 }
592
593 public function getDefaultQuery() {
594 $queries = parent::getDefaultQuery();
595 if ( !isset( $queries['user'] ) && $this->mUserName !== null ) {
596 $queries['user'] = $this->mUserName;
597 }
598
599 return $queries;
600 }
601
602 public function getTitle() {
603 return SpecialPage::getTitleFor( 'Listfiles' );
604 }
605}
getUser()
getAuthority()
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
getContext()
Handle database storage of comments such as edit summaries and log reasons.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
setContext(IContextSource $context)
getDefaultSort()
The database field name used as a default sort order.
getTableClass()
TablePager relies on mw-datatable for styling, see T214208.
formatValue( $field, $value)
outputUserDoesNotExist( $userName)
Add a message to the output stating that the user doesn't exist.
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
combineResult( $res1, $res2, $limit, $order)
Combine results from 2 tables.
buildQueryConds( $table)
Build the where clause of the query.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
__construct(IContextSource $context, CommentStore $commentStore, LinkRenderer $linkRenderer, ILoadBalancer $loadBalancer, RepoGroup $repoGroup, UserCache $userCache, UserNameUtils $userNameUtils, $userName, $search, $including, $showAll)
User null $mUser
The relevant user.
getIndexField()
Returns the name of the index field.If the pager supports multiple orders, it may return an array of ...
getRelevantUser()
Get the user relevant to the ImageList.
getQueryInfo()
Provides all parameters needed for the main paged query.
reallyDoQuery( $offset, $limit, $order)
Override reallyDoQuery to mix together two queries.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
getPagingQueries()
Get a URL query array for the prev, next, first and last links.
getQueryInfoReal( $table)
Actually get the query info.
isFieldSortable( $field)
Return true if the named field should be sortable by the UI, false otherwise.
const DIR_ASCENDING
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.
getDatabase()
Get the Database object in use.
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
const QUERY_ASCENDING
Backwards-compatible constant for reallyDoQuery() (do not change)
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition Linker.php:1449
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:39
MediaWiki exception.
Class that generates HTML anchor link elements for pages.
A class containing constants representing the names of configuration variables.
UserNameUtils service.
isIP(string $name)
Does the string match an anonymous IP address?
Prioritized list of file repositories.
Definition RepoGroup.php:29
getLocalRepo()
Get the local repository, i.e.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Table-based display with a user-selectable sort order.
internal since 1.36
Definition User.php:70
static newFromName( $name, $validate='valid')
Definition User.php:598
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.
Create and track the database connections and transactions for a given database cluster.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Result wrapper for grabbing data queried from an IDatabase object.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42