MediaWiki
REL1_21
|
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> </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 = ' '; 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 }