MediaWiki  REL1_21
Pager.php
Go to the documentation of this file.
00001 <?php
00032 interface Pager {
00033         function getNavigationBar();
00034         function getBody();
00035 }
00036 
00079 abstract class IndexPager extends ContextSource implements Pager {
00080         public $mRequest;
00081         public $mLimitsShown = array( 20, 50, 100, 250, 500 );
00082         public $mDefaultLimit = 50;
00083         public $mOffset, $mLimit;
00084         public $mQueryDone = false;
00085         public $mDb;
00086         public $mPastTheEndRow;
00087 
00092         protected $mIndexField;
00097         protected $mExtraSortFields;
00100         protected $mOrderType;
00112         public $mDefaultDirection;
00113         public $mIsBackwards;
00114 
00116         public $mIsFirst;
00117         public $mIsLast;
00118 
00119         protected $mLastShown, $mFirstShown, $mPastTheEndIndex, $mDefaultQuery, $mNavigationBar;
00120 
00124         protected $mIncludeOffset = false;
00125 
00131         public $mResult;
00132 
00133         public function __construct( IContextSource $context = null ) {
00134                 if ( $context ) {
00135                         $this->setContext( $context );
00136                 }
00137 
00138                 $this->mRequest = $this->getRequest();
00139 
00140                 # NB: the offset is quoted, not validated. It is treated as an
00141                 # arbitrary string to support the widest variety of index types. Be
00142                 # careful outputting it into HTML!
00143                 $this->mOffset = $this->mRequest->getText( 'offset' );
00144 
00145                 # Use consistent behavior for the limit options
00146                 $this->mDefaultLimit = $this->getUser()->getIntOption( 'rclimit' );
00147                 if ( !$this->mLimit ) {
00148                         // Don't override if a subclass calls $this->setLimit() in its constructor.
00149                         list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset();
00150                 }
00151 
00152                 $this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
00153                 $this->mDb = wfGetDB( DB_SLAVE );
00154 
00155                 $index = $this->getIndexField(); // column to sort on
00156                 $extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning
00157                 $order = $this->mRequest->getVal( 'order' );
00158                 if( is_array( $index ) && isset( $index[$order] ) ) {
00159                         $this->mOrderType = $order;
00160                         $this->mIndexField = $index[$order];
00161                         $this->mExtraSortFields = isset( $extraSort[$order] )
00162                                 ? (array)$extraSort[$order]
00163                                 : array();
00164                 } elseif( is_array( $index ) ) {
00165                         # First element is the default
00166                         reset( $index );
00167                         list( $this->mOrderType, $this->mIndexField ) = each( $index );
00168                         $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
00169                                 ? (array)$extraSort[$this->mOrderType]
00170                                 : array();
00171                 } else {
00172                         # $index is not an array
00173                         $this->mOrderType = null;
00174                         $this->mIndexField = $index;
00175                         $this->mExtraSortFields = (array)$extraSort;
00176                 }
00177 
00178                 if( !isset( $this->mDefaultDirection ) ) {
00179                         $dir = $this->getDefaultDirections();
00180                         $this->mDefaultDirection = is_array( $dir )
00181                                 ? $dir[$this->mOrderType]
00182                                 : $dir;
00183                 }
00184         }
00185 
00191         public function getDatabase() {
00192                 return $this->mDb;
00193         }
00194 
00200         public function doQuery() {
00201                 # Use the child class name for profiling
00202                 $fname = __METHOD__ . ' (' . get_class( $this ) . ')';
00203                 wfProfileIn( $fname );
00204 
00205                 $descending = ( $this->mIsBackwards == $this->mDefaultDirection );
00206                 # Plus an extra row so that we can tell the "next" link should be shown
00207                 $queryLimit = $this->mLimit + 1;
00208 
00209                 if ( $this->mOffset == '' ) {
00210                         $isFirst = true;
00211                 } else {
00212                         // If there's an offset, we may or may not be at the first entry.
00213                         // The only way to tell is to run the query in the opposite
00214                         // direction see if we get a row.
00215                         $oldIncludeOffset = $this->mIncludeOffset;
00216                         $this->mIncludeOffset = !$this->mIncludeOffset;
00217                         $isFirst = !$this->reallyDoQuery( $this->mOffset, 1, !$descending )->numRows();
00218                         $this->mIncludeOffset = $oldIncludeOffset;
00219                 }
00220 
00221                 $this->mResult = $this->reallyDoQuery(
00222                         $this->mOffset,
00223                         $queryLimit,
00224                         $descending
00225                 );
00226 
00227                 $this->extractResultInfo( $isFirst, $queryLimit, $this->mResult );
00228                 $this->mQueryDone = true;
00229 
00230                 $this->preprocessResults( $this->mResult );
00231                 $this->mResult->rewind(); // Paranoia
00232 
00233                 wfProfileOut( $fname );
00234         }
00235 
00239         function getResult() {
00240                 return $this->mResult;
00241         }
00242 
00248         function setOffset( $offset ) {
00249                 $this->mOffset = $offset;
00250         }
00258         function setLimit( $limit ) {
00259                 $limit = (int) $limit;
00260                 // WebRequest::getLimitOffset() puts a cap of 5000, so do same here.
00261                 if ( $limit > 5000 ) {
00262                         $limit = 5000;
00263                 }
00264                 if ( $limit > 0 ) {
00265                         $this->mLimit = $limit;
00266                 }
00267         }
00268 
00276         public function setIncludeOffset( $include ) {
00277                 $this->mIncludeOffset = $include;
00278         }
00279 
00289         function extractResultInfo( $isFirst, $limit, ResultWrapper $res ) {
00290                 $numRows = $res->numRows();
00291                 if ( $numRows ) {
00292                         # Remove any table prefix from index field
00293                         $parts = explode( '.', $this->mIndexField );
00294                         $indexColumn = end( $parts );
00295 
00296                         $row = $res->fetchRow();
00297                         $firstIndex = $row[$indexColumn];
00298 
00299                         # Discard the extra result row if there is one
00300                         if ( $numRows > $this->mLimit && $numRows > 1 ) {
00301                                 $res->seek( $numRows - 1 );
00302                                 $this->mPastTheEndRow = $res->fetchObject();
00303                                 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
00304                                 $res->seek( $numRows - 2 );
00305                                 $row = $res->fetchRow();
00306                                 $lastIndex = $row[$indexColumn];
00307                         } else {
00308                                 $this->mPastTheEndRow = null;
00309                                 # Setting indexes to an empty string means that they will be
00310                                 # omitted if they would otherwise appear in URLs. It just so
00311                                 # happens that this  is the right thing to do in the standard
00312                                 # UI, in all the relevant cases.
00313                                 $this->mPastTheEndIndex = '';
00314                                 $res->seek( $numRows - 1 );
00315                                 $row = $res->fetchRow();
00316                                 $lastIndex = $row[$indexColumn];
00317                         }
00318                 } else {
00319                         $firstIndex = '';
00320                         $lastIndex = '';
00321                         $this->mPastTheEndRow = null;
00322                         $this->mPastTheEndIndex = '';
00323                 }
00324 
00325                 if ( $this->mIsBackwards ) {
00326                         $this->mIsFirst = ( $numRows < $limit );
00327                         $this->mIsLast = $isFirst;
00328                         $this->mLastShown = $firstIndex;
00329                         $this->mFirstShown = $lastIndex;
00330                 } else {
00331                         $this->mIsFirst = $isFirst;
00332                         $this->mIsLast = ( $numRows < $limit );
00333                         $this->mLastShown = $lastIndex;
00334                         $this->mFirstShown = $firstIndex;
00335                 }
00336         }
00337 
00343         function getSqlComment() {
00344                 return get_class( $this );
00345         }
00346 
00356         public function reallyDoQuery( $offset, $limit, $descending ) {
00357                 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending );
00358                 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
00359         }
00360 
00369         protected function buildQueryInfo( $offset, $limit, $descending ) {
00370                 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
00371                 $info = $this->getQueryInfo();
00372                 $tables = $info['tables'];
00373                 $fields = $info['fields'];
00374                 $conds = isset( $info['conds'] ) ? $info['conds'] : array();
00375                 $options = isset( $info['options'] ) ? $info['options'] : array();
00376                 $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array();
00377                 $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields );
00378                 if ( $descending ) {
00379                         $options['ORDER BY'] = $sortColumns;
00380                         $operator = $this->mIncludeOffset ? '>=' : '>';
00381                 } else {
00382                         $orderBy = array();
00383                         foreach ( $sortColumns as $col ) {
00384                                 $orderBy[] = $col . ' DESC';
00385                         }
00386                         $options['ORDER BY'] = $orderBy;
00387                         $operator = $this->mIncludeOffset ? '<=' : '<';
00388                 }
00389                 if ( $offset != '' ) {
00390                         $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
00391                 }
00392                 $options['LIMIT'] = intval( $limit );
00393                 return array( $tables, $fields, $conds, $fname, $options, $join_conds );
00394         }
00395 
00401         protected function preprocessResults( $result ) {}
00402 
00409         public function getBody() {
00410                 if ( !$this->mQueryDone ) {
00411                         $this->doQuery();
00412                 }
00413 
00414                 if ( $this->mResult->numRows() ) {
00415                         # Do any special query batches before display
00416                         $this->doBatchLookups();
00417                 }
00418 
00419                 # Don't use any extra rows returned by the query
00420                 $numRows = min( $this->mResult->numRows(), $this->mLimit );
00421 
00422                 $s = $this->getStartBody();
00423                 if ( $numRows ) {
00424                         if ( $this->mIsBackwards ) {
00425                                 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
00426                                         $this->mResult->seek( $i );
00427                                         $row = $this->mResult->fetchObject();
00428                                         $s .= $this->formatRow( $row );
00429                                 }
00430                         } else {
00431                                 $this->mResult->seek( 0 );
00432                                 for ( $i = 0; $i < $numRows; $i++ ) {
00433                                         $row = $this->mResult->fetchObject();
00434                                         $s .= $this->formatRow( $row );
00435                                 }
00436                         }
00437                 } else {
00438                         $s .= $this->getEmptyBody();
00439                 }
00440                 $s .= $this->getEndBody();
00441                 return $s;
00442         }
00443 
00453         function makeLink( $text, array $query = null, $type = null ) {
00454                 if ( $query === null ) {
00455                         return $text;
00456                 }
00457 
00458                 $attrs = array();
00459                 if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) {
00460                         # HTML5 rel attributes
00461                         $attrs['rel'] = $type;
00462                 }
00463 
00464                 if( $type ) {
00465                         $attrs['class'] = "mw-{$type}link";
00466                 }
00467 
00468                 return Linker::linkKnown(
00469                         $this->getTitle(),
00470                         $text,
00471                         $attrs,
00472                         $query + $this->getDefaultQuery()
00473                 );
00474         }
00475 
00483         protected function doBatchLookups() {}
00484 
00491         protected function getStartBody() {
00492                 return '';
00493         }
00494 
00500         protected function getEndBody() {
00501                 return '';
00502         }
00503 
00510         protected function getEmptyBody() {
00511                 return '';
00512         }
00513 
00521         function getDefaultQuery() {
00522                 if ( !isset( $this->mDefaultQuery ) ) {
00523                         $this->mDefaultQuery = $this->getRequest()->getQueryValues();
00524                         unset( $this->mDefaultQuery['title'] );
00525                         unset( $this->mDefaultQuery['dir'] );
00526                         unset( $this->mDefaultQuery['offset'] );
00527                         unset( $this->mDefaultQuery['limit'] );
00528                         unset( $this->mDefaultQuery['order'] );
00529                         unset( $this->mDefaultQuery['month'] );
00530                         unset( $this->mDefaultQuery['year'] );
00531                 }
00532                 return $this->mDefaultQuery;
00533         }
00534 
00540         function getNumRows() {
00541                 if ( !$this->mQueryDone ) {
00542                         $this->doQuery();
00543                 }
00544                 return $this->mResult->numRows();
00545         }
00546 
00552         function getPagingQueries() {
00553                 if ( !$this->mQueryDone ) {
00554                         $this->doQuery();
00555                 }
00556 
00557                 # Don't announce the limit everywhere if it's the default
00558                 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit;
00559 
00560                 if ( $this->mIsFirst ) {
00561                         $prev = false;
00562                         $first = false;
00563                 } else {
00564                         $prev = array(
00565                                 'dir' => 'prev',
00566                                 'offset' => $this->mFirstShown,
00567                                 'limit' => $urlLimit
00568                         );
00569                         $first = array( 'limit' => $urlLimit );
00570                 }
00571                 if ( $this->mIsLast ) {
00572                         $next = false;
00573                         $last = false;
00574                 } else {
00575                         $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit );
00576                         $last = array( 'dir' => 'prev', 'limit' => $urlLimit );
00577                 }
00578                 return array(
00579                         'prev' => $prev,
00580                         'next' => $next,
00581                         'first' => $first,
00582                         'last' => $last
00583                 );
00584         }
00585 
00591         function isNavigationBarShown() {
00592                 if ( !$this->mQueryDone ) {
00593                         $this->doQuery();
00594                 }
00595                 // Hide navigation by default if there is nothing to page
00596                 return !($this->mIsFirst && $this->mIsLast);
00597         }
00598 
00609         function getPagingLinks( $linkTexts, $disabledTexts = array() ) {
00610                 $queries = $this->getPagingQueries();
00611                 $links = array();
00612 
00613                 foreach ( $queries as $type => $query ) {
00614                         if ( $query !== false ) {
00615                                 $links[$type] = $this->makeLink(
00616                                         $linkTexts[$type],
00617                                         $queries[$type],
00618                                         $type
00619                                 );
00620                         } elseif ( isset( $disabledTexts[$type] ) ) {
00621                                 $links[$type] = $disabledTexts[$type];
00622                         } else {
00623                                 $links[$type] = $linkTexts[$type];
00624                         }
00625                 }
00626 
00627                 return $links;
00628         }
00629 
00630         function getLimitLinks() {
00631                 $links = array();
00632                 if ( $this->mIsBackwards ) {
00633                         $offset = $this->mPastTheEndIndex;
00634                 } else {
00635                         $offset = $this->mOffset;
00636                 }
00637                 foreach ( $this->mLimitsShown as $limit ) {
00638                         $links[] = $this->makeLink(
00639                                 $this->getLanguage()->formatNum( $limit ),
00640                                 array( 'offset' => $offset, 'limit' => $limit ),
00641                                 'num'
00642                         );
00643                 }
00644                 return $links;
00645         }
00646 
00655         abstract function formatRow( $row );
00656 
00669         abstract function getQueryInfo();
00670 
00683         abstract function getIndexField();
00684 
00701         protected function getExtraSortFields() { return array(); }
00702 
00722         protected function getDefaultDirections() { return false; }
00723 }
00724 
00729 abstract class AlphabeticPager extends IndexPager {
00730 
00737         function getNavigationBar() {
00738                 if ( !$this->isNavigationBarShown() ) {
00739                         return '';
00740                 }
00741 
00742                 if( isset( $this->mNavigationBar ) ) {
00743                         return $this->mNavigationBar;
00744                 }
00745 
00746                 $linkTexts = array(
00747                         'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(),
00748                         'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(),
00749                         'first' => $this->msg( 'page_first' )->escaped(),
00750                         'last' => $this->msg( 'page_last' )->escaped()
00751                 );
00752 
00753                 $lang = $this->getLanguage();
00754 
00755                 $pagingLinks = $this->getPagingLinks( $linkTexts );
00756                 $limitLinks = $this->getLimitLinks();
00757                 $limits = $lang->pipeList( $limitLinks );
00758 
00759                 $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams(
00760                         $lang->pipeList( array( $pagingLinks['first'],
00761                         $pagingLinks['last'] ) ) )->escaped() . " " .
00762                         $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'],
00763                                 $pagingLinks['next'], $limits )->escaped();
00764 
00765                 if( !is_array( $this->getIndexField() ) ) {
00766                         # Early return to avoid undue nesting
00767                         return $this->mNavigationBar;
00768                 }
00769 
00770                 $extra = '';
00771                 $first = true;
00772                 $msgs = $this->getOrderTypeMessages();
00773                 foreach( array_keys( $msgs ) as $order ) {
00774                         if( $first ) {
00775                                 $first = false;
00776                         } else {
00777                                 $extra .= $this->msg( 'pipe-separator' )->escaped();
00778                         }
00779 
00780                         if( $order == $this->mOrderType ) {
00781                                 $extra .= $this->msg( $msgs[$order] )->escaped();
00782                         } else {
00783                                 $extra .= $this->makeLink(
00784                                         $this->msg( $msgs[$order] )->escaped(),
00785                                         array( 'order' => $order )
00786                                 );
00787                         }
00788                 }
00789 
00790                 if( $extra !== '' ) {
00791                         $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped();
00792                         $this->mNavigationBar .= $extra;
00793                 }
00794 
00795                 return $this->mNavigationBar;
00796         }
00797 
00806         protected function getOrderTypeMessages() {
00807                 return null;
00808         }
00809 }
00810 
00815 abstract class ReverseChronologicalPager extends IndexPager {
00816         public $mDefaultDirection = true;
00817         public $mYear;
00818         public $mMonth;
00819 
00820         function getNavigationBar() {
00821                 if ( !$this->isNavigationBarShown() ) {
00822                         return '';
00823                 }
00824 
00825                 if ( isset( $this->mNavigationBar ) ) {
00826                         return $this->mNavigationBar;
00827                 }
00828 
00829                 $linkTexts = array(
00830                         'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
00831                         'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
00832                         'first' => $this->msg( 'histlast' )->escaped(),
00833                         'last' => $this->msg( 'histfirst' )->escaped()
00834                 );
00835 
00836                 $pagingLinks = $this->getPagingLinks( $linkTexts );
00837                 $limitLinks = $this->getLimitLinks();
00838                 $limits = $this->getLanguage()->pipeList( $limitLinks );
00839                 $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" .
00840                         $this->msg( 'pipe-separator' )->escaped() .
00841                         "{$pagingLinks['last']}" )->escaped();
00842 
00843                 $this->mNavigationBar = $firstLastLinks . ' ' .
00844                         $this->msg( 'viewprevnext' )->rawParams(
00845                                 $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped();
00846 
00847                 return $this->mNavigationBar;
00848         }
00849 
00850         function getDateCond( $year, $month ) {
00851                 $year = intval( $year );
00852                 $month = intval( $month );
00853 
00854                 // Basic validity checks
00855                 $this->mYear = $year > 0 ? $year : false;
00856                 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
00857 
00858                 // Given an optional year and month, we need to generate a timestamp
00859                 // to use as "WHERE rev_timestamp <= result"
00860                 // Examples: year = 2006 equals < 20070101 (+000000)
00861                 // year=2005, month=1    equals < 20050201
00862                 // year=2005, month=12   equals < 20060101
00863                 if ( !$this->mYear && !$this->mMonth ) {
00864                         return;
00865                 }
00866 
00867                 if ( $this->mYear ) {
00868                         $year = $this->mYear;
00869                 } else {
00870                         // If no year given, assume the current one
00871                         $year = gmdate( 'Y' );
00872                         // If this month hasn't happened yet this year, go back to last year's month
00873                         if( $this->mMonth > gmdate( 'n' ) ) {
00874                                 $year--;
00875                         }
00876                 }
00877 
00878                 if ( $this->mMonth ) {
00879                         $month = $this->mMonth + 1;
00880                         // For December, we want January 1 of the next year
00881                         if ( $month > 12 ) {
00882                                 $month = 1;
00883                                 $year++;
00884                         }
00885                 } else {
00886                         // No month implies we want up to the end of the year in question
00887                         $month = 1;
00888                         $year++;
00889                 }
00890 
00891                 // Y2K38 bug
00892                 if ( $year > 2032 ) {
00893                         $year = 2032;
00894                 }
00895 
00896                 $ymd = (int)sprintf( "%04d%02d01", $year, $month );
00897 
00898                 if ( $ymd > 20320101 ) {
00899                         $ymd = 20320101;
00900                 }
00901 
00902                 $this->mOffset = $this->mDb->timestamp( "${ymd}000000" );
00903         }
00904 }
00905 
00910 abstract class TablePager extends IndexPager {
00911         var $mSort;
00912         var $mCurrentRow;
00913 
00914         public function __construct( IContextSource $context = null ) {
00915                 if ( $context ) {
00916                         $this->setContext( $context );
00917                 }
00918 
00919                 $this->mSort = $this->getRequest()->getText( 'sort' );
00920                 if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) {
00921                         $this->mSort = $this->getDefaultSort();
00922                 }
00923                 if ( $this->getRequest()->getBool( 'asc' ) ) {
00924                         $this->mDefaultDirection = false;
00925                 } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
00926                         $this->mDefaultDirection = true;
00927                 } /* Else leave it at whatever the class default is */
00928 
00929                 parent::__construct();
00930         }
00931 
00936         function getStartBody() {
00937                 global $wgStylePath;
00938                 $tableClass = htmlspecialchars( $this->getTableClass() );
00939                 $sortClass = htmlspecialchars( $this->getSortHeaderClass() );
00940 
00941                 $s = "<table style='border:1px;' class=\"mw-datatable $tableClass\"><thead><tr>\n";
00942                 $fields = $this->getFieldNames();
00943 
00944                 # Make table header
00945                 foreach ( $fields as $field => $name ) {
00946                         if ( strval( $name ) == '' ) {
00947                                 $s .= "<th>&#160;</th>\n";
00948                         } elseif ( $this->isFieldSortable( $field ) ) {
00949                                 $query = array( 'sort' => $field, 'limit' => $this->mLimit );
00950                                 if ( $field == $this->mSort ) {
00951                                         # This is the sorted column
00952                                         # Prepare a link that goes in the other sort order
00953                                         if ( $this->mDefaultDirection ) {
00954                                                 # Descending
00955                                                 $image = 'Arr_d.png';
00956                                                 $query['asc'] = '1';
00957                                                 $query['desc'] = '';
00958                                                 $alt = $this->msg( 'descending_abbrev' )->escaped();
00959                                         } else {
00960                                                 # Ascending
00961                                                 $image = 'Arr_u.png';
00962                                                 $query['asc'] = '';
00963                                                 $query['desc'] = '1';
00964                                                 $alt = $this->msg( 'ascending_abbrev' )->escaped();
00965                                         }
00966                                         $image = htmlspecialchars( "$wgStylePath/common/images/$image" );
00967                                         $link = $this->makeLink(
00968                                                 "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" .
00969                                                         htmlspecialchars( $name ), $query );
00970                                         $s .= "<th class=\"$sortClass\">$link</th>\n";
00971                                 } else {
00972                                         $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n";
00973                                 }
00974                         } else {
00975                                 $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n";
00976                         }
00977                 }
00978                 $s .= "</tr></thead><tbody>\n";
00979                 return $s;
00980         }
00981 
00986         function getEndBody() {
00987                 return "</tbody></table>\n";
00988         }
00989 
00994         function getEmptyBody() {
00995                 $colspan = count( $this->getFieldNames() );
00996                 $msgEmpty = $this->msg( 'table_pager_empty' )->escaped();
00997                 return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n";
00998         }
00999 
01005         function formatRow( $row ) {
01006                 $this->mCurrentRow = $row; // In case formatValue etc need to know
01007                 $s = Xml::openElement( 'tr', $this->getRowAttrs( $row ) );
01008                 $fieldNames = $this->getFieldNames();
01009 
01010                 foreach ( $fieldNames as $field => $name ) {
01011                         $value = isset( $row->$field ) ? $row->$field : null;
01012                         $formatted = strval( $this->formatValue( $field, $value ) );
01013 
01014                         if ( $formatted == '' ) {
01015                                 $formatted = '&#160;';
01016                         }
01017 
01018                         $s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted );
01019                 }
01020 
01021                 $s .= "</tr>\n";
01022 
01023                 return $s;
01024         }
01025 
01034         function getRowClass( $row ) {
01035                 return '';
01036         }
01037 
01046         function getRowAttrs( $row ) {
01047                 $class = $this->getRowClass( $row );
01048                 if ( $class === '' ) {
01049                         // Return an empty array to avoid clutter in HTML like class=""
01050                         return array();
01051                 } else {
01052                         return array( 'class' => $this->getRowClass( $row ) );
01053                 }
01054         }
01055 
01067         function getCellAttrs( $field, $value ) {
01068                 return array( 'class' => 'TablePager_col_' . $field );
01069         }
01070 
01075         function getIndexField() {
01076                 return $this->mSort;
01077         }
01078 
01083         function getTableClass() {
01084                 return 'TablePager';
01085         }
01086 
01091         function getNavClass() {
01092                 return 'TablePager_nav';
01093         }
01094 
01099         function getSortHeaderClass() {
01100                 return 'TablePager_sort';
01101         }
01102 
01107         public function getNavigationBar() {
01108                 global $wgStylePath;
01109 
01110                 if ( !$this->isNavigationBarShown() ) {
01111                         return '';
01112                 }
01113 
01114                 $path = "$wgStylePath/common/images";
01115                 $labels = array(
01116                         'first' => 'table_pager_first',
01117                         'prev' => 'table_pager_prev',
01118                         'next' => 'table_pager_next',
01119                         'last' => 'table_pager_last',
01120                 );
01121                 $images = array(
01122                         'first' => 'arrow_first_25.png',
01123                         'prev' => 'arrow_left_25.png',
01124                         'next' => 'arrow_right_25.png',
01125                         'last' => 'arrow_last_25.png',
01126                 );
01127                 $disabledImages = array(
01128                         'first' => 'arrow_disabled_first_25.png',
01129                         'prev' => 'arrow_disabled_left_25.png',
01130                         'next' => 'arrow_disabled_right_25.png',
01131                         'last' => 'arrow_disabled_last_25.png',
01132                 );
01133                 if( $this->getLanguage()->isRTL() ) {
01134                         $keys = array_keys( $labels );
01135                         $images = array_combine( $keys, array_reverse( $images ) );
01136                         $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) );
01137                 }
01138 
01139                 $linkTexts = array();
01140                 $disabledTexts = array();
01141                 foreach ( $labels as $type => $label ) {
01142                         $msgLabel = $this->msg( $label )->escaped();
01143                         $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel";
01144                         $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel";
01145                 }
01146                 $links = $this->getPagingLinks( $linkTexts, $disabledTexts );
01147 
01148                 $navClass = htmlspecialchars( $this->getNavClass() );
01149                 $s = "<table class=\"$navClass\"><tr>\n";
01150                 $width = 100 / count( $links ) . '%';
01151                 foreach ( $labels as $type => $label ) {
01152                         $s .= "<td style='width:$width;'>{$links[$type]}</td>\n";
01153                 }
01154                 $s .= "</tr></table>\n";
01155                 return $s;
01156         }
01157 
01163         public function getLimitSelect() {
01164                 # Add the current limit from the query string
01165                 # to avoid that the limit is lost after clicking Go next time
01166                 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
01167                         $this->mLimitsShown[] = $this->mLimit;
01168                         sort( $this->mLimitsShown );
01169                 }
01170                 $s = Html::openElement( 'select', array( 'name' => 'limit' ) ) . "\n";
01171                 foreach ( $this->mLimitsShown as $key => $value ) {
01172                         # The pair is either $index => $limit, in which case the $value
01173                         # will be numeric, or $limit => $text, in which case the $value
01174                         # will be a string.
01175                         if( is_int( $value ) ) {
01176                                 $limit = $value;
01177                                 $text = $this->getLanguage()->formatNum( $limit );
01178                         } else {
01179                                 $limit = $key;
01180                                 $text = $value;
01181                         }
01182                         $s .= Xml::option( $text, $limit, $limit == $this->mLimit ) . "\n";
01183                 }
01184                 $s .= Html::closeElement( 'select' );
01185                 return $s;
01186         }
01187 
01196         function getHiddenFields( $blacklist = array() ) {
01197                 $blacklist = (array)$blacklist;
01198                 $query = $this->getRequest()->getQueryValues();
01199                 foreach ( $blacklist as $name ) {
01200                         unset( $query[$name] );
01201                 }
01202                 $s = '';
01203                 foreach ( $query as $name => $value ) {
01204                         $encName = htmlspecialchars( $name );
01205                         $encValue = htmlspecialchars( $value );
01206                         $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n";
01207                 }
01208                 return $s;
01209         }
01210 
01216         function getLimitForm() {
01217                 global $wgScript;
01218 
01219                 return Xml::openElement(
01220                         'form',
01221                         array(
01222                                 'method' => 'get',
01223                                 'action' => $wgScript
01224                         ) ) . "\n" . $this->getLimitDropdown() . "</form>\n";
01225         }
01226 
01232         function getLimitDropdown() {
01233                 # Make the select with some explanatory text
01234                 $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped();
01235 
01236                 return $this->msg( 'table_pager_limit' )
01237                         ->rawParams( $this->getLimitSelect() )->escaped() .
01238                         "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
01239                         $this->getHiddenFields( array( 'limit' ) );
01240         }
01241 
01248         abstract function isFieldSortable( $field );
01249 
01262         abstract function formatValue( $name, $value );
01263 
01271         abstract function getDefaultSort();
01272 
01280         abstract function getFieldNames();
01281 }