MediaWiki
REL1_24
|
00001 <?php 00024 class SpecialListFiles extends IncludableSpecialPage { 00025 public function __construct() { 00026 parent::__construct( 'Listfiles' ); 00027 } 00028 00029 public function execute( $par ) { 00030 $this->setHeaders(); 00031 $this->outputHeader(); 00032 00033 if ( $this->including() ) { 00034 $userName = $par; 00035 $search = ''; 00036 $showAll = false; 00037 } else { 00038 $userName = $this->getRequest()->getText( 'user', $par ); 00039 $search = $this->getRequest()->getText( 'ilsearch', '' ); 00040 $showAll = $this->getRequest()->getBool( 'ilshowall', false ); 00041 } 00042 00043 $pager = new ImageListPager( 00044 $this->getContext(), 00045 $userName, 00046 $search, 00047 $this->including(), 00048 $showAll 00049 ); 00050 00051 $out = $this->getOutput(); 00052 if ( $this->including() ) { 00053 $out->addParserOutputContent( $pager->getBodyOutput() ); 00054 } else { 00055 $out->addHTML( $pager->getForm() ); 00056 $out->addParserOutputContent( $pager->getFullOutput() ); 00057 } 00058 } 00059 00060 protected function getGroupName() { 00061 return 'media'; 00062 } 00063 } 00064 00068 class ImageListPager extends TablePager { 00069 protected $mFieldNames = null; 00070 00071 // Subclasses should override buildQueryConds instead of using $mQueryConds variable. 00072 protected $mQueryConds = array(); 00073 00074 protected $mUserName = null; 00075 00076 protected $mSearch = ''; 00077 00078 protected $mIncluding = false; 00079 00080 protected $mShowAll = false; 00081 00082 protected $mTableName = 'image'; 00083 00084 function __construct( IContextSource $context, $userName = null, $search = '', 00085 $including = false, $showAll = false 00086 ) { 00087 $this->mIncluding = $including; 00088 $this->mShowAll = $showAll; 00089 00090 if ( $userName ) { 00091 $nt = Title::newFromText( $userName, NS_USER ); 00092 if ( !is_null( $nt ) ) { 00093 $this->mUserName = $nt->getText(); 00094 } 00095 } 00096 00097 if ( $search !== '' && !$this->getConfig()->get( 'MiserMode' ) ) { 00098 $this->mSearch = $search; 00099 $nt = Title::newFromURL( $this->mSearch ); 00100 00101 if ( $nt ) { 00102 $dbr = wfGetDB( DB_SLAVE ); 00103 $this->mQueryConds[] = 'LOWER(img_name)' . 00104 $dbr->buildLike( $dbr->anyString(), 00105 strtolower( $nt->getDBkey() ), $dbr->anyString() ); 00106 } 00107 } 00108 00109 if ( !$including ) { 00110 if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) { 00111 $this->mDefaultDirection = IndexPager::DIR_DESCENDING; 00112 } else { 00113 $this->mDefaultDirection = IndexPager::DIR_ASCENDING; 00114 } 00115 } else { 00116 $this->mDefaultDirection = IndexPager::DIR_DESCENDING; 00117 } 00118 00119 parent::__construct( $context ); 00120 } 00121 00129 protected function buildQueryConds( $table ) { 00130 $prefix = $table === 'image' ? 'img' : 'oi'; 00131 $conds = array(); 00132 00133 if ( !is_null( $this->mUserName ) ) { 00134 $conds[$prefix . '_user_text'] = $this->mUserName; 00135 } 00136 00137 if ( $this->mSearch !== '' ) { 00138 $nt = Title::newFromURL( $this->mSearch ); 00139 if ( $nt ) { 00140 $dbr = wfGetDB( DB_SLAVE ); 00141 $conds[] = 'LOWER(' . $prefix . '_name)' . 00142 $dbr->buildLike( $dbr->anyString(), 00143 strtolower( $nt->getDBkey() ), $dbr->anyString() ); 00144 } 00145 } 00146 00147 if ( $table === 'oldimage' ) { 00148 // Don't want to deal with revdel. 00149 // Future fixme: Show partial information as appropriate. 00150 // Would have to be careful about filtering by username when username is deleted. 00151 $conds['oi_deleted'] = 0; 00152 } 00153 00154 // Add mQueryConds in case anyone was subclassing and using the old variable. 00155 return $conds + $this->mQueryConds; 00156 } 00157 00161 function getFieldNames() { 00162 if ( !$this->mFieldNames ) { 00163 $this->mFieldNames = array( 00164 'img_timestamp' => $this->msg( 'listfiles_date' )->text(), 00165 'img_name' => $this->msg( 'listfiles_name' )->text(), 00166 'thumb' => $this->msg( 'listfiles_thumb' )->text(), 00167 'img_size' => $this->msg( 'listfiles_size' )->text(), 00168 ); 00169 if ( is_null( $this->mUserName ) ) { 00170 // Do not show username if filtering by username 00171 $this->mFieldNames['img_user_text'] = $this->msg( 'listfiles_user' )->text(); 00172 } 00173 // img_description down here, in order so that its still after the username field. 00174 $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text(); 00175 00176 if ( !$this->getConfig()->get( 'MiserMode' ) && !$this->mShowAll ) { 00177 $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text(); 00178 } 00179 if ( $this->mShowAll ) { 00180 $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text(); 00181 } 00182 } 00183 00184 return $this->mFieldNames; 00185 } 00186 00187 function isFieldSortable( $field ) { 00188 if ( $this->mIncluding ) { 00189 return false; 00190 } 00191 $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); 00192 /* For reference, the indicies we can use for sorting are: 00193 * On the image table: img_usertext_timestamp, img_size, img_timestamp 00194 * On oldimage: oi_usertext_timestamp, oi_name_timestamp 00195 * 00196 * In particular that means we cannot sort by timestamp when not filtering 00197 * by user and including old images in the results. Which is sad. 00198 */ 00199 if ( $this->getConfig()->get( 'MiserMode' ) && !is_null( $this->mUserName ) ) { 00200 // If we're sorting by user, the index only supports sorting by time. 00201 if ( $field === 'img_timestamp' ) { 00202 return true; 00203 } else { 00204 return false; 00205 } 00206 } elseif ( $this->getConfig()->get( 'MiserMode' ) && $this->mShowAll /* && mUserName === null */ ) { 00207 // no oi_timestamp index, so only alphabetical sorting in this case. 00208 if ( $field === 'img_name' ) { 00209 return true; 00210 } else { 00211 return false; 00212 } 00213 } 00214 00215 return in_array( $field, $sortable ); 00216 } 00217 00218 function getQueryInfo() { 00219 // Hacky Hacky Hacky - I want to get query info 00220 // for two different tables, without reimplementing 00221 // the pager class. 00222 $qi = $this->getQueryInfoReal( $this->mTableName ); 00223 00224 return $qi; 00225 } 00226 00237 protected function getQueryInfoReal( $table ) { 00238 $prefix = $table === 'oldimage' ? 'oi' : 'img'; 00239 00240 $tables = array( $table ); 00241 $fields = array_keys( $this->getFieldNames() ); 00242 00243 if ( $table === 'oldimage' ) { 00244 foreach ( $fields as $id => &$field ) { 00245 if ( substr( $field, 0, 4 ) !== 'img_' ) { 00246 continue; 00247 } 00248 $field = $prefix . substr( $field, 3 ) . ' AS ' . $field; 00249 } 00250 $fields[array_search( 'top', $fields )] = "'no' AS top"; 00251 } else { 00252 if ( $this->mShowAll ) { 00253 $fields[array_search( 'top', $fields )] = "'yes' AS top"; 00254 } 00255 } 00256 $fields[] = $prefix . '_user AS img_user'; 00257 $fields[array_search( 'thumb', $fields )] = $prefix . '_name AS thumb'; 00258 00259 $options = $join_conds = array(); 00260 00261 # Depends on $wgMiserMode 00262 # Will also not happen if mShowAll is true. 00263 if ( isset( $this->mFieldNames['count'] ) ) { 00264 $tables[] = 'oldimage'; 00265 00266 # Need to rewrite this one 00267 foreach ( $fields as &$field ) { 00268 if ( $field == 'count' ) { 00269 $field = 'COUNT(oi_archive_name) AS count'; 00270 } 00271 } 00272 unset( $field ); 00273 00274 $dbr = wfGetDB( DB_SLAVE ); 00275 if ( $dbr->implicitGroupby() ) { 00276 $options = array( 'GROUP BY' => 'img_name' ); 00277 } else { 00278 $columnlist = preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ); 00279 $options = array( 'GROUP BY' => array_merge( array( 'img_user' ), $columnlist ) ); 00280 } 00281 $join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) ); 00282 } 00283 00284 return array( 00285 'tables' => $tables, 00286 'fields' => $fields, 00287 'conds' => $this->buildQueryConds( $table ), 00288 'options' => $options, 00289 'join_conds' => $join_conds 00290 ); 00291 } 00292 00304 function reallyDoQuery( $offset, $limit, $asc ) { 00305 $prevTableName = $this->mTableName; 00306 $this->mTableName = 'image'; 00307 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = 00308 $this->buildQueryInfo( $offset, $limit, $asc ); 00309 $imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00310 $this->mTableName = $prevTableName; 00311 00312 if ( !$this->mShowAll ) { 00313 return $imageRes; 00314 } 00315 00316 $this->mTableName = 'oldimage'; 00317 00318 # Hacky... 00319 $oldIndex = $this->mIndexField; 00320 if ( substr( $this->mIndexField, 0, 4 ) !== 'img_' ) { 00321 throw new MWException( "Expected to be sorting on an image table field" ); 00322 } 00323 $this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 ); 00324 00325 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = 00326 $this->buildQueryInfo( $offset, $limit, $asc ); 00327 $oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00328 00329 $this->mTableName = $prevTableName; 00330 $this->mIndexField = $oldIndex; 00331 00332 return $this->combineResult( $imageRes, $oldimageRes, $limit, $asc ); 00333 } 00334 00346 protected function combineResult( $res1, $res2, $limit, $ascending ) { 00347 $res1->rewind(); 00348 $res2->rewind(); 00349 $topRes1 = $res1->next(); 00350 $topRes2 = $res2->next(); 00351 $resultArray = array(); 00352 for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) { 00353 if ( strcmp( $topRes1->{$this->mIndexField}, $topRes2->{$this->mIndexField} ) > 0 ) { 00354 if ( !$ascending ) { 00355 $resultArray[] = $topRes1; 00356 $topRes1 = $res1->next(); 00357 } else { 00358 $resultArray[] = $topRes2; 00359 $topRes2 = $res2->next(); 00360 } 00361 } else { 00362 if ( !$ascending ) { 00363 $resultArray[] = $topRes2; 00364 $topRes2 = $res2->next(); 00365 } else { 00366 $resultArray[] = $topRes1; 00367 $topRes1 = $res1->next(); 00368 } 00369 } 00370 } 00371 00372 // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect 00373 for ( ; $i < $limit && $topRes1; $i++ ) { 00374 // @codingStandardsIgnoreEnd 00375 $resultArray[] = $topRes1; 00376 $topRes1 = $res1->next(); 00377 } 00378 00379 // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect 00380 for ( ; $i < $limit && $topRes2; $i++ ) { 00381 // @codingStandardsIgnoreEnd 00382 $resultArray[] = $topRes2; 00383 $topRes2 = $res2->next(); 00384 } 00385 00386 return new FakeResultWrapper( $resultArray ); 00387 } 00388 00389 function getDefaultSort() { 00390 if ( $this->mShowAll && $this->getConfig()->get( 'MiserMode' ) && is_null( $this->mUserName ) ) { 00391 // Unfortunately no index on oi_timestamp. 00392 return 'img_name'; 00393 } else { 00394 return 'img_timestamp'; 00395 } 00396 } 00397 00398 function doBatchLookups() { 00399 $userIds = array(); 00400 $this->mResult->seek( 0 ); 00401 foreach ( $this->mResult as $row ) { 00402 $userIds[] = $row->img_user; 00403 } 00404 # Do a link batch query for names and userpages 00405 UserCache::singleton()->doQuery( $userIds, array( 'userpage' ), __METHOD__ ); 00406 } 00407 00422 function formatValue( $field, $value ) { 00423 switch ( $field ) { 00424 case 'thumb': 00425 $opt = array( 'time' => $this->mCurrentRow->img_timestamp ); 00426 $file = RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt ); 00427 // If statement for paranoia 00428 if ( $file ) { 00429 $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) ); 00430 00431 return $thumb->toHtml( array( 'desc-link' => true ) ); 00432 } else { 00433 return htmlspecialchars( $value ); 00434 } 00435 case 'img_timestamp': 00436 // We may want to make this a link to the "old" version when displaying old files 00437 return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) ); 00438 case 'img_name': 00439 static $imgfile = null; 00440 if ( $imgfile === null ) { 00441 $imgfile = $this->msg( 'imgfile' )->text(); 00442 } 00443 00444 // Weird files can maybe exist? Bug 22227 00445 $filePage = Title::makeTitleSafe( NS_FILE, $value ); 00446 if ( $filePage ) { 00447 $link = Linker::linkKnown( 00448 $filePage, 00449 htmlspecialchars( $filePage->getText() ) 00450 ); 00451 $download = Xml::element( 'a', 00452 array( 'href' => wfLocalFile( $filePage )->getURL() ), 00453 $imgfile 00454 ); 00455 $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped(); 00456 00457 // Add delete links if allowed 00458 // From https://github.com/Wikia/app/pull/3859 00459 if ( $filePage->userCan( 'delete', $this->getUser() ) ) { 00460 $deleteMsg = $this->msg( 'listfiles-delete' )->escaped(); 00461 00462 $delete = Linker::linkKnown( 00463 $filePage, $deleteMsg, array(), array( 'action' => 'delete' ) 00464 ); 00465 $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped(); 00466 00467 return "$link $download $delete"; 00468 } 00469 00470 return "$link $download"; 00471 } else { 00472 return htmlspecialchars( $value ); 00473 } 00474 case 'img_user_text': 00475 if ( $this->mCurrentRow->img_user ) { 00476 $name = User::whoIs( $this->mCurrentRow->img_user ); 00477 $link = Linker::link( 00478 Title::makeTitle( NS_USER, $name ), 00479 htmlspecialchars( $name ) 00480 ); 00481 } else { 00482 $link = htmlspecialchars( $value ); 00483 } 00484 00485 return $link; 00486 case 'img_size': 00487 return htmlspecialchars( $this->getLanguage()->formatSize( $value ) ); 00488 case 'img_description': 00489 return Linker::formatComment( $value ); 00490 case 'count': 00491 return intval( $value ) + 1; 00492 case 'top': 00493 // Messages: listfiles-latestversion-yes, listfiles-latestversion-no 00494 return $this->msg( 'listfiles-latestversion-' . $value ); 00495 default: 00496 throw new MWException( "Unknown field '$field'" ); 00497 } 00498 } 00499 00500 function getForm() { 00501 $fields = array(); 00502 $fields['limit'] = array( 00503 'type' => 'select', 00504 'name' => 'limit', 00505 'label-message' => 'table_pager_limit_label', 00506 'options' => $this->getLimitSelectList(), 00507 'default' => $this->mLimit, 00508 ); 00509 00510 if ( !$this->getConfig()->get( 'MiserMode' ) ) { 00511 $fields['ilsearch'] = array( 00512 'type' => 'text', 00513 'name' => 'ilsearch', 00514 'id' => 'mw-ilsearch', 00515 'label-message' => 'listfiles_search_for', 00516 'default' => $this->mSearch, 00517 'size' => '40', 00518 'maxlength' => '255', 00519 ); 00520 } 00521 00522 $fields['user'] = array( 00523 'type' => 'text', 00524 'name' => 'user', 00525 'id' => 'mw-listfiles-user', 00526 'label-message' => 'username', 00527 'default' => $this->mUserName, 00528 'size' => '40', 00529 'maxlength' => '255', 00530 ); 00531 00532 $fields['ilshowall'] = array( 00533 'type' => 'check', 00534 'name' => 'ilshowall', 00535 'id' => 'mw-listfiles-show-all', 00536 'label-message' => 'listfiles-show-all', 00537 'default' => $this->mShowAll, 00538 ); 00539 00540 $query = $this->getRequest()->getQueryValues(); 00541 unset( $query['title'] ); 00542 unset( $query['limit'] ); 00543 unset( $query['ilsearch'] ); 00544 unset( $query['user'] ); 00545 00546 $form = new HTMLForm( $fields, $this->getContext() ); 00547 00548 $form->setMethod( 'get' ); 00549 $form->setTitle( $this->getTitle() ); 00550 $form->setId( 'mw-listfiles-form' ); 00551 $form->setWrapperLegendMsg( 'listfiles' ); 00552 $form->setSubmitTextMsg( 'table_pager_limit_submit' ); 00553 $form->addHiddenFields( $query ); 00554 00555 $form->prepareForm(); 00556 $form->displayForm( '' ); 00557 } 00558 00559 function getTableClass() { 00560 return parent::getTableClass() . ' listfiles'; 00561 } 00562 00563 function getNavClass() { 00564 return parent::getNavClass() . ' listfiles_nav'; 00565 } 00566 00567 function getSortHeaderClass() { 00568 return parent::getSortHeaderClass() . ' listfiles_sort'; 00569 } 00570 00571 function getPagingQueries() { 00572 $queries = parent::getPagingQueries(); 00573 if ( !is_null( $this->mUserName ) ) { 00574 # Append the username to the query string 00575 foreach ( $queries as &$query ) { 00576 if ( $query !== false ) { 00577 $query['user'] = $this->mUserName; 00578 } 00579 } 00580 } 00581 00582 return $queries; 00583 } 00584 00585 function getDefaultQuery() { 00586 $queries = parent::getDefaultQuery(); 00587 if ( !isset( $queries['user'] ) && !is_null( $this->mUserName ) ) { 00588 $queries['user'] = $this->mUserName; 00589 } 00590 00591 return $queries; 00592 } 00593 00594 function getTitle() { 00595 return SpecialPage::getTitleFor( 'Listfiles' ); 00596 } 00597 }