MediaWiki
master
|
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 = intval( $this->getUser()->getOption( '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 $this->mResult = $this->reallyDoQuery( 00210 $this->mOffset, 00211 $queryLimit, 00212 $descending 00213 ); 00214 00215 $this->extractResultInfo( $this->mOffset, $queryLimit, $this->mResult ); 00216 $this->mQueryDone = true; 00217 00218 $this->preprocessResults( $this->mResult ); 00219 $this->mResult->rewind(); // Paranoia 00220 00221 wfProfileOut( $fname ); 00222 } 00223 00227 function getResult() { 00228 return $this->mResult; 00229 } 00230 00236 function setOffset( $offset ) { 00237 $this->mOffset = $offset; 00238 } 00246 function setLimit( $limit ) { 00247 $limit = (int) $limit; 00248 // WebRequest::getLimitOffset() puts a cap of 5000, so do same here. 00249 if ( $limit > 5000 ) { 00250 $limit = 5000; 00251 } 00252 if ( $limit > 0 ) { 00253 $this->mLimit = $limit; 00254 } 00255 } 00256 00264 public function setIncludeOffset( $include ) { 00265 $this->mIncludeOffset = $include; 00266 } 00267 00276 function extractResultInfo( $offset, $limit, ResultWrapper $res ) { 00277 $numRows = $res->numRows(); 00278 if ( $numRows ) { 00279 # Remove any table prefix from index field 00280 $parts = explode( '.', $this->mIndexField ); 00281 $indexColumn = end( $parts ); 00282 00283 $row = $res->fetchRow(); 00284 $firstIndex = $row[$indexColumn]; 00285 00286 # Discard the extra result row if there is one 00287 if ( $numRows > $this->mLimit && $numRows > 1 ) { 00288 $res->seek( $numRows - 1 ); 00289 $this->mPastTheEndRow = $res->fetchObject(); 00290 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn; 00291 $res->seek( $numRows - 2 ); 00292 $row = $res->fetchRow(); 00293 $lastIndex = $row[$indexColumn]; 00294 } else { 00295 $this->mPastTheEndRow = null; 00296 # Setting indexes to an empty string means that they will be 00297 # omitted if they would otherwise appear in URLs. It just so 00298 # happens that this is the right thing to do in the standard 00299 # UI, in all the relevant cases. 00300 $this->mPastTheEndIndex = ''; 00301 $res->seek( $numRows - 1 ); 00302 $row = $res->fetchRow(); 00303 $lastIndex = $row[$indexColumn]; 00304 } 00305 } else { 00306 $firstIndex = ''; 00307 $lastIndex = ''; 00308 $this->mPastTheEndRow = null; 00309 $this->mPastTheEndIndex = ''; 00310 } 00311 00312 if ( $this->mIsBackwards ) { 00313 $this->mIsFirst = ( $numRows < $limit ); 00314 $this->mIsLast = ( $offset == '' ); 00315 $this->mLastShown = $firstIndex; 00316 $this->mFirstShown = $lastIndex; 00317 } else { 00318 $this->mIsFirst = ( $offset == '' ); 00319 $this->mIsLast = ( $numRows < $limit ); 00320 $this->mLastShown = $lastIndex; 00321 $this->mFirstShown = $firstIndex; 00322 } 00323 } 00324 00330 function getSqlComment() { 00331 return get_class( $this ); 00332 } 00333 00343 public function reallyDoQuery( $offset, $limit, $descending ) { 00344 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending ); 00345 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00346 } 00347 00356 protected function buildQueryInfo( $offset, $limit, $descending ) { 00357 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')'; 00358 $info = $this->getQueryInfo(); 00359 $tables = $info['tables']; 00360 $fields = $info['fields']; 00361 $conds = isset( $info['conds'] ) ? $info['conds'] : array(); 00362 $options = isset( $info['options'] ) ? $info['options'] : array(); 00363 $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array(); 00364 $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields ); 00365 if ( $descending ) { 00366 $options['ORDER BY'] = $sortColumns; 00367 $operator = $this->mIncludeOffset ? '>=' : '>'; 00368 } else { 00369 $orderBy = array(); 00370 foreach ( $sortColumns as $col ) { 00371 $orderBy[] = $col . ' DESC'; 00372 } 00373 $options['ORDER BY'] = $orderBy; 00374 $operator = $this->mIncludeOffset ? '<=' : '<'; 00375 } 00376 if ( $offset != '' ) { 00377 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset ); 00378 } 00379 $options['LIMIT'] = intval( $limit ); 00380 return array( $tables, $fields, $conds, $fname, $options, $join_conds ); 00381 } 00382 00388 protected function preprocessResults( $result ) {} 00389 00396 public function getBody() { 00397 if ( !$this->mQueryDone ) { 00398 $this->doQuery(); 00399 } 00400 00401 if ( $this->mResult->numRows() ) { 00402 # Do any special query batches before display 00403 $this->doBatchLookups(); 00404 } 00405 00406 # Don't use any extra rows returned by the query 00407 $numRows = min( $this->mResult->numRows(), $this->mLimit ); 00408 00409 $s = $this->getStartBody(); 00410 if ( $numRows ) { 00411 if ( $this->mIsBackwards ) { 00412 for ( $i = $numRows - 1; $i >= 0; $i-- ) { 00413 $this->mResult->seek( $i ); 00414 $row = $this->mResult->fetchObject(); 00415 $s .= $this->formatRow( $row ); 00416 } 00417 } else { 00418 $this->mResult->seek( 0 ); 00419 for ( $i = 0; $i < $numRows; $i++ ) { 00420 $row = $this->mResult->fetchObject(); 00421 $s .= $this->formatRow( $row ); 00422 } 00423 } 00424 } else { 00425 $s .= $this->getEmptyBody(); 00426 } 00427 $s .= $this->getEndBody(); 00428 return $s; 00429 } 00430 00440 function makeLink( $text, array $query = null, $type = null ) { 00441 if ( $query === null ) { 00442 return $text; 00443 } 00444 00445 $attrs = array(); 00446 if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { 00447 # HTML5 rel attributes 00448 $attrs['rel'] = $type; 00449 } 00450 00451 if( $type ) { 00452 $attrs['class'] = "mw-{$type}link"; 00453 } 00454 00455 return Linker::linkKnown( 00456 $this->getTitle(), 00457 $text, 00458 $attrs, 00459 $query + $this->getDefaultQuery() 00460 ); 00461 } 00462 00470 protected function doBatchLookups() {} 00471 00478 protected function getStartBody() { 00479 return ''; 00480 } 00481 00487 protected function getEndBody() { 00488 return ''; 00489 } 00490 00497 protected function getEmptyBody() { 00498 return ''; 00499 } 00500 00508 function getDefaultQuery() { 00509 if ( !isset( $this->mDefaultQuery ) ) { 00510 $this->mDefaultQuery = $this->getRequest()->getQueryValues(); 00511 unset( $this->mDefaultQuery['title'] ); 00512 unset( $this->mDefaultQuery['dir'] ); 00513 unset( $this->mDefaultQuery['offset'] ); 00514 unset( $this->mDefaultQuery['limit'] ); 00515 unset( $this->mDefaultQuery['order'] ); 00516 unset( $this->mDefaultQuery['month'] ); 00517 unset( $this->mDefaultQuery['year'] ); 00518 } 00519 return $this->mDefaultQuery; 00520 } 00521 00527 function getNumRows() { 00528 if ( !$this->mQueryDone ) { 00529 $this->doQuery(); 00530 } 00531 return $this->mResult->numRows(); 00532 } 00533 00539 function getPagingQueries() { 00540 if ( !$this->mQueryDone ) { 00541 $this->doQuery(); 00542 } 00543 00544 # Don't announce the limit everywhere if it's the default 00545 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit; 00546 00547 if ( $this->mIsFirst ) { 00548 $prev = false; 00549 $first = false; 00550 } else { 00551 $prev = array( 00552 'dir' => 'prev', 00553 'offset' => $this->mFirstShown, 00554 'limit' => $urlLimit 00555 ); 00556 $first = array( 'limit' => $urlLimit ); 00557 } 00558 if ( $this->mIsLast ) { 00559 $next = false; 00560 $last = false; 00561 } else { 00562 $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); 00563 $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); 00564 } 00565 return array( 00566 'prev' => $prev, 00567 'next' => $next, 00568 'first' => $first, 00569 'last' => $last 00570 ); 00571 } 00572 00578 function isNavigationBarShown() { 00579 if ( !$this->mQueryDone ) { 00580 $this->doQuery(); 00581 } 00582 // Hide navigation by default if there is nothing to page 00583 return !($this->mIsFirst && $this->mIsLast); 00584 } 00585 00596 function getPagingLinks( $linkTexts, $disabledTexts = array() ) { 00597 $queries = $this->getPagingQueries(); 00598 $links = array(); 00599 00600 foreach ( $queries as $type => $query ) { 00601 if ( $query !== false ) { 00602 $links[$type] = $this->makeLink( 00603 $linkTexts[$type], 00604 $queries[$type], 00605 $type 00606 ); 00607 } elseif ( isset( $disabledTexts[$type] ) ) { 00608 $links[$type] = $disabledTexts[$type]; 00609 } else { 00610 $links[$type] = $linkTexts[$type]; 00611 } 00612 } 00613 00614 return $links; 00615 } 00616 00617 function getLimitLinks() { 00618 $links = array(); 00619 if ( $this->mIsBackwards ) { 00620 $offset = $this->mPastTheEndIndex; 00621 } else { 00622 $offset = $this->mOffset; 00623 } 00624 foreach ( $this->mLimitsShown as $limit ) { 00625 $links[] = $this->makeLink( 00626 $this->getLanguage()->formatNum( $limit ), 00627 array( 'offset' => $offset, 'limit' => $limit ), 00628 'num' 00629 ); 00630 } 00631 return $links; 00632 } 00633 00642 abstract function formatRow( $row ); 00643 00656 abstract function getQueryInfo(); 00657 00670 abstract function getIndexField(); 00671 00688 protected function getExtraSortFields() { return array(); } 00689 00709 protected function getDefaultDirections() { return false; } 00710 } 00711 00712 00717 abstract class AlphabeticPager extends IndexPager { 00718 00725 function getNavigationBar() { 00726 if ( !$this->isNavigationBarShown() ) { 00727 return ''; 00728 } 00729 00730 if( isset( $this->mNavigationBar ) ) { 00731 return $this->mNavigationBar; 00732 } 00733 00734 $linkTexts = array( 00735 'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(), 00736 'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(), 00737 'first' => $this->msg( 'page_first' )->escaped(), 00738 'last' => $this->msg( 'page_last' )->escaped() 00739 ); 00740 00741 $lang = $this->getLanguage(); 00742 00743 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00744 $limitLinks = $this->getLimitLinks(); 00745 $limits = $lang->pipeList( $limitLinks ); 00746 00747 $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams( 00748 $lang->pipeList( array( $pagingLinks['first'], 00749 $pagingLinks['last'] ) ) )->escaped() . " " . 00750 $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], 00751 $pagingLinks['next'], $limits )->escaped(); 00752 00753 if( !is_array( $this->getIndexField() ) ) { 00754 # Early return to avoid undue nesting 00755 return $this->mNavigationBar; 00756 } 00757 00758 $extra = ''; 00759 $first = true; 00760 $msgs = $this->getOrderTypeMessages(); 00761 foreach( array_keys( $msgs ) as $order ) { 00762 if( $first ) { 00763 $first = false; 00764 } else { 00765 $extra .= $this->msg( 'pipe-separator' )->escaped(); 00766 } 00767 00768 if( $order == $this->mOrderType ) { 00769 $extra .= $this->msg( $msgs[$order] )->escaped(); 00770 } else { 00771 $extra .= $this->makeLink( 00772 $this->msg( $msgs[$order] )->escaped(), 00773 array( 'order' => $order ) 00774 ); 00775 } 00776 } 00777 00778 if( $extra !== '' ) { 00779 $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped(); 00780 $this->mNavigationBar .= $extra; 00781 } 00782 00783 return $this->mNavigationBar; 00784 } 00785 00794 protected function getOrderTypeMessages() { 00795 return null; 00796 } 00797 } 00798 00803 abstract class ReverseChronologicalPager extends IndexPager { 00804 public $mDefaultDirection = true; 00805 public $mYear; 00806 public $mMonth; 00807 00808 function getNavigationBar() { 00809 if ( !$this->isNavigationBarShown() ) { 00810 return ''; 00811 } 00812 00813 if ( isset( $this->mNavigationBar ) ) { 00814 return $this->mNavigationBar; 00815 } 00816 00817 $linkTexts = array( 00818 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(), 00819 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(), 00820 'first' => $this->msg( 'histlast' )->escaped(), 00821 'last' => $this->msg( 'histfirst' )->escaped() 00822 ); 00823 00824 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00825 $limitLinks = $this->getLimitLinks(); 00826 $limits = $this->getLanguage()->pipeList( $limitLinks ); 00827 $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" . 00828 $this->msg( 'pipe-separator' )->escaped() . 00829 "{$pagingLinks['last']}" )->escaped(); 00830 00831 $this->mNavigationBar = $firstLastLinks . ' ' . 00832 $this->msg( 'viewprevnext' )->rawParams( 00833 $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped(); 00834 00835 return $this->mNavigationBar; 00836 } 00837 00838 function getDateCond( $year, $month ) { 00839 $year = intval( $year ); 00840 $month = intval( $month ); 00841 00842 // Basic validity checks 00843 $this->mYear = $year > 0 ? $year : false; 00844 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false; 00845 00846 // Given an optional year and month, we need to generate a timestamp 00847 // to use as "WHERE rev_timestamp <= result" 00848 // Examples: year = 2006 equals < 20070101 (+000000) 00849 // year=2005, month=1 equals < 20050201 00850 // year=2005, month=12 equals < 20060101 00851 if ( !$this->mYear && !$this->mMonth ) { 00852 return; 00853 } 00854 00855 if ( $this->mYear ) { 00856 $year = $this->mYear; 00857 } else { 00858 // If no year given, assume the current one 00859 $year = gmdate( 'Y' ); 00860 // If this month hasn't happened yet this year, go back to last year's month 00861 if( $this->mMonth > gmdate( 'n' ) ) { 00862 $year--; 00863 } 00864 } 00865 00866 if ( $this->mMonth ) { 00867 $month = $this->mMonth + 1; 00868 // For December, we want January 1 of the next year 00869 if ($month > 12) { 00870 $month = 1; 00871 $year++; 00872 } 00873 } else { 00874 // No month implies we want up to the end of the year in question 00875 $month = 1; 00876 $year++; 00877 } 00878 00879 // Y2K38 bug 00880 if ( $year > 2032 ) { 00881 $year = 2032; 00882 } 00883 00884 $ymd = (int)sprintf( "%04d%02d01", $year, $month ); 00885 00886 if ( $ymd > 20320101 ) { 00887 $ymd = 20320101; 00888 } 00889 00890 $this->mOffset = $this->mDb->timestamp( "${ymd}000000" ); 00891 } 00892 } 00893 00898 abstract class TablePager extends IndexPager { 00899 var $mSort; 00900 var $mCurrentRow; 00901 00902 public function __construct( IContextSource $context = null ) { 00903 if ( $context ) { 00904 $this->setContext( $context ); 00905 } 00906 00907 $this->mSort = $this->getRequest()->getText( 'sort' ); 00908 if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) { 00909 $this->mSort = $this->getDefaultSort(); 00910 } 00911 if ( $this->getRequest()->getBool( 'asc' ) ) { 00912 $this->mDefaultDirection = false; 00913 } elseif ( $this->getRequest()->getBool( 'desc' ) ) { 00914 $this->mDefaultDirection = true; 00915 } /* Else leave it at whatever the class default is */ 00916 00917 parent::__construct(); 00918 } 00919 00924 function getStartBody() { 00925 global $wgStylePath; 00926 $tableClass = htmlspecialchars( $this->getTableClass() ); 00927 $sortClass = htmlspecialchars( $this->getSortHeaderClass() ); 00928 00929 $s = "<table style='border:1px;' class=\"mw-datatable $tableClass\"><thead><tr>\n"; 00930 $fields = $this->getFieldNames(); 00931 00932 # Make table header 00933 foreach ( $fields as $field => $name ) { 00934 if ( strval( $name ) == '' ) { 00935 $s .= "<th> </th>\n"; 00936 } elseif ( $this->isFieldSortable( $field ) ) { 00937 $query = array( 'sort' => $field, 'limit' => $this->mLimit ); 00938 if ( $field == $this->mSort ) { 00939 # This is the sorted column 00940 # Prepare a link that goes in the other sort order 00941 if ( $this->mDefaultDirection ) { 00942 # Descending 00943 $image = 'Arr_d.png'; 00944 $query['asc'] = '1'; 00945 $query['desc'] = ''; 00946 $alt = $this->msg( 'descending_abbrev' )->escaped(); 00947 } else { 00948 # Ascending 00949 $image = 'Arr_u.png'; 00950 $query['asc'] = ''; 00951 $query['desc'] = '1'; 00952 $alt = $this->msg( 'ascending_abbrev' )->escaped(); 00953 } 00954 $image = htmlspecialchars( "$wgStylePath/common/images/$image" ); 00955 $link = $this->makeLink( 00956 "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" . 00957 htmlspecialchars( $name ), $query ); 00958 $s .= "<th class=\"$sortClass\">$link</th>\n"; 00959 } else { 00960 $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n"; 00961 } 00962 } else { 00963 $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n"; 00964 } 00965 } 00966 $s .= "</tr></thead><tbody>\n"; 00967 return $s; 00968 } 00969 00974 function getEndBody() { 00975 return "</tbody></table>\n"; 00976 } 00977 00982 function getEmptyBody() { 00983 $colspan = count( $this->getFieldNames() ); 00984 $msgEmpty = $this->msg( 'table_pager_empty' )->escaped(); 00985 return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n"; 00986 } 00987 00993 function formatRow( $row ) { 00994 $this->mCurrentRow = $row; // In case formatValue etc need to know 00995 $s = Xml::openElement( 'tr', $this->getRowAttrs( $row ) ); 00996 $fieldNames = $this->getFieldNames(); 00997 00998 foreach ( $fieldNames as $field => $name ) { 00999 $value = isset( $row->$field ) ? $row->$field : null; 01000 $formatted = strval( $this->formatValue( $field, $value ) ); 01001 01002 if ( $formatted == '' ) { 01003 $formatted = ' '; 01004 } 01005 01006 $s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted ); 01007 } 01008 01009 $s .= "</tr>\n"; 01010 01011 return $s; 01012 } 01013 01022 function getRowClass( $row ) { 01023 return ''; 01024 } 01025 01034 function getRowAttrs( $row ) { 01035 $class = $this->getRowClass( $row ); 01036 if ( $class === '' ) { 01037 // Return an empty array to avoid clutter in HTML like class="" 01038 return array(); 01039 } else { 01040 return array( 'class' => $this->getRowClass( $row ) ); 01041 } 01042 } 01043 01055 function getCellAttrs( $field, $value ) { 01056 return array( 'class' => 'TablePager_col_' . $field ); 01057 } 01058 01063 function getIndexField() { 01064 return $this->mSort; 01065 } 01066 01071 function getTableClass() { 01072 return 'TablePager'; 01073 } 01074 01079 function getNavClass() { 01080 return 'TablePager_nav'; 01081 } 01082 01087 function getSortHeaderClass() { 01088 return 'TablePager_sort'; 01089 } 01090 01095 public function getNavigationBar() { 01096 global $wgStylePath; 01097 01098 if ( !$this->isNavigationBarShown() ) { 01099 return ''; 01100 } 01101 01102 $path = "$wgStylePath/common/images"; 01103 $labels = array( 01104 'first' => 'table_pager_first', 01105 'prev' => 'table_pager_prev', 01106 'next' => 'table_pager_next', 01107 'last' => 'table_pager_last', 01108 ); 01109 $images = array( 01110 'first' => 'arrow_first_25.png', 01111 'prev' => 'arrow_left_25.png', 01112 'next' => 'arrow_right_25.png', 01113 'last' => 'arrow_last_25.png', 01114 ); 01115 $disabledImages = array( 01116 'first' => 'arrow_disabled_first_25.png', 01117 'prev' => 'arrow_disabled_left_25.png', 01118 'next' => 'arrow_disabled_right_25.png', 01119 'last' => 'arrow_disabled_last_25.png', 01120 ); 01121 if( $this->getLanguage()->isRTL() ) { 01122 $keys = array_keys( $labels ); 01123 $images = array_combine( $keys, array_reverse( $images ) ); 01124 $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) ); 01125 } 01126 01127 $linkTexts = array(); 01128 $disabledTexts = array(); 01129 foreach ( $labels as $type => $label ) { 01130 $msgLabel = $this->msg( $label )->escaped(); 01131 $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel"; 01132 $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel"; 01133 } 01134 $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); 01135 01136 $navClass = htmlspecialchars( $this->getNavClass() ); 01137 $s = "<table class=\"$navClass\"><tr>\n"; 01138 $width = 100 / count( $links ) . '%'; 01139 foreach ( $labels as $type => $label ) { 01140 $s .= "<td style='width:$width;'>{$links[$type]}</td>\n"; 01141 } 01142 $s .= "</tr></table>\n"; 01143 return $s; 01144 } 01145 01151 public function getLimitSelect() { 01152 # Add the current limit from the query string 01153 # to avoid that the limit is lost after clicking Go next time 01154 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) { 01155 $this->mLimitsShown[] = $this->mLimit; 01156 sort( $this->mLimitsShown ); 01157 } 01158 $s = Html::openElement( 'select', array( 'name' => 'limit' ) ) . "\n"; 01159 foreach ( $this->mLimitsShown as $key => $value ) { 01160 # The pair is either $index => $limit, in which case the $value 01161 # will be numeric, or $limit => $text, in which case the $value 01162 # will be a string. 01163 if( is_int( $value ) ){ 01164 $limit = $value; 01165 $text = $this->getLanguage()->formatNum( $limit ); 01166 } else { 01167 $limit = $key; 01168 $text = $value; 01169 } 01170 $s .= Xml::option( $text, $limit, $limit == $this->mLimit ) . "\n"; 01171 } 01172 $s .= Html::closeElement( 'select' ); 01173 return $s; 01174 } 01175 01184 function getHiddenFields( $blacklist = array() ) { 01185 $blacklist = (array)$blacklist; 01186 $query = $this->getRequest()->getQueryValues(); 01187 foreach ( $blacklist as $name ) { 01188 unset( $query[$name] ); 01189 } 01190 $s = ''; 01191 foreach ( $query as $name => $value ) { 01192 $encName = htmlspecialchars( $name ); 01193 $encValue = htmlspecialchars( $value ); 01194 $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n"; 01195 } 01196 return $s; 01197 } 01198 01204 function getLimitForm() { 01205 global $wgScript; 01206 01207 return Xml::openElement( 01208 'form', 01209 array( 01210 'method' => 'get', 01211 'action' => $wgScript 01212 ) ) . "\n" . $this->getLimitDropdown() . "</form>\n"; 01213 } 01214 01220 function getLimitDropdown() { 01221 # Make the select with some explanatory text 01222 $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped(); 01223 01224 return $this->msg( 'table_pager_limit' ) 01225 ->rawParams( $this->getLimitSelect() )->escaped() . 01226 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" . 01227 $this->getHiddenFields( array( 'limit' ) ); 01228 } 01229 01236 abstract function isFieldSortable( $field ); 01237 01250 abstract function formatValue( $name, $value ); 01251 01259 abstract function getDefaultSort(); 01260 01268 abstract function getFieldNames(); 01269 }