140 # NB: the offset is quoted, not validated. It is treated as an
141 # arbitrary string to support the widest variety of index types. Be
142 # careful outputting it into HTML!
143 $this->mOffset = $this->mRequest->getText(
'offset' );
145 # Use consistent behavior for the limit options
146 $this->mDefaultLimit = $this->
getUser()->getIntOption(
'rclimit' );
147 if ( !$this->mLimit ) {
149 list( $this->mLimit, ) = $this->mRequest->getLimitOffset();
152 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
153 # Let the subclass set the DB here; otherwise use a slave DB for the current wiki
158 $order = $this->mRequest->getVal(
'order' );
159 if ( is_array( $index ) && isset( $index[$order] ) ) {
160 $this->mOrderType = $order;
161 $this->mIndexField = $index[$order];
162 $this->mExtraSortFields = isset( $extraSort[$order] )
163 ? (
array)$extraSort[$order]
165 } elseif ( is_array( $index ) ) {
166 # First element is the default
168 list( $this->mOrderType, $this->mIndexField ) = each( $index );
169 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
170 ? (
array)$extraSort[$this->mOrderType]
173 # $index is not an array
174 $this->mOrderType =
null;
175 $this->mIndexField = $index;
176 $this->mExtraSortFields = (
array)$extraSort;
179 if ( !isset( $this->mDefaultDirection ) ) {
181 $this->mDefaultDirection = is_array(
$dir )
202 # Use the child class name for profiling
203 $fname = __METHOD__ .
' (' . get_class( $this ) .
')';
207 # Plus an extra row so that we can tell the "next" link should be shown
208 $queryLimit = $this->mLimit + 1;
210 if ( $this->mOffset ==
'' ) {
218 $isFirst = !$this->
reallyDoQuery( $this->mOffset, 1, !$descending )->numRows();
219 $this->mIncludeOffset = $oldIncludeOffset;
229 $this->mQueryDone =
true;
232 $this->mResult->rewind();
250 $this->mOffset = $offset;
288 $this->mIncludeOffset = $include;
301 $numRows =
$res->numRows();
303 # Remove any table prefix from index field
304 $parts = explode(
'.', $this->mIndexField );
305 $indexColumn = end( $parts );
307 $row =
$res->fetchRow();
308 $firstIndex = $row[$indexColumn];
310 # Discard the extra result row if there is one
311 if ( $numRows > $this->mLimit && $numRows > 1 ) {
312 $res->seek( $numRows - 1 );
313 $this->mPastTheEndRow =
$res->fetchObject();
314 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
315 $res->seek( $numRows - 2 );
316 $row =
$res->fetchRow();
317 $lastIndex = $row[$indexColumn];
319 $this->mPastTheEndRow =
null;
320 # Setting indexes to an empty string means that they will be
321 # omitted if they would otherwise appear in URLs. It just so
322 # happens that this is the right thing to do in the standard
323 # UI, in all the relevant cases.
324 $this->mPastTheEndIndex =
'';
325 $res->seek( $numRows - 1 );
326 $row =
$res->fetchRow();
327 $lastIndex = $row[$indexColumn];
332 $this->mPastTheEndRow =
null;
333 $this->mPastTheEndIndex =
'';
336 if ( $this->mIsBackwards ) {
337 $this->mIsFirst = ( $numRows <
$limit );
338 $this->mIsLast = $isFirst;
339 $this->mLastShown = $firstIndex;
340 $this->mFirstShown = $lastIndex;
342 $this->mIsFirst = $isFirst;
343 $this->mIsLast = ( $numRows <
$limit );
344 $this->mLastShown = $lastIndex;
345 $this->mFirstShown = $firstIndex;
355 return get_class( $this );
384 $fields = $info[
'fields'];
385 $conds = isset( $info[
'conds'] ) ? $info[
'conds'] :
array();
386 $options = isset( $info[
'options'] ) ? $info[
'options'] :
array();
387 $join_conds = isset( $info[
'join_conds'] ) ? $info[
'join_conds'] :
array();
388 $sortColumns = array_merge(
array( $this->mIndexField ), $this->mExtraSortFields );
390 $options[
'ORDER BY'] = $sortColumns;
391 $operator = $this->mIncludeOffset ?
'>=' :
'>';
394 foreach ( $sortColumns
as $col ) {
395 $orderBy[] = $col .
' DESC';
398 $operator = $this->mIncludeOffset ?
'<=' :
'<';
400 if ( $offset !=
'' ) {
401 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
421 if ( !$this->mQueryDone ) {
425 if ( $this->mResult->numRows() ) {
426 # Do any special query batches before display
430 # Don't use any extra rows returned by the query
435 if ( $this->mIsBackwards ) {
436 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
437 $this->mResult->seek( $i );
438 $row = $this->mResult->fetchObject();
442 $this->mResult->seek( 0 );
443 for ( $i = 0; $i < $numRows; $i++ ) {
444 $row = $this->mResult->fetchObject();
470 if ( in_array(
$type,
array(
'first',
'prev',
'next',
'last' ) ) ) {
471 # HTML5 rel attributes
472 $attrs[
'rel'] =
$type;
476 $attrs[
'class'] =
"mw-{$type}link";
533 if ( !isset( $this->mDefaultQuery ) ) {
534 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
535 unset( $this->mDefaultQuery[
'title'] );
536 unset( $this->mDefaultQuery[
'dir'] );
537 unset( $this->mDefaultQuery[
'offset'] );
538 unset( $this->mDefaultQuery[
'limit'] );
539 unset( $this->mDefaultQuery[
'order'] );
540 unset( $this->mDefaultQuery[
'month'] );
541 unset( $this->mDefaultQuery[
'year'] );
552 if ( !$this->mQueryDone ) {
555 return $this->mResult->numRows();
564 if ( !$this->mQueryDone ) {
568 # Don't announce the limit everywhere if it's the default
569 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null :
$this->mLimit;
571 if ( $this->mIsFirst ) {
577 'offset' => $this->mFirstShown,
580 $first =
array(
'limit' => $urlLimit );
582 if ( $this->mIsLast ) {
586 $next =
array(
'offset' => $this->mLastShown,
'limit' => $urlLimit );
587 $last =
array(
'dir' =>
'prev',
'limit' => $urlLimit );
603 if ( !$this->mQueryDone ) {
631 } elseif ( isset( $disabledTexts[
$type] ) ) {
643 if ( $this->mIsBackwards ) {
648 foreach ( $this->mLimitsShown
as $limit ) {
757 if ( isset( $this->mNavigationBar ) ) {
762 'prev' => $this->
msg(
'prevn' )->numParams( $this->mLimit )->escaped(),
763 'next' => $this->
msg(
'nextn' )->numParams( $this->mLimit )->escaped(),
764 'first' => $this->
msg(
'page_first' )->escaped(),
765 'last' => $this->
msg(
'page_last' )->escaped()
772 $limits = $lang->pipeList( $limitLinks );
774 $this->mNavigationBar = $this->
msg(
'parentheses' )->rawParams(
775 $lang->pipeList(
array( $pagingLinks[
'first'],
776 $pagingLinks[
'last'] ) ) )->escaped() .
" " .
777 $this->
msg(
'viewprevnext' )->rawParams( $pagingLinks[
'prev'],
778 $pagingLinks[
'next'], $limits )->escaped();
781 # Early return to avoid undue nesting
788 foreach ( array_keys( $msgs )
as $order ) {
792 $extra .= $this->
msg(
'pipe-separator' )->escaped();
795 if ( $order == $this->mOrderType ) {
796 $extra .= $this->
msg( $msgs[$order] )->escaped();
799 $this->
msg( $msgs[$order] )->escaped(),
800 array(
'order' => $order )
805 if ( $extra !==
'' ) {
806 $extra =
' ' . $this->
msg(
'parentheses' )->rawParams( $extra )->escaped();
807 $this->mNavigationBar .= $extra;
840 if ( isset( $this->mNavigationBar ) ) {
845 'prev' => $this->
msg(
'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
846 'next' => $this->
msg(
'pager-older-n' )->numParams( $this->mLimit )->escaped(),
847 'first' => $this->
msg(
'histlast' )->escaped(),
848 'last' => $this->
msg(
'histfirst' )->escaped()
853 $limits = $this->
getLanguage()->pipeList( $limitLinks );
854 $firstLastLinks = $this->
msg(
'parentheses' )->rawParams(
"{$pagingLinks['first']}" .
855 $this->
msg(
'pipe-separator' )->escaped() .
856 "{$pagingLinks['last']}" )->escaped();
858 $this->mNavigationBar = $firstLastLinks .
' ' .
859 $this->
msg(
'viewprevnext' )->rawParams(
860 $pagingLinks[
'prev'], $pagingLinks[
'next'], $limits )->escaped();
866 $year = intval( $year );
867 $month = intval( $month );
870 $this->mYear = $year > 0 ? $year :
false;
871 $this->mMonth = ( $month > 0 && $month < 13 ) ? $month :
false;
878 if ( !$this->mYear && !$this->mMonth ) {
882 if ( $this->mYear ) {
889 if ( $this->mMonth >
$timestamp->format(
'n' ) ) {
894 if ( $this->mMonth ) {
895 $month = $this->mMonth + 1;
908 if ( $year > 2032 ) {
912 $ymd = (int)sprintf(
"%04d%02d01", $year, $month );
914 if ( $ymd > 20320101 ) {
918 $this->mOffset = $this->mDb->timestamp(
"${ymd}000000" );
935 $this->mSort = $this->
getRequest()->getText(
'sort' );
936 if ( !array_key_exists( $this->mSort, $this->
getFieldNames() )
941 if ( $this->
getRequest()->getBool(
'asc' ) ) {
942 $this->mDefaultDirection =
false;
943 } elseif ( $this->
getRequest()->getBool(
'desc' ) ) {
944 $this->mDefaultDirection =
true;
947 parent::__construct();
962 foreach ( $fields
as $field =>
$name ) {
963 if ( strval(
$name ) ==
'' ) {
966 $query =
array(
'sort' => $field,
'limit' => $this->mLimit );
967 if ( $field == $this->mSort ) {
968 # This is the sorted column
969 # Prepare a link that goes in the other sort order
970 if ( $this->mDefaultDirection ) {
972 $image =
'Arr_d.png';
975 $alt = $this->
msg(
'descending_abbrev' )->escaped();
978 $image =
'Arr_u.png';
981 $alt = $this->
msg(
'ascending_abbrev' )->escaped();
983 $image =
"$wgStylePath/common/images/$image";
986 'alt' => $alt,
'src' => $image ) ) . htmlspecialchars(
$name ),
$query );
1010 return "</tbody></table>\n";
1019 $msgEmpty = $this->
msg(
'table_pager_empty' )->text();
1030 $this->mCurrentRow = $row;
1034 foreach ( $fieldNames
as $field =>
$name ) {
1035 $value = isset( $row->$field ) ? $row->$field :
null;
1038 if ( $formatted ==
'' ) {
1039 $formatted =
' ';
1072 if ( $class ===
'' ) {
1092 return array(
'class' =>
'TablePager_col_' . $field );
1108 return 'TablePager';
1116 return 'TablePager_nav';
1124 return 'TablePager_sort';
1138 $path =
"$wgStylePath/common/images";
1140 'first' =>
'table_pager_first',
1141 'prev' =>
'table_pager_prev',
1142 'next' =>
'table_pager_next',
1143 'last' =>
'table_pager_last',
1146 'first' =>
'arrow_first_25.png',
1147 'prev' =>
'arrow_left_25.png',
1148 'next' =>
'arrow_right_25.png',
1149 'last' =>
'arrow_last_25.png',
1151 $disabledImages =
array(
1152 'first' =>
'arrow_disabled_first_25.png',
1153 'prev' =>
'arrow_disabled_left_25.png',
1154 'next' =>
'arrow_disabled_right_25.png',
1155 'last' =>
'arrow_disabled_last_25.png',
1158 $keys = array_keys( $labels );
1159 $images = array_combine(
$keys, array_reverse( $images ) );
1160 $disabledImages = array_combine(
$keys, array_reverse( $disabledImages ) );
1163 $linkTexts =
array();
1164 $disabledTexts =
array();
1165 foreach ( $labels
as $type => $label ) {
1166 $msgLabel = $this->
msg( $label )->escaped();
1168 'alt' => $msgLabel ) ) .
"<br />$msgLabel";
1170 'alt' => $msgLabel ) ) .
"<br />$msgLabel";
1176 $width = 100 / count( $links ) .
'%';
1177 foreach ( $labels
as $type => $label ) {
1191 $select =
new XmlSelect(
'limit',
false, $this->mLimit );
1196 return $select->getHTML();
1207 # Add the current limit from the query string
1208 # to avoid that the limit is lost after clicking Go next time
1209 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
1211 sort( $this->mLimitsShown );
1214 foreach ( $this->mLimitsShown
as $key =>
$value ) {
1215 # The pair is either $index => $limit, in which case the $value
1216 # will be numeric, or $limit => $text, in which case the $value
1218 if ( is_int(
$value ) ) {
1239 $blacklist = (
array)$blacklist;
1241 foreach ( $blacklist
as $name ) {
1263 'action' => $wgScript
1275 # Make the select with some explanatory text
1276 $msgSubmit = $this->
msg(
'table_pager_limit_submit' )->escaped();
1278 return $this->
msg(
'table_pager_limit' )
1280 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .