[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:Listfiles 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup SpecialPage 22 */ 23 24 class SpecialListFiles extends IncludableSpecialPage { 25 public function __construct() { 26 parent::__construct( 'Listfiles' ); 27 } 28 29 public function execute( $par ) { 30 $this->setHeaders(); 31 $this->outputHeader(); 32 33 if ( $this->including() ) { 34 $userName = $par; 35 $search = ''; 36 $showAll = false; 37 } else { 38 $userName = $this->getRequest()->getText( 'user', $par ); 39 $search = $this->getRequest()->getText( 'ilsearch', '' ); 40 $showAll = $this->getRequest()->getBool( 'ilshowall', false ); 41 } 42 43 $pager = new ImageListPager( 44 $this->getContext(), 45 $userName, 46 $search, 47 $this->including(), 48 $showAll 49 ); 50 51 $out = $this->getOutput(); 52 if ( $this->including() ) { 53 $out->addParserOutputContent( $pager->getBodyOutput() ); 54 } else { 55 $out->addHTML( $pager->getForm() ); 56 $out->addParserOutputContent( $pager->getFullOutput() ); 57 } 58 } 59 60 protected function getGroupName() { 61 return 'media'; 62 } 63 } 64 65 /** 66 * @ingroup SpecialPage Pager 67 */ 68 class ImageListPager extends TablePager { 69 protected $mFieldNames = null; 70 71 // Subclasses should override buildQueryConds instead of using $mQueryConds variable. 72 protected $mQueryConds = array(); 73 74 protected $mUserName = null; 75 76 protected $mSearch = ''; 77 78 protected $mIncluding = false; 79 80 protected $mShowAll = false; 81 82 protected $mTableName = 'image'; 83 84 function __construct( IContextSource $context, $userName = null, $search = '', 85 $including = false, $showAll = false 86 ) { 87 $this->mIncluding = $including; 88 $this->mShowAll = $showAll; 89 90 if ( $userName ) { 91 $nt = Title::newFromText( $userName, NS_USER ); 92 if ( !is_null( $nt ) ) { 93 $this->mUserName = $nt->getText(); 94 } 95 } 96 97 if ( $search !== '' && !$this->getConfig()->get( 'MiserMode' ) ) { 98 $this->mSearch = $search; 99 $nt = Title::newFromURL( $this->mSearch ); 100 101 if ( $nt ) { 102 $dbr = wfGetDB( DB_SLAVE ); 103 $this->mQueryConds[] = 'LOWER(img_name)' . 104 $dbr->buildLike( $dbr->anyString(), 105 strtolower( $nt->getDBkey() ), $dbr->anyString() ); 106 } 107 } 108 109 if ( !$including ) { 110 if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) { 111 $this->mDefaultDirection = IndexPager::DIR_DESCENDING; 112 } else { 113 $this->mDefaultDirection = IndexPager::DIR_ASCENDING; 114 } 115 } else { 116 $this->mDefaultDirection = IndexPager::DIR_DESCENDING; 117 } 118 119 parent::__construct( $context ); 120 } 121 122 /** 123 * Build the where clause of the query. 124 * 125 * Replaces the older mQueryConds member variable. 126 * @param string $table Either "image" or "oldimage" 127 * @return array The query conditions. 128 */ 129 protected function buildQueryConds( $table ) { 130 $prefix = $table === 'image' ? 'img' : 'oi'; 131 $conds = array(); 132 133 if ( !is_null( $this->mUserName ) ) { 134 $conds[$prefix . '_user_text'] = $this->mUserName; 135 } 136 137 if ( $this->mSearch !== '' ) { 138 $nt = Title::newFromURL( $this->mSearch ); 139 if ( $nt ) { 140 $dbr = wfGetDB( DB_SLAVE ); 141 $conds[] = 'LOWER(' . $prefix . '_name)' . 142 $dbr->buildLike( $dbr->anyString(), 143 strtolower( $nt->getDBkey() ), $dbr->anyString() ); 144 } 145 } 146 147 if ( $table === 'oldimage' ) { 148 // Don't want to deal with revdel. 149 // Future fixme: Show partial information as appropriate. 150 // Would have to be careful about filtering by username when username is deleted. 151 $conds['oi_deleted'] = 0; 152 } 153 154 // Add mQueryConds in case anyone was subclassing and using the old variable. 155 return $conds + $this->mQueryConds; 156 } 157 158 /** 159 * @return array 160 */ 161 function getFieldNames() { 162 if ( !$this->mFieldNames ) { 163 $this->mFieldNames = array( 164 'img_timestamp' => $this->msg( 'listfiles_date' )->text(), 165 'img_name' => $this->msg( 'listfiles_name' )->text(), 166 'thumb' => $this->msg( 'listfiles_thumb' )->text(), 167 'img_size' => $this->msg( 'listfiles_size' )->text(), 168 ); 169 if ( is_null( $this->mUserName ) ) { 170 // Do not show username if filtering by username 171 $this->mFieldNames['img_user_text'] = $this->msg( 'listfiles_user' )->text(); 172 } 173 // img_description down here, in order so that its still after the username field. 174 $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text(); 175 176 if ( !$this->getConfig()->get( 'MiserMode' ) && !$this->mShowAll ) { 177 $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text(); 178 } 179 if ( $this->mShowAll ) { 180 $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text(); 181 } 182 } 183 184 return $this->mFieldNames; 185 } 186 187 function isFieldSortable( $field ) { 188 if ( $this->mIncluding ) { 189 return false; 190 } 191 $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); 192 /* For reference, the indicies we can use for sorting are: 193 * On the image table: img_usertext_timestamp, img_size, img_timestamp 194 * On oldimage: oi_usertext_timestamp, oi_name_timestamp 195 * 196 * In particular that means we cannot sort by timestamp when not filtering 197 * by user and including old images in the results. Which is sad. 198 */ 199 if ( $this->getConfig()->get( 'MiserMode' ) && !is_null( $this->mUserName ) ) { 200 // If we're sorting by user, the index only supports sorting by time. 201 if ( $field === 'img_timestamp' ) { 202 return true; 203 } else { 204 return false; 205 } 206 } elseif ( $this->getConfig()->get( 'MiserMode' ) && $this->mShowAll /* && mUserName === null */ ) { 207 // no oi_timestamp index, so only alphabetical sorting in this case. 208 if ( $field === 'img_name' ) { 209 return true; 210 } else { 211 return false; 212 } 213 } 214 215 return in_array( $field, $sortable ); 216 } 217 218 function getQueryInfo() { 219 // Hacky Hacky Hacky - I want to get query info 220 // for two different tables, without reimplementing 221 // the pager class. 222 $qi = $this->getQueryInfoReal( $this->mTableName ); 223 224 return $qi; 225 } 226 227 /** 228 * Actually get the query info. 229 * 230 * This is to allow displaying both stuff from image and oldimage table. 231 * 232 * This is a bit hacky. 233 * 234 * @param string $table Either 'image' or 'oldimage' 235 * @return array Query info 236 */ 237 protected function getQueryInfoReal( $table ) { 238 $prefix = $table === 'oldimage' ? 'oi' : 'img'; 239 240 $tables = array( $table ); 241 $fields = array_keys( $this->getFieldNames() ); 242 243 if ( $table === 'oldimage' ) { 244 foreach ( $fields as $id => &$field ) { 245 if ( substr( $field, 0, 4 ) !== 'img_' ) { 246 continue; 247 } 248 $field = $prefix . substr( $field, 3 ) . ' AS ' . $field; 249 } 250 $fields[array_search( 'top', $fields )] = "'no' AS top"; 251 } else { 252 if ( $this->mShowAll ) { 253 $fields[array_search( 'top', $fields )] = "'yes' AS top"; 254 } 255 } 256 $fields[] = $prefix . '_user AS img_user'; 257 $fields[array_search( 'thumb', $fields )] = $prefix . '_name AS thumb'; 258 259 $options = $join_conds = array(); 260 261 # Depends on $wgMiserMode 262 # Will also not happen if mShowAll is true. 263 if ( isset( $this->mFieldNames['count'] ) ) { 264 $tables[] = 'oldimage'; 265 266 # Need to rewrite this one 267 foreach ( $fields as &$field ) { 268 if ( $field == 'count' ) { 269 $field = 'COUNT(oi_archive_name) AS count'; 270 } 271 } 272 unset( $field ); 273 274 $dbr = wfGetDB( DB_SLAVE ); 275 if ( $dbr->implicitGroupby() ) { 276 $options = array( 'GROUP BY' => 'img_name' ); 277 } else { 278 $columnlist = preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ); 279 $options = array( 'GROUP BY' => array_merge( array( 'img_user' ), $columnlist ) ); 280 } 281 $join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) ); 282 } 283 284 return array( 285 'tables' => $tables, 286 'fields' => $fields, 287 'conds' => $this->buildQueryConds( $table ), 288 'options' => $options, 289 'join_conds' => $join_conds 290 ); 291 } 292 293 /** 294 * Override reallyDoQuery to mix together two queries. 295 * 296 * @note $asc is named $descending in IndexPager base class. However 297 * it is true when the order is ascending, and false when the order 298 * is descending, so I renamed it to $asc here. 299 * @param int $offset 300 * @param int $limit 301 * @param bool $asc 302 * @return array 303 */ 304 function reallyDoQuery( $offset, $limit, $asc ) { 305 $prevTableName = $this->mTableName; 306 $this->mTableName = 'image'; 307 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = 308 $this->buildQueryInfo( $offset, $limit, $asc ); 309 $imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 310 $this->mTableName = $prevTableName; 311 312 if ( !$this->mShowAll ) { 313 return $imageRes; 314 } 315 316 $this->mTableName = 'oldimage'; 317 318 # Hacky... 319 $oldIndex = $this->mIndexField; 320 if ( substr( $this->mIndexField, 0, 4 ) !== 'img_' ) { 321 throw new MWException( "Expected to be sorting on an image table field" ); 322 } 323 $this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 ); 324 325 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = 326 $this->buildQueryInfo( $offset, $limit, $asc ); 327 $oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 328 329 $this->mTableName = $prevTableName; 330 $this->mIndexField = $oldIndex; 331 332 return $this->combineResult( $imageRes, $oldimageRes, $limit, $asc ); 333 } 334 335 /** 336 * Combine results from 2 tables. 337 * 338 * Note: This will throw away some results 339 * 340 * @param ResultWrapper $res1 341 * @param ResultWrapper $res2 342 * @param int $limit 343 * @param bool $ascending See note about $asc in $this->reallyDoQuery 344 * @return FakeResultWrapper $res1 and $res2 combined 345 */ 346 protected function combineResult( $res1, $res2, $limit, $ascending ) { 347 $res1->rewind(); 348 $res2->rewind(); 349 $topRes1 = $res1->next(); 350 $topRes2 = $res2->next(); 351 $resultArray = array(); 352 for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) { 353 if ( strcmp( $topRes1->{$this->mIndexField}, $topRes2->{$this->mIndexField} ) > 0 ) { 354 if ( !$ascending ) { 355 $resultArray[] = $topRes1; 356 $topRes1 = $res1->next(); 357 } else { 358 $resultArray[] = $topRes2; 359 $topRes2 = $res2->next(); 360 } 361 } else { 362 if ( !$ascending ) { 363 $resultArray[] = $topRes2; 364 $topRes2 = $res2->next(); 365 } else { 366 $resultArray[] = $topRes1; 367 $topRes1 = $res1->next(); 368 } 369 } 370 } 371 372 // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect 373 for ( ; $i < $limit && $topRes1; $i++ ) { 374 // @codingStandardsIgnoreEnd 375 $resultArray[] = $topRes1; 376 $topRes1 = $res1->next(); 377 } 378 379 // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect 380 for ( ; $i < $limit && $topRes2; $i++ ) { 381 // @codingStandardsIgnoreEnd 382 $resultArray[] = $topRes2; 383 $topRes2 = $res2->next(); 384 } 385 386 return new FakeResultWrapper( $resultArray ); 387 } 388 389 function getDefaultSort() { 390 if ( $this->mShowAll && $this->getConfig()->get( 'MiserMode' ) && is_null( $this->mUserName ) ) { 391 // Unfortunately no index on oi_timestamp. 392 return 'img_name'; 393 } else { 394 return 'img_timestamp'; 395 } 396 } 397 398 function doBatchLookups() { 399 $userIds = array(); 400 $this->mResult->seek( 0 ); 401 foreach ( $this->mResult as $row ) { 402 $userIds[] = $row->img_user; 403 } 404 # Do a link batch query for names and userpages 405 UserCache::singleton()->doQuery( $userIds, array( 'userpage' ), __METHOD__ ); 406 } 407 408 /** 409 * @param string $field 410 * @param string $value 411 * @return Message|string|int The return type depends on the value of $field: 412 * - thumb: string 413 * - img_timestamp: string 414 * - img_name: string 415 * - img_user_text: string 416 * - img_size: string 417 * - img_description: string 418 * - count: int 419 * - top: Message 420 * @throws MWException 421 */ 422 function formatValue( $field, $value ) { 423 switch ( $field ) { 424 case 'thumb': 425 $opt = array( 'time' => $this->mCurrentRow->img_timestamp ); 426 $file = RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt ); 427 // If statement for paranoia 428 if ( $file ) { 429 $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) ); 430 431 return $thumb->toHtml( array( 'desc-link' => true ) ); 432 } else { 433 return htmlspecialchars( $value ); 434 } 435 case 'img_timestamp': 436 // We may want to make this a link to the "old" version when displaying old files 437 return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) ); 438 case 'img_name': 439 static $imgfile = null; 440 if ( $imgfile === null ) { 441 $imgfile = $this->msg( 'imgfile' )->text(); 442 } 443 444 // Weird files can maybe exist? Bug 22227 445 $filePage = Title::makeTitleSafe( NS_FILE, $value ); 446 if ( $filePage ) { 447 $link = Linker::linkKnown( 448 $filePage, 449 htmlspecialchars( $filePage->getText() ) 450 ); 451 $download = Xml::element( 'a', 452 array( 'href' => wfLocalFile( $filePage )->getURL() ), 453 $imgfile 454 ); 455 $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped(); 456 457 // Add delete links if allowed 458 // From https://github.com/Wikia/app/pull/3859 459 if ( $filePage->userCan( 'delete', $this->getUser() ) ) { 460 $deleteMsg = $this->msg( 'listfiles-delete' )->escaped(); 461 462 $delete = Linker::linkKnown( 463 $filePage, $deleteMsg, array(), array( 'action' => 'delete' ) 464 ); 465 $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped(); 466 467 return "$link $download $delete"; 468 } 469 470 return "$link $download"; 471 } else { 472 return htmlspecialchars( $value ); 473 } 474 case 'img_user_text': 475 if ( $this->mCurrentRow->img_user ) { 476 $name = User::whoIs( $this->mCurrentRow->img_user ); 477 $link = Linker::link( 478 Title::makeTitle( NS_USER, $name ), 479 htmlspecialchars( $name ) 480 ); 481 } else { 482 $link = htmlspecialchars( $value ); 483 } 484 485 return $link; 486 case 'img_size': 487 return htmlspecialchars( $this->getLanguage()->formatSize( $value ) ); 488 case 'img_description': 489 return Linker::formatComment( $value ); 490 case 'count': 491 return intval( $value ) + 1; 492 case 'top': 493 // Messages: listfiles-latestversion-yes, listfiles-latestversion-no 494 return $this->msg( 'listfiles-latestversion-' . $value ); 495 default: 496 throw new MWException( "Unknown field '$field'" ); 497 } 498 } 499 500 function getForm() { 501 $fields = array(); 502 $fields['limit'] = array( 503 'type' => 'select', 504 'name' => 'limit', 505 'label-message' => 'table_pager_limit_label', 506 'options' => $this->getLimitSelectList(), 507 'default' => $this->mLimit, 508 ); 509 510 if ( !$this->getConfig()->get( 'MiserMode' ) ) { 511 $fields['ilsearch'] = array( 512 'type' => 'text', 513 'name' => 'ilsearch', 514 'id' => 'mw-ilsearch', 515 'label-message' => 'listfiles_search_for', 516 'default' => $this->mSearch, 517 'size' => '40', 518 'maxlength' => '255', 519 ); 520 } 521 522 $fields['user'] = array( 523 'type' => 'text', 524 'name' => 'user', 525 'id' => 'mw-listfiles-user', 526 'label-message' => 'username', 527 'default' => $this->mUserName, 528 'size' => '40', 529 'maxlength' => '255', 530 ); 531 532 $fields['ilshowall'] = array( 533 'type' => 'check', 534 'name' => 'ilshowall', 535 'id' => 'mw-listfiles-show-all', 536 'label-message' => 'listfiles-show-all', 537 'default' => $this->mShowAll, 538 ); 539 540 $query = $this->getRequest()->getQueryValues(); 541 unset( $query['title'] ); 542 unset( $query['limit'] ); 543 unset( $query['ilsearch'] ); 544 unset( $query['user'] ); 545 546 $form = new HTMLForm( $fields, $this->getContext() ); 547 548 $form->setMethod( 'get' ); 549 $form->setTitle( $this->getTitle() ); 550 $form->setId( 'mw-listfiles-form' ); 551 $form->setWrapperLegendMsg( 'listfiles' ); 552 $form->setSubmitTextMsg( 'table_pager_limit_submit' ); 553 $form->addHiddenFields( $query ); 554 555 $form->prepareForm(); 556 $form->displayForm( '' ); 557 } 558 559 function getTableClass() { 560 return parent::getTableClass() . ' listfiles'; 561 } 562 563 function getNavClass() { 564 return parent::getNavClass() . ' listfiles_nav'; 565 } 566 567 function getSortHeaderClass() { 568 return parent::getSortHeaderClass() . ' listfiles_sort'; 569 } 570 571 function getPagingQueries() { 572 $queries = parent::getPagingQueries(); 573 if ( !is_null( $this->mUserName ) ) { 574 # Append the username to the query string 575 foreach ( $queries as &$query ) { 576 if ( $query !== false ) { 577 $query['user'] = $this->mUserName; 578 } 579 } 580 } 581 582 return $queries; 583 } 584 585 function getDefaultQuery() { 586 $queries = parent::getDefaultQuery(); 587 if ( !isset( $queries['user'] ) && !is_null( $this->mUserName ) ) { 588 $queries['user'] = $this->mUserName; 589 } 590 591 return $queries; 592 } 593 594 function getTitle() { 595 return SpecialPage::getTitleFor( 'Listfiles' ); 596 } 597 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |