MediaWiki
REL1_23
|
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 } 00252 00260 function setLimit( $limit ) { 00261 $limit = (int)$limit; 00262 // WebRequest::getLimitOffset() puts a cap of 5000, so do same here. 00263 if ( $limit > 5000 ) { 00264 $limit = 5000; 00265 } 00266 if ( $limit > 0 ) { 00267 $this->mLimit = $limit; 00268 } 00269 } 00270 00276 function getLimit() { 00277 return $this->mLimit; 00278 } 00279 00287 public function setIncludeOffset( $include ) { 00288 $this->mIncludeOffset = $include; 00289 } 00290 00300 function extractResultInfo( $isFirst, $limit, ResultWrapper $res ) { 00301 $numRows = $res->numRows(); 00302 if ( $numRows ) { 00303 # Remove any table prefix from index field 00304 $parts = explode( '.', $this->mIndexField ); 00305 $indexColumn = end( $parts ); 00306 00307 $row = $res->fetchRow(); 00308 $firstIndex = $row[$indexColumn]; 00309 00310 # Discard the extra result row if there is one 00311 if ( $numRows > $this->mLimit && $numRows > 1 ) { 00312 $res->seek( $numRows - 1 ); 00313 $this->mPastTheEndRow = $res->fetchObject(); 00314 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn; 00315 $res->seek( $numRows - 2 ); 00316 $row = $res->fetchRow(); 00317 $lastIndex = $row[$indexColumn]; 00318 } else { 00319 $this->mPastTheEndRow = null; 00320 # Setting indexes to an empty string means that they will be 00321 # omitted if they would otherwise appear in URLs. It just so 00322 # happens that this is the right thing to do in the standard 00323 # UI, in all the relevant cases. 00324 $this->mPastTheEndIndex = ''; 00325 $res->seek( $numRows - 1 ); 00326 $row = $res->fetchRow(); 00327 $lastIndex = $row[$indexColumn]; 00328 } 00329 } else { 00330 $firstIndex = ''; 00331 $lastIndex = ''; 00332 $this->mPastTheEndRow = null; 00333 $this->mPastTheEndIndex = ''; 00334 } 00335 00336 if ( $this->mIsBackwards ) { 00337 $this->mIsFirst = ( $numRows < $limit ); 00338 $this->mIsLast = $isFirst; 00339 $this->mLastShown = $firstIndex; 00340 $this->mFirstShown = $lastIndex; 00341 } else { 00342 $this->mIsFirst = $isFirst; 00343 $this->mIsLast = ( $numRows < $limit ); 00344 $this->mLastShown = $lastIndex; 00345 $this->mFirstShown = $firstIndex; 00346 } 00347 } 00348 00354 function getSqlComment() { 00355 return get_class( $this ); 00356 } 00357 00367 public function reallyDoQuery( $offset, $limit, $descending ) { 00368 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending ); 00369 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); 00370 } 00371 00380 protected function buildQueryInfo( $offset, $limit, $descending ) { 00381 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')'; 00382 $info = $this->getQueryInfo(); 00383 $tables = $info['tables']; 00384 $fields = $info['fields']; 00385 $conds = isset( $info['conds'] ) ? $info['conds'] : array(); 00386 $options = isset( $info['options'] ) ? $info['options'] : array(); 00387 $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array(); 00388 $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields ); 00389 if ( $descending ) { 00390 $options['ORDER BY'] = $sortColumns; 00391 $operator = $this->mIncludeOffset ? '>=' : '>'; 00392 } else { 00393 $orderBy = array(); 00394 foreach ( $sortColumns as $col ) { 00395 $orderBy[] = $col . ' DESC'; 00396 } 00397 $options['ORDER BY'] = $orderBy; 00398 $operator = $this->mIncludeOffset ? '<=' : '<'; 00399 } 00400 if ( $offset != '' ) { 00401 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset ); 00402 } 00403 $options['LIMIT'] = intval( $limit ); 00404 return array( $tables, $fields, $conds, $fname, $options, $join_conds ); 00405 } 00406 00412 protected function preprocessResults( $result ) {} 00413 00420 public function getBody() { 00421 if ( !$this->mQueryDone ) { 00422 $this->doQuery(); 00423 } 00424 00425 if ( $this->mResult->numRows() ) { 00426 # Do any special query batches before display 00427 $this->doBatchLookups(); 00428 } 00429 00430 # Don't use any extra rows returned by the query 00431 $numRows = min( $this->mResult->numRows(), $this->mLimit ); 00432 00433 $s = $this->getStartBody(); 00434 if ( $numRows ) { 00435 if ( $this->mIsBackwards ) { 00436 for ( $i = $numRows - 1; $i >= 0; $i-- ) { 00437 $this->mResult->seek( $i ); 00438 $row = $this->mResult->fetchObject(); 00439 $s .= $this->formatRow( $row ); 00440 } 00441 } else { 00442 $this->mResult->seek( 0 ); 00443 for ( $i = 0; $i < $numRows; $i++ ) { 00444 $row = $this->mResult->fetchObject(); 00445 $s .= $this->formatRow( $row ); 00446 } 00447 } 00448 } else { 00449 $s .= $this->getEmptyBody(); 00450 } 00451 $s .= $this->getEndBody(); 00452 return $s; 00453 } 00454 00464 function makeLink( $text, array $query = null, $type = null ) { 00465 if ( $query === null ) { 00466 return $text; 00467 } 00468 00469 $attrs = array(); 00470 if ( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { 00471 # HTML5 rel attributes 00472 $attrs['rel'] = $type; 00473 } 00474 00475 if ( $type ) { 00476 $attrs['class'] = "mw-{$type}link"; 00477 } 00478 00479 return Linker::linkKnown( 00480 $this->getTitle(), 00481 $text, 00482 $attrs, 00483 $query + $this->getDefaultQuery() 00484 ); 00485 } 00486 00494 protected function doBatchLookups() {} 00495 00502 protected function getStartBody() { 00503 return ''; 00504 } 00505 00511 protected function getEndBody() { 00512 return ''; 00513 } 00514 00521 protected function getEmptyBody() { 00522 return ''; 00523 } 00524 00532 function getDefaultQuery() { 00533 if ( !isset( $this->mDefaultQuery ) ) { 00534 $this->mDefaultQuery = $this->getRequest()->getQueryValues(); 00535 unset( $this->mDefaultQuery['title'] ); 00536 unset( $this->mDefaultQuery['dir'] ); 00537 unset( $this->mDefaultQuery['offset'] ); 00538 unset( $this->mDefaultQuery['limit'] ); 00539 unset( $this->mDefaultQuery['order'] ); 00540 unset( $this->mDefaultQuery['month'] ); 00541 unset( $this->mDefaultQuery['year'] ); 00542 } 00543 return $this->mDefaultQuery; 00544 } 00545 00551 function getNumRows() { 00552 if ( !$this->mQueryDone ) { 00553 $this->doQuery(); 00554 } 00555 return $this->mResult->numRows(); 00556 } 00557 00563 function getPagingQueries() { 00564 if ( !$this->mQueryDone ) { 00565 $this->doQuery(); 00566 } 00567 00568 # Don't announce the limit everywhere if it's the default 00569 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit; 00570 00571 if ( $this->mIsFirst ) { 00572 $prev = false; 00573 $first = false; 00574 } else { 00575 $prev = array( 00576 'dir' => 'prev', 00577 'offset' => $this->mFirstShown, 00578 'limit' => $urlLimit 00579 ); 00580 $first = array( 'limit' => $urlLimit ); 00581 } 00582 if ( $this->mIsLast ) { 00583 $next = false; 00584 $last = false; 00585 } else { 00586 $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); 00587 $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); 00588 } 00589 return array( 00590 'prev' => $prev, 00591 'next' => $next, 00592 'first' => $first, 00593 'last' => $last 00594 ); 00595 } 00596 00602 function isNavigationBarShown() { 00603 if ( !$this->mQueryDone ) { 00604 $this->doQuery(); 00605 } 00606 // Hide navigation by default if there is nothing to page 00607 return !( $this->mIsFirst && $this->mIsLast ); 00608 } 00609 00620 function getPagingLinks( $linkTexts, $disabledTexts = array() ) { 00621 $queries = $this->getPagingQueries(); 00622 $links = array(); 00623 00624 foreach ( $queries as $type => $query ) { 00625 if ( $query !== false ) { 00626 $links[$type] = $this->makeLink( 00627 $linkTexts[$type], 00628 $queries[$type], 00629 $type 00630 ); 00631 } elseif ( isset( $disabledTexts[$type] ) ) { 00632 $links[$type] = $disabledTexts[$type]; 00633 } else { 00634 $links[$type] = $linkTexts[$type]; 00635 } 00636 } 00637 00638 return $links; 00639 } 00640 00641 function getLimitLinks() { 00642 $links = array(); 00643 if ( $this->mIsBackwards ) { 00644 $offset = $this->mPastTheEndIndex; 00645 } else { 00646 $offset = $this->mOffset; 00647 } 00648 foreach ( $this->mLimitsShown as $limit ) { 00649 $links[] = $this->makeLink( 00650 $this->getLanguage()->formatNum( $limit ), 00651 array( 'offset' => $offset, 'limit' => $limit ), 00652 'num' 00653 ); 00654 } 00655 return $links; 00656 } 00657 00666 abstract function formatRow( $row ); 00667 00680 abstract function getQueryInfo(); 00681 00694 abstract function getIndexField(); 00695 00712 protected function getExtraSortFields() { 00713 return array(); 00714 } 00715 00735 protected function getDefaultDirections() { 00736 return false; 00737 } 00738 } 00739 00744 abstract class AlphabeticPager extends IndexPager { 00745 00752 function getNavigationBar() { 00753 if ( !$this->isNavigationBarShown() ) { 00754 return ''; 00755 } 00756 00757 if ( isset( $this->mNavigationBar ) ) { 00758 return $this->mNavigationBar; 00759 } 00760 00761 $linkTexts = array( 00762 'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(), 00763 'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(), 00764 'first' => $this->msg( 'page_first' )->escaped(), 00765 'last' => $this->msg( 'page_last' )->escaped() 00766 ); 00767 00768 $lang = $this->getLanguage(); 00769 00770 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00771 $limitLinks = $this->getLimitLinks(); 00772 $limits = $lang->pipeList( $limitLinks ); 00773 00774 $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams( 00775 $lang->pipeList( array( $pagingLinks['first'], 00776 $pagingLinks['last'] ) ) )->escaped() . " " . 00777 $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], 00778 $pagingLinks['next'], $limits )->escaped(); 00779 00780 if ( !is_array( $this->getIndexField() ) ) { 00781 # Early return to avoid undue nesting 00782 return $this->mNavigationBar; 00783 } 00784 00785 $extra = ''; 00786 $first = true; 00787 $msgs = $this->getOrderTypeMessages(); 00788 foreach ( array_keys( $msgs ) as $order ) { 00789 if ( $first ) { 00790 $first = false; 00791 } else { 00792 $extra .= $this->msg( 'pipe-separator' )->escaped(); 00793 } 00794 00795 if ( $order == $this->mOrderType ) { 00796 $extra .= $this->msg( $msgs[$order] )->escaped(); 00797 } else { 00798 $extra .= $this->makeLink( 00799 $this->msg( $msgs[$order] )->escaped(), 00800 array( 'order' => $order ) 00801 ); 00802 } 00803 } 00804 00805 if ( $extra !== '' ) { 00806 $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped(); 00807 $this->mNavigationBar .= $extra; 00808 } 00809 00810 return $this->mNavigationBar; 00811 } 00812 00821 protected function getOrderTypeMessages() { 00822 return null; 00823 } 00824 } 00825 00830 abstract class ReverseChronologicalPager extends IndexPager { 00831 public $mDefaultDirection = true; 00832 public $mYear; 00833 public $mMonth; 00834 00835 function getNavigationBar() { 00836 if ( !$this->isNavigationBarShown() ) { 00837 return ''; 00838 } 00839 00840 if ( isset( $this->mNavigationBar ) ) { 00841 return $this->mNavigationBar; 00842 } 00843 00844 $linkTexts = array( 00845 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(), 00846 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(), 00847 'first' => $this->msg( 'histlast' )->escaped(), 00848 'last' => $this->msg( 'histfirst' )->escaped() 00849 ); 00850 00851 $pagingLinks = $this->getPagingLinks( $linkTexts ); 00852 $limitLinks = $this->getLimitLinks(); 00853 $limits = $this->getLanguage()->pipeList( $limitLinks ); 00854 $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" . 00855 $this->msg( 'pipe-separator' )->escaped() . 00856 "{$pagingLinks['last']}" )->escaped(); 00857 00858 $this->mNavigationBar = $firstLastLinks . ' ' . 00859 $this->msg( 'viewprevnext' )->rawParams( 00860 $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped(); 00861 00862 return $this->mNavigationBar; 00863 } 00864 00865 function getDateCond( $year, $month ) { 00866 $year = intval( $year ); 00867 $month = intval( $month ); 00868 00869 // Basic validity checks 00870 $this->mYear = $year > 0 ? $year : false; 00871 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false; 00872 00873 // Given an optional year and month, we need to generate a timestamp 00874 // to use as "WHERE rev_timestamp <= result" 00875 // Examples: year = 2006 equals < 20070101 (+000000) 00876 // year=2005, month=1 equals < 20050201 00877 // year=2005, month=12 equals < 20060101 00878 if ( !$this->mYear && !$this->mMonth ) { 00879 return; 00880 } 00881 00882 if ( $this->mYear ) { 00883 $year = $this->mYear; 00884 } else { 00885 // If no year given, assume the current one 00886 $timestamp = MWTimestamp::getInstance(); 00887 $year = $timestamp->format( 'Y' ); 00888 // If this month hasn't happened yet this year, go back to last year's month 00889 if ( $this->mMonth > $timestamp->format( 'n' ) ) { 00890 $year--; 00891 } 00892 } 00893 00894 if ( $this->mMonth ) { 00895 $month = $this->mMonth + 1; 00896 // For December, we want January 1 of the next year 00897 if ( $month > 12 ) { 00898 $month = 1; 00899 $year++; 00900 } 00901 } else { 00902 // No month implies we want up to the end of the year in question 00903 $month = 1; 00904 $year++; 00905 } 00906 00907 // Y2K38 bug 00908 if ( $year > 2032 ) { 00909 $year = 2032; 00910 } 00911 00912 $ymd = (int)sprintf( "%04d%02d01", $year, $month ); 00913 00914 if ( $ymd > 20320101 ) { 00915 $ymd = 20320101; 00916 } 00917 00918 $this->mOffset = $this->mDb->timestamp( "${ymd}000000" ); 00919 } 00920 } 00921 00926 abstract class TablePager extends IndexPager { 00927 var $mSort; 00928 var $mCurrentRow; 00929 00930 public function __construct( IContextSource $context = null ) { 00931 if ( $context ) { 00932 $this->setContext( $context ); 00933 } 00934 00935 $this->mSort = $this->getRequest()->getText( 'sort' ); 00936 if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) 00937 || !$this->isFieldSortable( $this->mSort ) 00938 ) { 00939 $this->mSort = $this->getDefaultSort(); 00940 } 00941 if ( $this->getRequest()->getBool( 'asc' ) ) { 00942 $this->mDefaultDirection = false; 00943 } elseif ( $this->getRequest()->getBool( 'desc' ) ) { 00944 $this->mDefaultDirection = true; 00945 } /* Else leave it at whatever the class default is */ 00946 00947 parent::__construct(); 00948 } 00949 00954 function getStartBody() { 00955 global $wgStylePath; 00956 $sortClass = $this->getSortHeaderClass(); 00957 00958 $s = ''; 00959 $fields = $this->getFieldNames(); 00960 00961 # Make table header 00962 foreach ( $fields as $field => $name ) { 00963 if ( strval( $name ) == '' ) { 00964 $s .= Html::rawElement( 'th', array(), ' ' ) . "\n"; 00965 } elseif ( $this->isFieldSortable( $field ) ) { 00966 $query = array( 'sort' => $field, 'limit' => $this->mLimit ); 00967 if ( $field == $this->mSort ) { 00968 # This is the sorted column 00969 # Prepare a link that goes in the other sort order 00970 if ( $this->mDefaultDirection ) { 00971 # Descending 00972 $image = 'Arr_d.png'; 00973 $query['asc'] = '1'; 00974 $query['desc'] = ''; 00975 $alt = $this->msg( 'descending_abbrev' )->escaped(); 00976 } else { 00977 # Ascending 00978 $image = 'Arr_u.png'; 00979 $query['asc'] = ''; 00980 $query['desc'] = '1'; 00981 $alt = $this->msg( 'ascending_abbrev' )->escaped(); 00982 } 00983 $image = "$wgStylePath/common/images/$image"; 00984 $link = $this->makeLink( 00985 Html::element( 'img', array( 'width' => 12, 'height' => 12, 00986 'alt' => $alt, 'src' => $image ) ) . htmlspecialchars( $name ), $query ); 00987 $s .= Html::rawElement( 'th', array( 'class' => $sortClass ), $link ) . "\n"; 00988 } else { 00989 $s .= Html::rawElement( 'th', array(), 00990 $this->makeLink( htmlspecialchars( $name ), $query ) ) . "\n"; 00991 } 00992 } else { 00993 $s .= Html::element( 'th', array(), $name ) . "\n"; 00994 } 00995 } 00996 00997 $tableClass = $this->getTableClass(); 00998 $ret = Html::openElement( 'table', array( 'style' => 'border:1px;', 'class' => "mw-datatable $tableClass" ) ); 00999 $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) ); 01000 $ret .= Html::openElement( 'tbody' ) . "\n"; 01001 01002 return $ret; 01003 } 01004 01009 function getEndBody() { 01010 return "</tbody></table>\n"; 01011 } 01012 01017 function getEmptyBody() { 01018 $colspan = count( $this->getFieldNames() ); 01019 $msgEmpty = $this->msg( 'table_pager_empty' )->text(); 01020 return Html::rawElement( 'tr', array(), 01021 Html::element( 'td', array( 'colspan' => $colspan ), $msgEmpty ) ); 01022 } 01023 01029 function formatRow( $row ) { 01030 $this->mCurrentRow = $row; // In case formatValue etc need to know 01031 $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n"; 01032 $fieldNames = $this->getFieldNames(); 01033 01034 foreach ( $fieldNames as $field => $name ) { 01035 $value = isset( $row->$field ) ? $row->$field : null; 01036 $formatted = strval( $this->formatValue( $field, $value ) ); 01037 01038 if ( $formatted == '' ) { 01039 $formatted = ' '; 01040 } 01041 01042 $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n"; 01043 } 01044 01045 $s .= Html::closeElement( 'tr' ) . "\n"; 01046 01047 return $s; 01048 } 01049 01058 function getRowClass( $row ) { 01059 return ''; 01060 } 01061 01070 function getRowAttrs( $row ) { 01071 $class = $this->getRowClass( $row ); 01072 if ( $class === '' ) { 01073 // Return an empty array to avoid clutter in HTML like class="" 01074 return array(); 01075 } else { 01076 return array( 'class' => $this->getRowClass( $row ) ); 01077 } 01078 } 01079 01091 function getCellAttrs( $field, $value ) { 01092 return array( 'class' => 'TablePager_col_' . $field ); 01093 } 01094 01099 function getIndexField() { 01100 return $this->mSort; 01101 } 01102 01107 function getTableClass() { 01108 return 'TablePager'; 01109 } 01110 01115 function getNavClass() { 01116 return 'TablePager_nav'; 01117 } 01118 01123 function getSortHeaderClass() { 01124 return 'TablePager_sort'; 01125 } 01126 01131 public function getNavigationBar() { 01132 global $wgStylePath; 01133 01134 if ( !$this->isNavigationBarShown() ) { 01135 return ''; 01136 } 01137 01138 $path = "$wgStylePath/common/images"; 01139 $labels = array( 01140 'first' => 'table_pager_first', 01141 'prev' => 'table_pager_prev', 01142 'next' => 'table_pager_next', 01143 'last' => 'table_pager_last', 01144 ); 01145 $images = array( 01146 'first' => 'arrow_first_25.png', 01147 'prev' => 'arrow_left_25.png', 01148 'next' => 'arrow_right_25.png', 01149 'last' => 'arrow_last_25.png', 01150 ); 01151 $disabledImages = array( 01152 'first' => 'arrow_disabled_first_25.png', 01153 'prev' => 'arrow_disabled_left_25.png', 01154 'next' => 'arrow_disabled_right_25.png', 01155 'last' => 'arrow_disabled_last_25.png', 01156 ); 01157 if ( $this->getLanguage()->isRTL() ) { 01158 $keys = array_keys( $labels ); 01159 $images = array_combine( $keys, array_reverse( $images ) ); 01160 $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) ); 01161 } 01162 01163 $linkTexts = array(); 01164 $disabledTexts = array(); 01165 foreach ( $labels as $type => $label ) { 01166 $msgLabel = $this->msg( $label )->escaped(); 01167 $linkTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$images[$type]}", 01168 'alt' => $msgLabel ) ) . "<br />$msgLabel"; 01169 $disabledTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$disabledImages[$type]}", 01170 'alt' => $msgLabel ) ) . "<br />$msgLabel"; 01171 } 01172 $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); 01173 01174 $s = Html::openElement( 'table', array( 'class' => $this->getNavClass() ) ); 01175 $s .= Html::openElement( 'tr' ) . "\n"; 01176 $width = 100 / count( $links ) . '%'; 01177 foreach ( $labels as $type => $label ) { 01178 $s .= Html::rawElement( 'td', array( 'style' => "width:$width;" ), $links[$type] ) . "\n"; 01179 } 01180 $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"; 01181 return $s; 01182 } 01183 01190 public function getLimitSelect( $attribs = array() ) { 01191 $select = new XmlSelect( 'limit', false, $this->mLimit ); 01192 $select->addOptions( $this->getLimitSelectList() ); 01193 foreach ( $attribs as $name => $value ) { 01194 $select->setAttribute( $name, $value ); 01195 } 01196 return $select->getHTML(); 01197 } 01198 01206 public function getLimitSelectList() { 01207 # Add the current limit from the query string 01208 # to avoid that the limit is lost after clicking Go next time 01209 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) { 01210 $this->mLimitsShown[] = $this->mLimit; 01211 sort( $this->mLimitsShown ); 01212 } 01213 $ret = array(); 01214 foreach ( $this->mLimitsShown as $key => $value ) { 01215 # The pair is either $index => $limit, in which case the $value 01216 # will be numeric, or $limit => $text, in which case the $value 01217 # will be a string. 01218 if ( is_int( $value ) ) { 01219 $limit = $value; 01220 $text = $this->getLanguage()->formatNum( $limit ); 01221 } else { 01222 $limit = $key; 01223 $text = $value; 01224 } 01225 $ret[$text] = $limit; 01226 } 01227 return $ret; 01228 } 01229 01238 function getHiddenFields( $blacklist = array() ) { 01239 $blacklist = (array)$blacklist; 01240 $query = $this->getRequest()->getQueryValues(); 01241 foreach ( $blacklist as $name ) { 01242 unset( $query[$name] ); 01243 } 01244 $s = ''; 01245 foreach ( $query as $name => $value ) { 01246 $s .= Html::hidden( $name, $value ) . "\n"; 01247 } 01248 return $s; 01249 } 01250 01256 function getLimitForm() { 01257 global $wgScript; 01258 01259 return Html::rawElement( 01260 'form', 01261 array( 01262 'method' => 'get', 01263 'action' => $wgScript 01264 ), 01265 "\n" . $this->getLimitDropdown() 01266 ) . "\n"; 01267 } 01268 01274 function getLimitDropdown() { 01275 # Make the select with some explanatory text 01276 $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped(); 01277 01278 return $this->msg( 'table_pager_limit' ) 01279 ->rawParams( $this->getLimitSelect() )->escaped() . 01280 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" . 01281 $this->getHiddenFields( array( 'limit' ) ); 01282 } 01283 01290 abstract function isFieldSortable( $field ); 01291 01304 abstract function formatValue( $name, $value ); 01305 01313 abstract function getDefaultSort(); 01314 01322 abstract function getFieldNames(); 01323 }