165 # NB: the offset is quoted, not validated. It is treated as an
166 # arbitrary string to support the widest variety of index types. Be
167 # careful outputting it into HTML!
168 $this->mOffset = $this->mRequest->getText(
'offset' );
170 # Use consistent behavior for the limit options
171 $this->mDefaultLimit = $this->
getUser()->getIntOption(
'rclimit' );
172 if ( !$this->mLimit ) {
174 list( $this->mLimit, ) = $this->mRequest->getLimitOffset();
177 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
178 # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
183 $order = $this->mRequest->getVal(
'order' );
184 if ( is_array( $index ) && isset( $index[$order] ) ) {
185 $this->mOrderType = $order;
186 $this->mIndexField = $index[$order];
187 $this->mExtraSortFields = isset( $extraSort[$order] )
188 ? (
array)$extraSort[$order]
190 } elseif ( is_array( $index ) ) {
191 # First element is the default
192 $this->mIndexField = reset( $index );
193 $this->mOrderType =
key( $index );
194 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
195 ? (
array)$extraSort[$this->mOrderType]
198 # $index is not an array
199 $this->mOrderType =
null;
200 $this->mIndexField = $index;
201 $this->mExtraSortFields = (
array)$extraSort;
204 if ( !isset( $this->mDefaultDirection ) ) {
206 $this->mDefaultDirection = is_array( $dir )
227 # Use the child class name for profiling
233 ? self::QUERY_ASCENDING
234 : self::QUERY_DESCENDING;
237 # Plus an extra row so that we can tell the "next" link should be shown
238 $queryLimit = $this->mLimit + 1;
240 if ( $this->mOffset ==
'' ) {
249 $isFirst = !$this->
reallyDoQuery( $this->mOffset, 1, $oppositeOrder )->numRows();
250 $this->mIncludeOffset = $oldIncludeOffset;
260 $this->mQueryDone =
true;
263 $this->mResult->rewind();
271 return ( $order === self::QUERY_ASCENDING )
289 $this->mOffset = $offset;
300 $limit = (int)$limit;
302 if ( $limit > 5000 ) {
306 $this->mLimit = $limit;
327 $this->mIncludeOffset = $include;
340 $numRows =
$res->numRows();
342 # Remove any table prefix from index field
343 $parts = explode(
'.', $this->mIndexField );
344 $indexColumn = end( $parts );
346 $row =
$res->fetchRow();
347 $firstIndex = $row[$indexColumn];
349 # Discard the extra result row if there is one
350 if ( $numRows > $this->mLimit && $numRows > 1 ) {
351 $res->seek( $numRows - 1 );
352 $this->mPastTheEndRow =
$res->fetchObject();
353 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
354 $res->seek( $numRows - 2 );
355 $row =
$res->fetchRow();
356 $lastIndex = $row[$indexColumn];
358 $this->mPastTheEndRow =
null;
359 # Setting indexes to an empty string means that they will be
360 # omitted if they would otherwise appear in URLs. It just so
361 # happens that this is the right thing to do in the standard
362 # UI, in all the relevant cases.
363 $this->mPastTheEndIndex =
'';
364 $res->seek( $numRows - 1 );
365 $row =
$res->fetchRow();
366 $lastIndex = $row[$indexColumn];
371 $this->mPastTheEndRow =
null;
372 $this->mPastTheEndIndex =
'';
375 if ( $this->mIsBackwards ) {
376 $this->mIsFirst = ( $numRows < $limit );
377 $this->mIsLast = $isFirst;
378 $this->mLastShown = $firstIndex;
379 $this->mFirstShown = $lastIndex;
381 $this->mIsFirst = $isFirst;
382 $this->mIsLast = ( $numRows < $limit );
383 $this->mLastShown = $lastIndex;
384 $this->mFirstShown = $firstIndex;
428 $fields = $info[
'fields'];
429 $conds = $info[
'conds'] ?? [];
431 $join_conds = $info[
'join_conds'] ?? [];
432 $sortColumns = array_merge( [ $this->mIndexField ], $this->mExtraSortFields );
433 if ( $order === self::QUERY_ASCENDING ) {
434 $options[
'ORDER BY'] = $sortColumns;
435 $operator = $this->mIncludeOffset ?
'>=' :
'>';
438 foreach ( $sortColumns
as $col ) {
439 $orderBy[] = $col .
' DESC';
442 $operator = $this->mIncludeOffset ?
'<=' :
'<';
444 if ( $offset !=
'' ) {
445 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
447 $options[
'LIMIT'] = intval( $limit );
466 if ( !$this->mQueryDone ) {
470 if ( $this->mResult->numRows() ) {
471 # Do any special query batches before display
475 # Don't use any extra rows returned by the query
480 if ( $this->mIsBackwards ) {
481 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
482 $this->mResult->seek( $i );
483 $row = $this->mResult->fetchObject();
487 $this->mResult->seek( 0 );
488 for ( $i = 0; $i < $numRows; $i++ ) {
489 $row = $this->mResult->fetchObject();
515 if ( in_array(
$type, [
'prev',
'next' ] ) ) {
516 $attrs[
'rel'] =
$type;
519 if ( in_array(
$type, [
'asc',
'desc' ] ) ) {
520 $attrs[
'title'] = $this->
msg(
$type ==
'asc' ?
'sort-ascending' :
'sort-descending' )->text();
524 $attrs[
'class'] =
"mw-{$type}link";
582 if ( !isset( $this->mDefaultQuery ) ) {
583 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
584 unset( $this->mDefaultQuery[
'title'] );
585 unset( $this->mDefaultQuery[
'dir'] );
586 unset( $this->mDefaultQuery[
'offset'] );
587 unset( $this->mDefaultQuery[
'limit'] );
588 unset( $this->mDefaultQuery[
'order'] );
589 unset( $this->mDefaultQuery[
'month'] );
590 unset( $this->mDefaultQuery[
'year'] );
601 if ( !$this->mQueryDone ) {
604 return $this->mResult->numRows();
613 if ( !$this->mQueryDone ) {
617 # Don't announce the limit everywhere if it's the default
620 if ( $this->mIsFirst ) {
629 $first = [
'limit' => $urlLimit ];
631 if ( $this->mIsLast ) {
636 $last = [
'dir' =>
'prev',
'limit' => $urlLimit ];
652 if ( !$this->mQueryDone ) {
680 } elseif ( isset( $disabledTexts[
$type] ) ) {
692 if ( $this->mIsBackwards ) {
697 foreach ( $this->mLimitsShown
as $limit ) {
700 [
'offset' => $offset,
'limit' => $limit ],