MediaWiki
REL1_19
|
00001 <?php 00013 interface Pager { 00014 function getNavigationBar(); 00015 function getBody(); 00016 } 00017 00060 abstract class IndexPager extends ContextSource implements Pager { 00061 public $mRequest; 00062 public $mLimitsShown = array( 20, 50, 100, 250, 500 ); 00063 public $mDefaultLimit = 50; 00064 public $mOffset, $mLimit; 00065 public $mQueryDone = false; 00066 public $mDb; 00067 public $mPastTheEndRow; 00068 00073 protected $mIndexField; 00078 protected $mExtraSortFields; 00081 protected $mOrderType; 00093 public $mDefaultDirection; 00094 public $mIsBackwards; 00095 00097 public $mIsFirst; 00098 public $mIsLast; 00099 00100 protected $mLastShown, $mFirstShown, $mPastTheEndIndex, $mDefaultQuery, $mNavigationBar; 00101 00107 public $mResult; 00108 00109 public function __construct( IContextSource $context = null ) { 00110 if ( $context ) { 00111 $this->setContext( $context ); 00112 } 00113 00114 $this->mRequest = $this->getRequest(); 00115 00116 # NB: the offset is quoted, not validated. It is treated as an 00117 # arbitrary string to support the widest variety of index types. Be 00118 # careful outputting it into HTML! 00119 $this->mOffset = $this->mRequest->getText( 'offset' ); 00120 00121 # Use consistent behavior for the limit options 00122 $this->mDefaultLimit = intval( $this->getUser()->getOption( 'rclimit' ) ); 00123 list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset(); 00124 00125 $this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' ); 00126 $this->mDb = wfGetDB( DB_SLAVE ); 00127 00128 $index = $this->getIndexField(); // column to sort on 00129 $extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning 00130 $order = $this->mRequest->getVal( 'order' ); 00131 if( is_array( $index ) && isset( $index[$order] ) ) { 00132 $this->mOrderType = $order; 00133 $this->mIndexField = $index[$order]; 00134 $this->mExtraSortFields = isset( $extraSort[$order] ) 00135 ? (array)$extraSort[$order] 00136 : array(); 00137 } elseif( is_array( $index ) ) { 00138 # First element is the default 00139 reset( $index ); 00140 list( $this->mOrderType, $this->mIndexField ) = each( $index ); 00141 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] ) 00142 ? (array)$extraSort[$this->mOrderType] 00143 : array(); 00144 } else { 00145 # $index is not an array 00146 $this->mOrderType = null; 00147 $this->mIndexField = $index; 00148 $this->mExtraSortFields = (array)$extraSort; 00149 } 00150 00151 if( !isset( $this->mDefaultDirection ) ) { 00152 $dir = $this->getDefaultDirections(); 00153 $this->mDefaultDirection = is_array( $dir ) 00154 ? $dir[$this->mOrderType] 00155 : $dir; 00156 } 00157 } 00158 00164 public function doQuery() { 00165 # Use the child class name for profiling 00166 $fname = __METHOD__ . ' (' . get_class( $this ) . ')'; 00167 wfProfileIn( $fname ); 00168 00169 $descending = ( $this->mIsBackwards == $this->mDefaultDirection ); 00170 # Plus an extra row so that we can tell the "next" link should be shown 00171 $queryLimit = $this->mLimit + 1; 00172 00173 $this->mResult = $this->reallyDoQuery( 00174 $this->mOffset, 00175 $queryLimit, 00176 $descending 00177 ); 00178 $this->extractResultInfo( $this->mOffset, $queryLimit, $this->mResult ); 00179 $this->mQueryDone = true; 00180 00181 $this->preprocessResults( $this->mResult ); 00182 $this->mResult->rewind(); // Paranoia 00183 00184 wfProfileOut( $fname ); 00185 } 00186 00190 function getResult() { 00191 return $this->mResult; 00192 } 00193 00199 function setOffset( $offset ) { 00200 $this->mOffset = $offset; 00201 } 00207 function setLimit( $limit ) { 00208 $this->mLimit = $limit; 00209 } 00210 00219 function extractResultInfo( $offset, $limit, ResultWrapper $res ) { 00220 $numRows = $res->numRows(); 00221 if ( $numRows ) { 00222 # Remove any table prefix from index field 00223 $parts = explode( '.', $this->mIndexField ); 00224 $indexColumn = end( $parts ); 00225 00226 $row = $res->fetchRow(); 00227 $firstIndex = $row[$indexColumn]; 00228 00229 # Discard the extra result row if there is one 00230 if ( $numRows > $this->mLimit && $numRows > 1 ) { 00231 $res->seek( $numRows - 1 ); 00232 $this->mPastTheEndRow = $res->fetchObject(); 00233 $indexField = $this->mIndexField; 00234 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexField; 00235 $res->seek( $numRows - 2 ); 00236 $row = $res->fetchRow(); 00237 $lastIndex = $row[$indexColumn]; 00238 } else { 00239 $this->mPastTheEndRow = null; 00240 # Setting indexes to an empty string means that they will be 00241 # omitted if they would otherwise appear in URLs. It just so 00242 # happens that this is the right thing to do in the standard 00243 # UI, in all the relevant cases. 00244 $this->mPastTheEndIndex = ''; 00245 $res->seek( $numRows - 1 ); 00246 $row = $res->fetchRow(); 00247 $lastIndex = $row[$indexColumn]; 00248 } 00249 } else { 00250 $firstIndex = ''; 00251 $lastIndex = ''; 00252 $this->mPastTheEndRow = null; 00253 $this->mPastTheEndIndex = ''; 00254 } 00255 00256 if ( $this->mIsBackwards ) { 00257 $this->mIsFirst = ( $numRows < $limit ); 00258 $this->mIsLast = ( $offset == '' ); 00259 $this->mLastShown = $firstIndex; 00260 $this->mFirstShown = $lastIndex; 00261 } else { 00262 $this->mIsFirst = ( $offset == '' ); 00263 $this->mIsLast = ( $numRows < $limit ); 00264 $this->mLastShown = $lastIndex; 00265 $this->mFirstShown = $firstIndex; 00266 } 00267 } 00268 00274 function getSqlComment() { 00275 return get_class( $this ); 00276 } 00277 00287 function reallyDoQuery( $offset, $limit, $descending ) { 00288 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')'; 00289 $info = $this->getQueryInfo(); 00290 $tables = $info['tables']; 00291 $fields = $info['fields']; 00292 $conds = isset( $info['conds'] ) ? $info['conds'] : array(); 00293 $options = isset( $info['options'] ) ? $info['options'] : array(); 00294 $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array(); 00295 $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields ); 00296 if ( $descending ) { 00297 $options['ORDER BY'] = implode( ',', $sortColumns ); 00298 $operator = '>'; 00299 } else { 00300 $orderBy = array(); 00301 foreach ( $sortColumns as $col ) { 00302 $orderBy[] = $col . ' DESC'; 00303 } 00304 $options['ORDER BY'] = implode( ',', $orderBy ); 00305 $operator = '<'; 00306 } 00307 if ( $offset != '' ) { 00308 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset ); 00309 } 00310 $options['LIMIT'] = intval( $limit ); 00311 $res = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00312 return new ResultWrapper( $this->mDb, $res ); 00313 } 00314 00320 protected function preprocessResults( $result ) {} 00321 00328 public function getBody() { 00329 if ( !$this->mQueryDone ) { 00330 $this->doQuery(); 00331 } 00332 00333 if ( $this->mResult->numRows() ) { 00334 # Do any special query batches before display 00335 $this->doBatchLookups(); 00336 } 00337 00338 # Don't use any extra rows returned by the query 00339 $numRows = min( $this->mResult->numRows(), $this->mLimit ); 00340 00341 $s = $this->getStartBody(); 00342 if ( $numRows ) { 00343 if ( $this->mIsBackwards ) { 00344 for ( $i = $numRows - 1; $i >= 0; $i-- ) { 00345 $this->mResult->seek( $i ); 00346 $row = $this->mResult->fetchObject(); 00347 $s .= $this->formatRow( $row ); 00348 } 00349 } else { 00350 $this->mResult->seek( 0 ); 00351 for ( $i = 0; $i < $numRows; $i++ ) { 00352 $row = $this->mResult->fetchObject(); 00353 $s .= $this->formatRow( $row ); 00354 } 00355 } 00356 } else { 00357 $s .= $this->getEmptyBody(); 00358 } 00359 $s .= $this->getEndBody(); 00360 return $s; 00361 } 00362 00371 function makeLink($text, $query = null, $type=null) { 00372 if ( $query === null ) { 00373 return $text; 00374 } 00375 00376 $attrs = array(); 00377 if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { 00378 # HTML5 rel attributes 00379 $attrs['rel'] = $type; 00380 } 00381 00382 if( $type ) { 00383 $attrs['class'] = "mw-{$type}link"; 00384 } 00385 return Linker::linkKnown( 00386 $this->getTitle(), 00387 $text, 00388 $attrs, 00389 $query + $this->getDefaultQuery() 00390 ); 00391 } 00392 00400 protected function doBatchLookups() {} 00401 00408 protected function getStartBody() { 00409 return ''; 00410 } 00411 00417 protected function getEndBody() { 00418 return ''; 00419 } 00420 00427 protected function getEmptyBody() { 00428 return ''; 00429 } 00430 00438 function getDefaultQuery() { 00439 if ( !isset( $this->mDefaultQuery ) ) { 00440 $this->mDefaultQuery = $this->getRequest()->getQueryValues(); 00441 unset( $this->mDefaultQuery['title'] ); 00442 unset( $this->mDefaultQuery['dir'] ); 00443 unset( $this->mDefaultQuery['offset'] ); 00444 unset( $this->mDefaultQuery['limit'] ); 00445 unset( $this->mDefaultQuery['order'] ); 00446 unset( $this->mDefaultQuery['month'] ); 00447 unset( $this->mDefaultQuery['year'] ); 00448 } 00449 return $this->mDefaultQuery; 00450 } 00451 00457 function getNumRows() { 00458 if ( !$this->mQueryDone ) { 00459 $this->doQuery(); 00460 } 00461 return $this->mResult->numRows(); 00462 } 00463 00469 function getPagingQueries() { 00470 if ( !$this->mQueryDone ) { 00471 $this->doQuery(); 00472 } 00473 00474 # Don't announce the limit everywhere if it's the default 00475 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit; 00476 00477 if ( $this->mIsFirst ) { 00478 $prev = false; 00479 $first = false; 00480 } else { 00481 $prev = array( 00482 'dir' => 'prev', 00483 'offset' => $this->mFirstShown, 00484 'limit' => $urlLimit 00485 ); 00486 $first = array( 'limit' => $urlLimit ); 00487 } 00488 if ( $this->mIsLast ) { 00489 $next = false; 00490 $last = false; 00491 } else { 00492 $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); 00493 $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); 00494 } 00495 return array( 00496 'prev' => $prev, 00497 'next' => $next, 00498 'first' => $first, 00499 'last' => $last 00500 ); 00501 } 00502 00508 function isNavigationBarShown() { 00509 if ( !$this->mQueryDone ) { 00510 $this->doQuery(); 00511 } 00512 // Hide navigation by default if there is nothing to page 00513 return !($this->mIsFirst && $this->mIsLast); 00514 } 00515 00526 function getPagingLinks( $linkTexts, $disabledTexts = array() ) { 00527 $queries = $this->getPagingQueries(); 00528 $links = array(); 00529 foreach ( $queries as $type => $query ) { 00530 if ( $query !== false ) { 00531 $links[$type] = $this->makeLink( 00532 $linkTexts[$type], 00533 $queries[$type], 00534 $type 00535 ); 00536 } elseif ( isset( $disabledTexts[$type] ) ) { 00537 $links[$type] = $disabledTexts[$type]; 00538 } else { 00539 $links[$type] = $linkTexts[$type]; 00540 } 00541 } 00542 return $links; 00543 } 00544 00545 function getLimitLinks() { 00546 $links = array(); 00547 if ( $this->mIsBackwards ) { 00548 $offset = $this->mPastTheEndIndex; 00549 } else { 00550 $offset = $this->mOffset; 00551 } 00552 foreach ( $this->mLimitsShown as $limit ) { 00553 $links[] = $this->makeLink( 00554 $this->getLanguage()->formatNum( $limit ), 00555 array( 'offset' => $offset, 'limit' => $limit ), 00556 'num' 00557 ); 00558 } 00559 return $links; 00560 } 00561 00570 abstract function formatRow( $row ); 00571 00584 abstract function getQueryInfo(); 00585 00598 abstract function getIndexField(); 00599 00616 protected function getExtraSortFields() { return array(); } 00617 00637 protected function getDefaultDirections() { return false; } 00638 } 00639 00640 00645 abstract class AlphabeticPager extends IndexPager { 00646 00653 function getNavigationBar() { 00654 if ( !$this->isNavigationBarShown() ) return ''; 00655 00656 if( isset( $this->mNavigationBar ) ) { 00657 return $this->mNavigationBar; 00658 } 00659 00660 $lang = $this->getLanguage(); 00661 00662 $opts = array( 'parsemag', 'escapenoentities' ); 00663 $linkTexts = array( 00664 'prev' => wfMsgExt( 00665 'prevn', 00666 $opts, 00667 $lang->formatNum( $this->mLimit ) 00668 ), 00669 'next' => wfMsgExt( 00670 'nextn', 00671 $opts, 00672 $lang->formatNum($this->mLimit ) 00673 ), 00674 'first' => wfMsgExt( 'page_first', $opts ), 00675 'last' => wfMsgExt( 'page_last', $opts ) 00676 ); 00677 00678 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00679 $limitLinks = $this->getLimitLinks(); 00680 $limits = $lang->pipeList( $limitLinks ); 00681 00682 $this->mNavigationBar = 00683 "(" . $lang->pipeList( 00684 array( $pagingLinks['first'], 00685 $pagingLinks['last'] ) 00686 ) . ") " . 00687 wfMsgHtml( 'viewprevnext', $pagingLinks['prev'], 00688 $pagingLinks['next'], $limits ); 00689 00690 if( !is_array( $this->getIndexField() ) ) { 00691 # Early return to avoid undue nesting 00692 return $this->mNavigationBar; 00693 } 00694 00695 $extra = ''; 00696 $first = true; 00697 $msgs = $this->getOrderTypeMessages(); 00698 foreach( array_keys( $msgs ) as $order ) { 00699 if( $first ) { 00700 $first = false; 00701 } else { 00702 $extra .= wfMsgExt( 'pipe-separator' , 'escapenoentities' ); 00703 } 00704 00705 if( $order == $this->mOrderType ) { 00706 $extra .= wfMsgHTML( $msgs[$order] ); 00707 } else { 00708 $extra .= $this->makeLink( 00709 wfMsgHTML( $msgs[$order] ), 00710 array( 'order' => $order ) 00711 ); 00712 } 00713 } 00714 00715 if( $extra !== '' ) { 00716 $this->mNavigationBar .= " ($extra)"; 00717 } 00718 00719 return $this->mNavigationBar; 00720 } 00721 00730 protected function getOrderTypeMessages() { 00731 return null; 00732 } 00733 } 00734 00739 abstract class ReverseChronologicalPager extends IndexPager { 00740 public $mDefaultDirection = true; 00741 public $mYear; 00742 public $mMonth; 00743 00744 function getNavigationBar() { 00745 if ( !$this->isNavigationBarShown() ) { 00746 return ''; 00747 } 00748 00749 if ( isset( $this->mNavigationBar ) ) { 00750 return $this->mNavigationBar; 00751 } 00752 00753 $nicenumber = $this->getLanguage()->formatNum( $this->mLimit ); 00754 $linkTexts = array( 00755 'prev' => wfMsgExt( 00756 'pager-newer-n', 00757 array( 'parsemag', 'escape' ), 00758 $nicenumber 00759 ), 00760 'next' => wfMsgExt( 00761 'pager-older-n', 00762 array( 'parsemag', 'escape' ), 00763 $nicenumber 00764 ), 00765 'first' => wfMsgHtml( 'histlast' ), 00766 'last' => wfMsgHtml( 'histfirst' ) 00767 ); 00768 00769 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00770 $limitLinks = $this->getLimitLinks(); 00771 $limits = $this->getLanguage()->pipeList( $limitLinks ); 00772 00773 $this->mNavigationBar = "({$pagingLinks['first']}" . 00774 wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . 00775 "{$pagingLinks['last']}) " . 00776 wfMsgHTML( 00777 'viewprevnext', 00778 $pagingLinks['prev'], $pagingLinks['next'], 00779 $limits 00780 ); 00781 return $this->mNavigationBar; 00782 } 00783 00784 function getDateCond( $year, $month ) { 00785 $year = intval( $year ); 00786 $month = intval( $month ); 00787 // Basic validity checks 00788 $this->mYear = $year > 0 ? $year : false; 00789 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false; 00790 // Given an optional year and month, we need to generate a timestamp 00791 // to use as "WHERE rev_timestamp <= result" 00792 // Examples: year = 2006 equals < 20070101 (+000000) 00793 // year=2005, month=1 equals < 20050201 00794 // year=2005, month=12 equals < 20060101 00795 if ( !$this->mYear && !$this->mMonth ) { 00796 return; 00797 } 00798 if ( $this->mYear ) { 00799 $year = $this->mYear; 00800 } else { 00801 // If no year given, assume the current one 00802 $year = gmdate( 'Y' ); 00803 // If this month hasn't happened yet this year, go back to last year's month 00804 if( $this->mMonth > gmdate( 'n' ) ) { 00805 $year--; 00806 } 00807 } 00808 if ( $this->mMonth ) { 00809 $month = $this->mMonth + 1; 00810 // For December, we want January 1 of the next year 00811 if ($month > 12) { 00812 $month = 1; 00813 $year++; 00814 } 00815 } else { 00816 // No month implies we want up to the end of the year in question 00817 $month = 1; 00818 $year++; 00819 } 00820 // Y2K38 bug 00821 if ( $year > 2032 ) { 00822 $year = 2032; 00823 } 00824 $ymd = (int)sprintf( "%04d%02d01", $year, $month ); 00825 if ( $ymd > 20320101 ) { 00826 $ymd = 20320101; 00827 } 00828 $this->mOffset = $this->mDb->timestamp( "${ymd}000000" ); 00829 } 00830 } 00831 00836 abstract class TablePager extends IndexPager { 00837 var $mSort; 00838 var $mCurrentRow; 00839 00840 function __construct( IContextSource $context = null ) { 00841 if ( $context ) { 00842 $this->setContext( $context ); 00843 } 00844 00845 $this->mSort = $this->getRequest()->getText( 'sort' ); 00846 if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) { 00847 $this->mSort = $this->getDefaultSort(); 00848 } 00849 if ( $this->getRequest()->getBool( 'asc' ) ) { 00850 $this->mDefaultDirection = false; 00851 } elseif ( $this->getRequest()->getBool( 'desc' ) ) { 00852 $this->mDefaultDirection = true; 00853 } /* Else leave it at whatever the class default is */ 00854 00855 parent::__construct(); 00856 } 00857 00858 function getStartBody() { 00859 global $wgStylePath; 00860 $tableClass = htmlspecialchars( $this->getTableClass() ); 00861 $sortClass = htmlspecialchars( $this->getSortHeaderClass() ); 00862 00863 $s = "<table style='border:1;' class=\"mw-datatable $tableClass\"><thead><tr>\n"; 00864 $fields = $this->getFieldNames(); 00865 00866 # Make table header 00867 foreach ( $fields as $field => $name ) { 00868 if ( strval( $name ) == '' ) { 00869 $s .= "<th> </th>\n"; 00870 } elseif ( $this->isFieldSortable( $field ) ) { 00871 $query = array( 'sort' => $field, 'limit' => $this->mLimit ); 00872 if ( $field == $this->mSort ) { 00873 # This is the sorted column 00874 # Prepare a link that goes in the other sort order 00875 if ( $this->mDefaultDirection ) { 00876 # Descending 00877 $image = 'Arr_d.png'; 00878 $query['asc'] = '1'; 00879 $query['desc'] = ''; 00880 $alt = htmlspecialchars( wfMsg( 'descending_abbrev' ) ); 00881 } else { 00882 # Ascending 00883 $image = 'Arr_u.png'; 00884 $query['asc'] = ''; 00885 $query['desc'] = '1'; 00886 $alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) ); 00887 } 00888 $image = htmlspecialchars( "$wgStylePath/common/images/$image" ); 00889 $link = $this->makeLink( 00890 "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" . 00891 htmlspecialchars( $name ), $query ); 00892 $s .= "<th class=\"$sortClass\">$link</th>\n"; 00893 } else { 00894 $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n"; 00895 } 00896 } else { 00897 $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n"; 00898 } 00899 } 00900 $s .= "</tr></thead><tbody>\n"; 00901 return $s; 00902 } 00903 00904 function getEndBody() { 00905 return "</tbody></table>\n"; 00906 } 00907 00908 function getEmptyBody() { 00909 $colspan = count( $this->getFieldNames() ); 00910 $msgEmpty = wfMsgHtml( 'table_pager_empty' ); 00911 return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n"; 00912 } 00913 00918 function formatRow( $row ) { 00919 $this->mCurrentRow = $row; # In case formatValue etc need to know 00920 $s = Xml::openElement( 'tr', $this->getRowAttrs( $row ) ); 00921 $fieldNames = $this->getFieldNames(); 00922 foreach ( $fieldNames as $field => $name ) { 00923 $value = isset( $row->$field ) ? $row->$field : null; 00924 $formatted = strval( $this->formatValue( $field, $value ) ); 00925 if ( $formatted == '' ) { 00926 $formatted = ' '; 00927 } 00928 $s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted ); 00929 } 00930 $s .= "</tr>\n"; 00931 return $s; 00932 } 00933 00940 function getRowClass( $row ) { 00941 return ''; 00942 } 00943 00950 function getRowAttrs( $row ) { 00951 $class = $this->getRowClass( $row ); 00952 if ( $class === '' ) { 00953 // Return an empty array to avoid clutter in HTML like class="" 00954 return array(); 00955 } else { 00956 return array( 'class' => $this->getRowClass( $row ) ); 00957 } 00958 } 00959 00969 function getCellAttrs( $field, $value ) { 00970 return array( 'class' => 'TablePager_col_' . $field ); 00971 } 00972 00973 function getIndexField() { 00974 return $this->mSort; 00975 } 00976 00977 function getTableClass() { 00978 return 'TablePager'; 00979 } 00980 00981 function getNavClass() { 00982 return 'TablePager_nav'; 00983 } 00984 00985 function getSortHeaderClass() { 00986 return 'TablePager_sort'; 00987 } 00988 00993 function getNavigationBar() { 00994 global $wgStylePath; 00995 00996 if ( !$this->isNavigationBarShown() ) { 00997 return ''; 00998 } 00999 01000 $path = "$wgStylePath/common/images"; 01001 $labels = array( 01002 'first' => 'table_pager_first', 01003 'prev' => 'table_pager_prev', 01004 'next' => 'table_pager_next', 01005 'last' => 'table_pager_last', 01006 ); 01007 $images = array( 01008 'first' => 'arrow_first_25.png', 01009 'prev' => 'arrow_left_25.png', 01010 'next' => 'arrow_right_25.png', 01011 'last' => 'arrow_last_25.png', 01012 ); 01013 $disabledImages = array( 01014 'first' => 'arrow_disabled_first_25.png', 01015 'prev' => 'arrow_disabled_left_25.png', 01016 'next' => 'arrow_disabled_right_25.png', 01017 'last' => 'arrow_disabled_last_25.png', 01018 ); 01019 if( $this->getLanguage()->isRTL() ) { 01020 $keys = array_keys( $labels ); 01021 $images = array_combine( $keys, array_reverse( $images ) ); 01022 $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) ); 01023 } 01024 01025 $linkTexts = array(); 01026 $disabledTexts = array(); 01027 foreach ( $labels as $type => $label ) { 01028 $msgLabel = wfMsgHtml( $label ); 01029 $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel"; 01030 $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel"; 01031 } 01032 $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); 01033 01034 $navClass = htmlspecialchars( $this->getNavClass() ); 01035 $s = "<table class=\"$navClass\"><tr>\n"; 01036 $width = 100 / count( $links ) . '%'; 01037 foreach ( $labels as $type => $label ) { 01038 $s .= "<td style='width:$width;'>{$links[$type]}</td>\n"; 01039 } 01040 $s .= "</tr></table>\n"; 01041 return $s; 01042 } 01043 01049 function getLimitSelect() { 01050 # Add the current limit from the query string 01051 # to avoid that the limit is lost after clicking Go next time 01052 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) { 01053 $this->mLimitsShown[] = $this->mLimit; 01054 sort( $this->mLimitsShown ); 01055 } 01056 $s = Html::openElement( 'select', array( 'name' => 'limit' ) ) . "\n"; 01057 foreach ( $this->mLimitsShown as $key => $value ) { 01058 # The pair is either $index => $limit, in which case the $value 01059 # will be numeric, or $limit => $text, in which case the $value 01060 # will be a string. 01061 if( is_int( $value ) ){ 01062 $limit = $value; 01063 $text = $this->getLanguage()->formatNum( $limit ); 01064 } else { 01065 $limit = $key; 01066 $text = $value; 01067 } 01068 $s .= Xml::option( $text, $limit, $limit == $this->mLimit ) . "\n"; 01069 } 01070 $s .= Html::closeElement( 'select' ); 01071 return $s; 01072 } 01073 01082 function getHiddenFields( $blacklist = array() ) { 01083 $blacklist = (array)$blacklist; 01084 $query = $this->getRequest()->getQueryValues(); 01085 foreach ( $blacklist as $name ) { 01086 unset( $query[$name] ); 01087 } 01088 $s = ''; 01089 foreach ( $query as $name => $value ) { 01090 $encName = htmlspecialchars( $name ); 01091 $encValue = htmlspecialchars( $value ); 01092 $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n"; 01093 } 01094 return $s; 01095 } 01096 01102 function getLimitForm() { 01103 global $wgScript; 01104 01105 return Xml::openElement( 01106 'form', 01107 array( 01108 'method' => 'get', 01109 'action' => $wgScript 01110 ) ) . "\n" . $this->getLimitDropdown() . "</form>\n"; 01111 } 01112 01118 function getLimitDropdown() { 01119 # Make the select with some explanatory text 01120 $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' ); 01121 01122 return wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) . 01123 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" . 01124 $this->getHiddenFields( array( 'limit' ) ); 01125 } 01126 01133 abstract function isFieldSortable( $field ); 01134 01145 abstract function formatValue( $name, $value ); 01146 01150 abstract function getDefaultSort(); 01151 01159 abstract function getFieldNames(); 01160 }