MediaWiki  REL1_24
SpecialListfiles.php
Go to the documentation of this file.
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 }