171 # NB: the offset is quoted, not validated. It is treated as an
172 # arbitrary string to support the widest variety of index types. Be
173 # careful outputting it into HTML!
174 $this->mOffset = $this->mRequest->getText(
'offset' );
176 # Use consistent behavior for the limit options
177 $this->mDefaultLimit = $this->
getUser()->getIntOption(
'rclimit' );
178 if ( !$this->mLimit ) {
180 list( $this->mLimit, ) = $this->mRequest->getLimitOffset();
183 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
184 # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
189 $order = $this->mRequest->getVal(
'order' );
190 if ( is_array( $index ) && isset( $index[$order] ) ) {
191 $this->mOrderType = $order;
192 $this->mIndexField = $index[$order];
193 $this->mExtraSortFields = isset( $extraSort[$order] )
194 ? (array)$extraSort[$order]
196 } elseif ( is_array( $index ) ) {
197 # First element is the default
198 $this->mIndexField = reset( $index );
199 $this->mOrderType = key( $index );
200 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
201 ? (array)$extraSort[$this->mOrderType]
204 # $index is not an array
205 $this->mOrderType =
null;
206 $this->mIndexField = $index;
207 $this->mExtraSortFields = (array)$extraSort;
210 if ( !isset( $this->mDefaultDirection ) ) {
212 $this->mDefaultDirection = is_array( $dir )
234 # Use the child class name for profiling
235 $fname = __METHOD__ .
' (' . static::class .
')';
240 ? self::QUERY_ASCENDING
241 : self::QUERY_DESCENDING;
244 # Plus an extra row so that we can tell the "next" link should be shown
245 $queryLimit = $this->mLimit + 1;
247 if ( $this->mOffset ==
'' ) {
256 $isFirst = !$this->
reallyDoQuery( $this->mOffset, 1, $oppositeOrder )->numRows();
257 $this->mIncludeOffset = $oldIncludeOffset;
267 $this->mQueryDone =
true;
270 $this->mResult->rewind();
278 return ( $order === self::QUERY_ASCENDING )
296 $this->mOffset = $offset;
307 $limit = (int)$limit;
309 if ( $limit > 5000 ) {
313 $this->mLimit = $limit;
334 $this->mIncludeOffset = $include;
347 $numRows =
$res->numRows();
349 # Remove any table prefix from index field
350 $parts = explode(
'.', $this->mIndexField );
351 $indexColumn = end( $parts );
353 $row =
$res->fetchRow();
354 $firstIndex = $row[$indexColumn];
356 # Discard the extra result row if there is one
357 if ( $numRows > $this->mLimit && $numRows > 1 ) {
358 $res->seek( $numRows - 1 );
359 $this->mPastTheEndRow =
$res->fetchObject();
360 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
361 $res->seek( $numRows - 2 );
362 $row =
$res->fetchRow();
363 $lastIndex = $row[$indexColumn];
365 $this->mPastTheEndRow =
null;
366 # Setting indexes to an empty string means that they will be
367 # omitted if they would otherwise appear in URLs. It just so
368 # happens that this is the right thing to do in the standard
369 # UI, in all the relevant cases.
370 $this->mPastTheEndIndex =
'';
371 $res->seek( $numRows - 1 );
372 $row =
$res->fetchRow();
373 $lastIndex = $row[$indexColumn];
378 $this->mPastTheEndRow =
null;
379 $this->mPastTheEndIndex =
'';
382 if ( $this->mIsBackwards ) {
383 $this->mIsFirst = ( $numRows < $limit );
384 $this->mIsLast = $isFirst;
385 $this->mLastShown = $firstIndex;
386 $this->mFirstShown = $lastIndex;
388 $this->mIsFirst = $isFirst;
389 $this->mIsLast = ( $numRows < $limit );
390 $this->mLastShown = $lastIndex;
391 $this->mFirstShown = $firstIndex;
401 return static::class;
415 list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
418 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
434 $tables = $info[
'tables'];
435 $fields = $info[
'fields'];
436 $conds = $info[
'conds'] ?? [];
437 $options = $info[
'options'] ?? [];
438 $join_conds = $info[
'join_conds'] ?? [];
439 $sortColumns = array_merge( [ $this->mIndexField ], $this->mExtraSortFields );
440 if ( $order === self::QUERY_ASCENDING ) {
441 $options[
'ORDER BY'] = $sortColumns;
442 $operator = $this->mIncludeOffset ?
'>=' :
'>';
445 foreach ( $sortColumns as $col ) {
446 $orderBy[] = $col .
' DESC';
448 $options[
'ORDER BY'] = $orderBy;
449 $operator = $this->mIncludeOffset ?
'<=' :
'<';
451 if ( $offset !=
'' ) {
452 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
454 $options[
'LIMIT'] = intval( $limit );
455 return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
473 if ( !$this->mQueryDone ) {
477 if ( $this->mResult->numRows() ) {
478 # Do any special query batches before display
482 # Don't use any extra rows returned by the query
483 $numRows = min( $this->mResult->numRows(), $this->mLimit );
487 if ( $this->mIsBackwards ) {
488 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
489 $this->mResult->seek( $i );
490 $row = $this->mResult->fetchObject();
494 $this->mResult->seek( 0 );
495 for ( $i = 0; $i < $numRows; $i++ ) {
496 $row = $this->mResult->fetchObject();
517 if ( $query ===
null ) {
522 if ( in_array(
$type, [
'prev',
'next' ] ) ) {
523 $attrs[
'rel'] =
$type;
526 if ( in_array(
$type, [
'asc',
'desc' ] ) ) {
527 $attrs[
'title'] = $this->
msg(
$type ==
'asc' ?
'sort-ascending' :
'sort-descending' )->text();
531 $attrs[
'class'] =
"mw-{$type}link";
589 if ( !isset( $this->mDefaultQuery ) ) {
590 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
591 unset( $this->mDefaultQuery[
'title'] );
592 unset( $this->mDefaultQuery[
'dir'] );
593 unset( $this->mDefaultQuery[
'offset'] );
594 unset( $this->mDefaultQuery[
'limit'] );
595 unset( $this->mDefaultQuery[
'order'] );
596 unset( $this->mDefaultQuery[
'month'] );
597 unset( $this->mDefaultQuery[
'year'] );
608 if ( !$this->mQueryDone ) {
611 return $this->mResult->numRows();
620 if ( !$this->mQueryDone ) {
624 # Don't announce the limit everywhere if it's the default
625 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null :
$this->mLimit;
627 if ( $this->mIsFirst ) {
636 $first = [
'limit' => $urlLimit ];
638 if ( $this->mIsLast ) {
643 $last = [
'dir' =>
'prev',
'limit' => $urlLimit ];
659 if ( !$this->mQueryDone ) {
681 if ( $query !==
false ) {
687 } elseif ( isset( $disabledTexts[
$type] ) ) {
699 if ( $this->mIsBackwards ) {
704 foreach ( $this->mLimitsShown as $limit ) {
707 [
'offset' => $offset,
'limit' => $limit ],
806 array $query = [], $atend =
false
810 return $prevNext->buildPrevNextNavigation(
$title, $offset, $limit, $query, $atend );
814 if ( $this->linkRenderer ===
null ) {
815 $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();