MediaWiki
REL1_22
|
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 # Let the subclass set the DB here; otherwise use a slave DB for the current wiki 00154 $this->mDb = $this->mDb ?: wfGetDB( DB_SLAVE ); 00155 00156 $index = $this->getIndexField(); // column to sort on 00157 $extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning 00158 $order = $this->mRequest->getVal( 'order' ); 00159 if ( is_array( $index ) && isset( $index[$order] ) ) { 00160 $this->mOrderType = $order; 00161 $this->mIndexField = $index[$order]; 00162 $this->mExtraSortFields = isset( $extraSort[$order] ) 00163 ? (array)$extraSort[$order] 00164 : array(); 00165 } elseif ( is_array( $index ) ) { 00166 # First element is the default 00167 reset( $index ); 00168 list( $this->mOrderType, $this->mIndexField ) = each( $index ); 00169 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] ) 00170 ? (array)$extraSort[$this->mOrderType] 00171 : array(); 00172 } else { 00173 # $index is not an array 00174 $this->mOrderType = null; 00175 $this->mIndexField = $index; 00176 $this->mExtraSortFields = (array)$extraSort; 00177 } 00178 00179 if ( !isset( $this->mDefaultDirection ) ) { 00180 $dir = $this->getDefaultDirections(); 00181 $this->mDefaultDirection = is_array( $dir ) 00182 ? $dir[$this->mOrderType] 00183 : $dir; 00184 } 00185 } 00186 00192 public function getDatabase() { 00193 return $this->mDb; 00194 } 00195 00201 public function doQuery() { 00202 # Use the child class name for profiling 00203 $fname = __METHOD__ . ' (' . get_class( $this ) . ')'; 00204 wfProfileIn( $fname ); 00205 00206 $descending = ( $this->mIsBackwards == $this->mDefaultDirection ); 00207 # Plus an extra row so that we can tell the "next" link should be shown 00208 $queryLimit = $this->mLimit + 1; 00209 00210 if ( $this->mOffset == '' ) { 00211 $isFirst = true; 00212 } else { 00213 // If there's an offset, we may or may not be at the first entry. 00214 // The only way to tell is to run the query in the opposite 00215 // direction see if we get a row. 00216 $oldIncludeOffset = $this->mIncludeOffset; 00217 $this->mIncludeOffset = !$this->mIncludeOffset; 00218 $isFirst = !$this->reallyDoQuery( $this->mOffset, 1, !$descending )->numRows(); 00219 $this->mIncludeOffset = $oldIncludeOffset; 00220 } 00221 00222 $this->mResult = $this->reallyDoQuery( 00223 $this->mOffset, 00224 $queryLimit, 00225 $descending 00226 ); 00227 00228 $this->extractResultInfo( $isFirst, $queryLimit, $this->mResult ); 00229 $this->mQueryDone = true; 00230 00231 $this->preprocessResults( $this->mResult ); 00232 $this->mResult->rewind(); // Paranoia 00233 00234 wfProfileOut( $fname ); 00235 } 00236 00240 function getResult() { 00241 return $this->mResult; 00242 } 00243 00249 function setOffset( $offset ) { 00250 $this->mOffset = $offset; 00251 } 00259 function setLimit( $limit ) { 00260 $limit = (int)$limit; 00261 // WebRequest::getLimitOffset() puts a cap of 5000, so do same here. 00262 if ( $limit > 5000 ) { 00263 $limit = 5000; 00264 } 00265 if ( $limit > 0 ) { 00266 $this->mLimit = $limit; 00267 } 00268 } 00269 00277 public function setIncludeOffset( $include ) { 00278 $this->mIncludeOffset = $include; 00279 } 00280 00290 function extractResultInfo( $isFirst, $limit, ResultWrapper $res ) { 00291 $numRows = $res->numRows(); 00292 if ( $numRows ) { 00293 # Remove any table prefix from index field 00294 $parts = explode( '.', $this->mIndexField ); 00295 $indexColumn = end( $parts ); 00296 00297 $row = $res->fetchRow(); 00298 $firstIndex = $row[$indexColumn]; 00299 00300 # Discard the extra result row if there is one 00301 if ( $numRows > $this->mLimit && $numRows > 1 ) { 00302 $res->seek( $numRows - 1 ); 00303 $this->mPastTheEndRow = $res->fetchObject(); 00304 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn; 00305 $res->seek( $numRows - 2 ); 00306 $row = $res->fetchRow(); 00307 $lastIndex = $row[$indexColumn]; 00308 } else { 00309 $this->mPastTheEndRow = null; 00310 # Setting indexes to an empty string means that they will be 00311 # omitted if they would otherwise appear in URLs. It just so 00312 # happens that this is the right thing to do in the standard 00313 # UI, in all the relevant cases. 00314 $this->mPastTheEndIndex = ''; 00315 $res->seek( $numRows - 1 ); 00316 $row = $res->fetchRow(); 00317 $lastIndex = $row[$indexColumn]; 00318 } 00319 } else { 00320 $firstIndex = ''; 00321 $lastIndex = ''; 00322 $this->mPastTheEndRow = null; 00323 $this->mPastTheEndIndex = ''; 00324 } 00325 00326 if ( $this->mIsBackwards ) { 00327 $this->mIsFirst = ( $numRows < $limit ); 00328 $this->mIsLast = $isFirst; 00329 $this->mLastShown = $firstIndex; 00330 $this->mFirstShown = $lastIndex; 00331 } else { 00332 $this->mIsFirst = $isFirst; 00333 $this->mIsLast = ( $numRows < $limit ); 00334 $this->mLastShown = $lastIndex; 00335 $this->mFirstShown = $firstIndex; 00336 } 00337 } 00338 00344 function getSqlComment() { 00345 return get_class( $this ); 00346 } 00347 00357 public function reallyDoQuery( $offset, $limit, $descending ) { 00358 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending ); 00359 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00360 } 00361 00370 protected function buildQueryInfo( $offset, $limit, $descending ) { 00371 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')'; 00372 $info = $this->getQueryInfo(); 00373 $tables = $info['tables']; 00374 $fields = $info['fields']; 00375 $conds = isset( $info['conds'] ) ? $info['conds'] : array(); 00376 $options = isset( $info['options'] ) ? $info['options'] : array(); 00377 $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array(); 00378 $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields ); 00379 if ( $descending ) { 00380 $options['ORDER BY'] = $sortColumns; 00381 $operator = $this->mIncludeOffset ? '>=' : '>'; 00382 } else { 00383 $orderBy = array(); 00384 foreach ( $sortColumns as $col ) { 00385 $orderBy[] = $col . ' DESC'; 00386 } 00387 $options['ORDER BY'] = $orderBy; 00388 $operator = $this->mIncludeOffset ? '<=' : '<'; 00389 } 00390 if ( $offset != '' ) { 00391 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset ); 00392 } 00393 $options['LIMIT'] = intval( $limit ); 00394 return array( $tables, $fields, $conds, $fname, $options, $join_conds ); 00395 } 00396 00402 protected function preprocessResults( $result ) {} 00403 00410 public function getBody() { 00411 if ( !$this->mQueryDone ) { 00412 $this->doQuery(); 00413 } 00414 00415 if ( $this->mResult->numRows() ) { 00416 # Do any special query batches before display 00417 $this->doBatchLookups(); 00418 } 00419 00420 # Don't use any extra rows returned by the query 00421 $numRows = min( $this->mResult->numRows(), $this->mLimit ); 00422 00423 $s = $this->getStartBody(); 00424 if ( $numRows ) { 00425 if ( $this->mIsBackwards ) { 00426 for ( $i = $numRows - 1; $i >= 0; $i-- ) { 00427 $this->mResult->seek( $i ); 00428 $row = $this->mResult->fetchObject(); 00429 $s .= $this->formatRow( $row ); 00430 } 00431 } else { 00432 $this->mResult->seek( 0 ); 00433 for ( $i = 0; $i < $numRows; $i++ ) { 00434 $row = $this->mResult->fetchObject(); 00435 $s .= $this->formatRow( $row ); 00436 } 00437 } 00438 } else { 00439 $s .= $this->getEmptyBody(); 00440 } 00441 $s .= $this->getEndBody(); 00442 return $s; 00443 } 00444 00454 function makeLink( $text, array $query = null, $type = null ) { 00455 if ( $query === null ) { 00456 return $text; 00457 } 00458 00459 $attrs = array(); 00460 if ( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { 00461 # HTML5 rel attributes 00462 $attrs['rel'] = $type; 00463 } 00464 00465 if ( $type ) { 00466 $attrs['class'] = "mw-{$type}link"; 00467 } 00468 00469 return Linker::linkKnown( 00470 $this->getTitle(), 00471 $text, 00472 $attrs, 00473 $query + $this->getDefaultQuery() 00474 ); 00475 } 00476 00484 protected function doBatchLookups() {} 00485 00492 protected function getStartBody() { 00493 return ''; 00494 } 00495 00501 protected function getEndBody() { 00502 return ''; 00503 } 00504 00511 protected function getEmptyBody() { 00512 return ''; 00513 } 00514 00522 function getDefaultQuery() { 00523 if ( !isset( $this->mDefaultQuery ) ) { 00524 $this->mDefaultQuery = $this->getRequest()->getQueryValues(); 00525 unset( $this->mDefaultQuery['title'] ); 00526 unset( $this->mDefaultQuery['dir'] ); 00527 unset( $this->mDefaultQuery['offset'] ); 00528 unset( $this->mDefaultQuery['limit'] ); 00529 unset( $this->mDefaultQuery['order'] ); 00530 unset( $this->mDefaultQuery['month'] ); 00531 unset( $this->mDefaultQuery['year'] ); 00532 } 00533 return $this->mDefaultQuery; 00534 } 00535 00541 function getNumRows() { 00542 if ( !$this->mQueryDone ) { 00543 $this->doQuery(); 00544 } 00545 return $this->mResult->numRows(); 00546 } 00547 00553 function getPagingQueries() { 00554 if ( !$this->mQueryDone ) { 00555 $this->doQuery(); 00556 } 00557 00558 # Don't announce the limit everywhere if it's the default 00559 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit; 00560 00561 if ( $this->mIsFirst ) { 00562 $prev = false; 00563 $first = false; 00564 } else { 00565 $prev = array( 00566 'dir' => 'prev', 00567 'offset' => $this->mFirstShown, 00568 'limit' => $urlLimit 00569 ); 00570 $first = array( 'limit' => $urlLimit ); 00571 } 00572 if ( $this->mIsLast ) { 00573 $next = false; 00574 $last = false; 00575 } else { 00576 $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); 00577 $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); 00578 } 00579 return array( 00580 'prev' => $prev, 00581 'next' => $next, 00582 'first' => $first, 00583 'last' => $last 00584 ); 00585 } 00586 00592 function isNavigationBarShown() { 00593 if ( !$this->mQueryDone ) { 00594 $this->doQuery(); 00595 } 00596 // Hide navigation by default if there is nothing to page 00597 return !( $this->mIsFirst && $this->mIsLast ); 00598 } 00599 00610 function getPagingLinks( $linkTexts, $disabledTexts = array() ) { 00611 $queries = $this->getPagingQueries(); 00612 $links = array(); 00613 00614 foreach ( $queries as $type => $query ) { 00615 if ( $query !== false ) { 00616 $links[$type] = $this->makeLink( 00617 $linkTexts[$type], 00618 $queries[$type], 00619 $type 00620 ); 00621 } elseif ( isset( $disabledTexts[$type] ) ) { 00622 $links[$type] = $disabledTexts[$type]; 00623 } else { 00624 $links[$type] = $linkTexts[$type]; 00625 } 00626 } 00627 00628 return $links; 00629 } 00630 00631 function getLimitLinks() { 00632 $links = array(); 00633 if ( $this->mIsBackwards ) { 00634 $offset = $this->mPastTheEndIndex; 00635 } else { 00636 $offset = $this->mOffset; 00637 } 00638 foreach ( $this->mLimitsShown as $limit ) { 00639 $links[] = $this->makeLink( 00640 $this->getLanguage()->formatNum( $limit ), 00641 array( 'offset' => $offset, 'limit' => $limit ), 00642 'num' 00643 ); 00644 } 00645 return $links; 00646 } 00647 00656 abstract function formatRow( $row ); 00657 00670 abstract function getQueryInfo(); 00671 00684 abstract function getIndexField(); 00685 00702 protected function getExtraSortFields() { 00703 return array(); 00704 } 00705 00725 protected function getDefaultDirections() { 00726 return false; 00727 } 00728 } 00729 00734 abstract class AlphabeticPager extends IndexPager { 00735 00742 function getNavigationBar() { 00743 if ( !$this->isNavigationBarShown() ) { 00744 return ''; 00745 } 00746 00747 if ( isset( $this->mNavigationBar ) ) { 00748 return $this->mNavigationBar; 00749 } 00750 00751 $linkTexts = array( 00752 'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(), 00753 'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(), 00754 'first' => $this->msg( 'page_first' )->escaped(), 00755 'last' => $this->msg( 'page_last' )->escaped() 00756 ); 00757 00758 $lang = $this->getLanguage(); 00759 00760 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00761 $limitLinks = $this->getLimitLinks(); 00762 $limits = $lang->pipeList( $limitLinks ); 00763 00764 $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams( 00765 $lang->pipeList( array( $pagingLinks['first'], 00766 $pagingLinks['last'] ) ) )->escaped() . " " . 00767 $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], 00768 $pagingLinks['next'], $limits )->escaped(); 00769 00770 if ( !is_array( $this->getIndexField() ) ) { 00771 # Early return to avoid undue nesting 00772 return $this->mNavigationBar; 00773 } 00774 00775 $extra = ''; 00776 $first = true; 00777 $msgs = $this->getOrderTypeMessages(); 00778 foreach ( array_keys( $msgs ) as $order ) { 00779 if ( $first ) { 00780 $first = false; 00781 } else { 00782 $extra .= $this->msg( 'pipe-separator' )->escaped(); 00783 } 00784 00785 if ( $order == $this->mOrderType ) { 00786 $extra .= $this->msg( $msgs[$order] )->escaped(); 00787 } else { 00788 $extra .= $this->makeLink( 00789 $this->msg( $msgs[$order] )->escaped(), 00790 array( 'order' => $order ) 00791 ); 00792 } 00793 } 00794 00795 if ( $extra !== '' ) { 00796 $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped(); 00797 $this->mNavigationBar .= $extra; 00798 } 00799 00800 return $this->mNavigationBar; 00801 } 00802 00811 protected function getOrderTypeMessages() { 00812 return null; 00813 } 00814 } 00815 00820 abstract class ReverseChronologicalPager extends IndexPager { 00821 public $mDefaultDirection = true; 00822 public $mYear; 00823 public $mMonth; 00824 00825 function getNavigationBar() { 00826 if ( !$this->isNavigationBarShown() ) { 00827 return ''; 00828 } 00829 00830 if ( isset( $this->mNavigationBar ) ) { 00831 return $this->mNavigationBar; 00832 } 00833 00834 $linkTexts = array( 00835 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(), 00836 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(), 00837 'first' => $this->msg( 'histlast' )->escaped(), 00838 'last' => $this->msg( 'histfirst' )->escaped() 00839 ); 00840 00841 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00842 $limitLinks = $this->getLimitLinks(); 00843 $limits = $this->getLanguage()->pipeList( $limitLinks ); 00844 $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" . 00845 $this->msg( 'pipe-separator' )->escaped() . 00846 "{$pagingLinks['last']}" )->escaped(); 00847 00848 $this->mNavigationBar = $firstLastLinks . ' ' . 00849 $this->msg( 'viewprevnext' )->rawParams( 00850 $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped(); 00851 00852 return $this->mNavigationBar; 00853 } 00854 00855 function getDateCond( $year, $month ) { 00856 $year = intval( $year ); 00857 $month = intval( $month ); 00858 00859 // Basic validity checks 00860 $this->mYear = $year > 0 ? $year : false; 00861 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false; 00862 00863 // Given an optional year and month, we need to generate a timestamp 00864 // to use as "WHERE rev_timestamp <= result" 00865 // Examples: year = 2006 equals < 20070101 (+000000) 00866 // year=2005, month=1 equals < 20050201 00867 // year=2005, month=12 equals < 20060101 00868 if ( !$this->mYear && !$this->mMonth ) { 00869 return; 00870 } 00871 00872 if ( $this->mYear ) { 00873 $year = $this->mYear; 00874 } else { 00875 // If no year given, assume the current one 00876 $timestamp = MWTimestamp::getInstance(); 00877 $year = $timestamp->format( 'Y' ); 00878 // If this month hasn't happened yet this year, go back to last year's month 00879 if ( $this->mMonth > $timestamp->format( 'n' ) ) { 00880 $year--; 00881 } 00882 } 00883 00884 if ( $this->mMonth ) { 00885 $month = $this->mMonth + 1; 00886 // For December, we want January 1 of the next year 00887 if ( $month > 12 ) { 00888 $month = 1; 00889 $year++; 00890 } 00891 } else { 00892 // No month implies we want up to the end of the year in question 00893 $month = 1; 00894 $year++; 00895 } 00896 00897 // Y2K38 bug 00898 if ( $year > 2032 ) { 00899 $year = 2032; 00900 } 00901 00902 $ymd = (int)sprintf( "%04d%02d01", $year, $month ); 00903 00904 if ( $ymd > 20320101 ) { 00905 $ymd = 20320101; 00906 } 00907 00908 $this->mOffset = $this->mDb->timestamp( "${ymd}000000" ); 00909 } 00910 } 00911 00916 abstract class TablePager extends IndexPager { 00917 var $mSort; 00918 var $mCurrentRow; 00919 00920 public function __construct( IContextSource $context = null ) { 00921 if ( $context ) { 00922 $this->setContext( $context ); 00923 } 00924 00925 $this->mSort = $this->getRequest()->getText( 'sort' ); 00926 if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) 00927 || !$this->isFieldSortable( $this->mSort ) 00928 ) { 00929 $this->mSort = $this->getDefaultSort(); 00930 } 00931 if ( $this->getRequest()->getBool( 'asc' ) ) { 00932 $this->mDefaultDirection = false; 00933 } elseif ( $this->getRequest()->getBool( 'desc' ) ) { 00934 $this->mDefaultDirection = true; 00935 } /* Else leave it at whatever the class default is */ 00936 00937 parent::__construct(); 00938 } 00939 00944 function getStartBody() { 00945 global $wgStylePath; 00946 $sortClass = $this->getSortHeaderClass(); 00947 00948 $s = ''; 00949 $fields = $this->getFieldNames(); 00950 00951 # Make table header 00952 foreach ( $fields as $field => $name ) { 00953 if ( strval( $name ) == '' ) { 00954 $s .= Html::rawElement( 'th', array(), ' ' ) . "\n"; 00955 } elseif ( $this->isFieldSortable( $field ) ) { 00956 $query = array( 'sort' => $field, 'limit' => $this->mLimit ); 00957 if ( $field == $this->mSort ) { 00958 # This is the sorted column 00959 # Prepare a link that goes in the other sort order 00960 if ( $this->mDefaultDirection ) { 00961 # Descending 00962 $image = 'Arr_d.png'; 00963 $query['asc'] = '1'; 00964 $query['desc'] = ''; 00965 $alt = $this->msg( 'descending_abbrev' )->escaped(); 00966 } else { 00967 # Ascending 00968 $image = 'Arr_u.png'; 00969 $query['asc'] = ''; 00970 $query['desc'] = '1'; 00971 $alt = $this->msg( 'ascending_abbrev' )->escaped(); 00972 } 00973 $image = "$wgStylePath/common/images/$image"; 00974 $link = $this->makeLink( 00975 Html::element( 'img', array( 'width' => 12, 'height' => 12, 00976 'alt' => $alt, 'src' => $image ) ) . htmlspecialchars( $name ), $query ); 00977 $s .= Html::rawElement( 'th', array( 'class' => $sortClass ), $link ) . "\n"; 00978 } else { 00979 $s .= Html::rawElement( 'th', array(), 00980 $this->makeLink( htmlspecialchars( $name ), $query ) ) . "\n"; 00981 } 00982 } else { 00983 $s .= Html::element( 'th', array(), $name ) . "\n"; 00984 } 00985 } 00986 00987 $tableClass = $this->getTableClass(); 00988 $ret = Html::openElement( 'table', array( 'style' => 'border:1px;', 'class' => "mw-datatable $tableClass" ) ); 00989 $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) ); 00990 $ret .= Html::openElement( 'tbody' ) . "\n"; 00991 00992 return $ret; 00993 } 00994 00999 function getEndBody() { 01000 return "</tbody></table>\n"; 01001 } 01002 01007 function getEmptyBody() { 01008 $colspan = count( $this->getFieldNames() ); 01009 $msgEmpty = $this->msg( 'table_pager_empty' )->text(); 01010 return Html::rawElement( 'tr', array(), 01011 Html::element( 'td', array( 'colspan' => $colspan ), $msgEmpty ) ); 01012 } 01013 01019 function formatRow( $row ) { 01020 $this->mCurrentRow = $row; // In case formatValue etc need to know 01021 $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n"; 01022 $fieldNames = $this->getFieldNames(); 01023 01024 foreach ( $fieldNames as $field => $name ) { 01025 $value = isset( $row->$field ) ? $row->$field : null; 01026 $formatted = strval( $this->formatValue( $field, $value ) ); 01027 01028 if ( $formatted == '' ) { 01029 $formatted = ' '; 01030 } 01031 01032 $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n"; 01033 } 01034 01035 $s .= Html::closeElement( 'tr' ) . "\n"; 01036 01037 return $s; 01038 } 01039 01048 function getRowClass( $row ) { 01049 return ''; 01050 } 01051 01060 function getRowAttrs( $row ) { 01061 $class = $this->getRowClass( $row ); 01062 if ( $class === '' ) { 01063 // Return an empty array to avoid clutter in HTML like class="" 01064 return array(); 01065 } else { 01066 return array( 'class' => $this->getRowClass( $row ) ); 01067 } 01068 } 01069 01081 function getCellAttrs( $field, $value ) { 01082 return array( 'class' => 'TablePager_col_' . $field ); 01083 } 01084 01089 function getIndexField() { 01090 return $this->mSort; 01091 } 01092 01097 function getTableClass() { 01098 return 'TablePager'; 01099 } 01100 01105 function getNavClass() { 01106 return 'TablePager_nav'; 01107 } 01108 01113 function getSortHeaderClass() { 01114 return 'TablePager_sort'; 01115 } 01116 01121 public function getNavigationBar() { 01122 global $wgStylePath; 01123 01124 if ( !$this->isNavigationBarShown() ) { 01125 return ''; 01126 } 01127 01128 $path = "$wgStylePath/common/images"; 01129 $labels = array( 01130 'first' => 'table_pager_first', 01131 'prev' => 'table_pager_prev', 01132 'next' => 'table_pager_next', 01133 'last' => 'table_pager_last', 01134 ); 01135 $images = array( 01136 'first' => 'arrow_first_25.png', 01137 'prev' => 'arrow_left_25.png', 01138 'next' => 'arrow_right_25.png', 01139 'last' => 'arrow_last_25.png', 01140 ); 01141 $disabledImages = array( 01142 'first' => 'arrow_disabled_first_25.png', 01143 'prev' => 'arrow_disabled_left_25.png', 01144 'next' => 'arrow_disabled_right_25.png', 01145 'last' => 'arrow_disabled_last_25.png', 01146 ); 01147 if ( $this->getLanguage()->isRTL() ) { 01148 $keys = array_keys( $labels ); 01149 $images = array_combine( $keys, array_reverse( $images ) ); 01150 $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) ); 01151 } 01152 01153 $linkTexts = array(); 01154 $disabledTexts = array(); 01155 foreach ( $labels as $type => $label ) { 01156 $msgLabel = $this->msg( $label )->escaped(); 01157 $linkTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$images[$type]}", 01158 'alt' => $msgLabel ) ) . "<br />$msgLabel"; 01159 $disabledTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$disabledImages[$type]}", 01160 'alt' => $msgLabel ) ) . "<br />$msgLabel"; 01161 } 01162 $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); 01163 01164 $s = Html::openElement( 'table', array( 'class' => $this->getNavClass() ) ); 01165 $s .= Html::openElement( 'tr' ) . "\n"; 01166 $width = 100 / count( $links ) . '%'; 01167 foreach ( $labels as $type => $label ) { 01168 $s .= Html::rawElement( 'td', array( 'style' => "width:$width;" ), $links[$type] ) . "\n"; 01169 } 01170 $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"; 01171 return $s; 01172 } 01173 01180 public function getLimitSelect( $attribs = array() ) { 01181 $select = new XmlSelect( 'limit', false, $this->mLimit ); 01182 $select->addOptions( $this->getLimitSelectList() ); 01183 foreach ( $attribs as $name => $value ) { 01184 $select->setAttribute( $name, $value ); 01185 } 01186 return $select->getHTML(); 01187 } 01188 01196 public function getLimitSelectList() { 01197 # Add the current limit from the query string 01198 # to avoid that the limit is lost after clicking Go next time 01199 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) { 01200 $this->mLimitsShown[] = $this->mLimit; 01201 sort( $this->mLimitsShown ); 01202 } 01203 $ret = array(); 01204 foreach ( $this->mLimitsShown as $key => $value ) { 01205 # The pair is either $index => $limit, in which case the $value 01206 # will be numeric, or $limit => $text, in which case the $value 01207 # will be a string. 01208 if ( is_int( $value ) ) { 01209 $limit = $value; 01210 $text = $this->getLanguage()->formatNum( $limit ); 01211 } else { 01212 $limit = $key; 01213 $text = $value; 01214 } 01215 $ret[$text] = $limit; 01216 } 01217 return $ret; 01218 } 01219 01228 function getHiddenFields( $blacklist = array() ) { 01229 $blacklist = (array)$blacklist; 01230 $query = $this->getRequest()->getQueryValues(); 01231 foreach ( $blacklist as $name ) { 01232 unset( $query[$name] ); 01233 } 01234 $s = ''; 01235 foreach ( $query as $name => $value ) { 01236 $s .= Html::hidden( $name, $value ) . "\n"; 01237 } 01238 return $s; 01239 } 01240 01246 function getLimitForm() { 01247 global $wgScript; 01248 01249 return Html::rawElement( 01250 'form', 01251 array( 01252 'method' => 'get', 01253 'action' => $wgScript 01254 ), 01255 "\n" . $this->getLimitDropdown() 01256 ) . "\n"; 01257 } 01258 01264 function getLimitDropdown() { 01265 # Make the select with some explanatory text 01266 $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped(); 01267 01268 return $this->msg( 'table_pager_limit' ) 01269 ->rawParams( $this->getLimitSelect() )->escaped() . 01270 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" . 01271 $this->getHiddenFields( array( 'limit' ) ); 01272 } 01273 01280 abstract function isFieldSortable( $field ); 01281 01294 abstract function formatValue( $name, $value ); 01295 01303 abstract function getDefaultSort(); 01304 01312 abstract function getFieldNames(); 01313 }