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