182 # NB: the offset is quoted, not validated. It is treated as an
183 # arbitrary string to support the widest variety of index types. Be
184 # careful outputting it into HTML!
185 $this->mOffset = $this->mRequest->getText(
'offset' );
187 # Use consistent behavior for the limit options
188 $this->mDefaultLimit = MediaWikiServices::getInstance()
189 ->getUserOptionsLookup()
190 ->getIntOption( $this->
getUser(),
'rclimit' );
191 if ( !$this->mLimit ) {
193 list( $this->mLimit, ) = $this->mRequest
194 ->getLimitOffsetForUser( $this->
getUser() );
197 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
198 # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
203 $order = $this->mRequest->getVal(
'order' );
205 if ( is_array( $index ) && isset( $index[$order] ) ) {
206 $this->mOrderType = $order;
207 $this->mIndexField = $index[$order];
208 $this->mExtraSortFields = isset( $extraSort[$order] )
209 ? (array)$extraSort[$order]
211 } elseif ( is_array( $index ) ) {
212 # First element is the default
213 $this->mIndexField = reset( $index );
214 $this->mOrderType = key( $index );
215 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
216 ? (array)$extraSort[$this->mOrderType]
219 # $index is not an array
220 $this->mOrderType =
null;
221 $this->mIndexField = $index;
222 $isSortAssociative = array_values( $extraSort ) !== $extraSort;
223 if ( $isSortAssociative ) {
224 $this->mExtraSortFields = isset( $extraSort[$index] )
225 ? (array)$extraSort[$index]
228 $this->mExtraSortFields = (array)$extraSort;
232 if ( !isset( $this->mDefaultDirection ) ) {
234 $this->mDefaultDirection = is_array( $dir )
259 ? self::QUERY_ASCENDING
263 # Plus an extra row so that we can tell the "next" link should be shown
264 $queryLimit = $this->mLimit + 1;
266 if ( $this->mOffset ==
'' ) {
275 $isFirst = !$this->
reallyDoQuery( $this->mOffset, 1, $oppositeOrder )->numRows();
276 $this->mIncludeOffset = $oldIncludeOffset;
286 $this->mQueryDone =
true;
289 $this->mResult->rewind();
370 $numRows =
$res->numRows();
374 $this->mPastTheEndIndex = [];
375 $this->mPastTheEndRow =
null;
378 $indexColumns = array_map(
static function ( $v ) {
380 $parts = explode(
'.', $v );
381 return end( $parts );
382 }, (array)$this->mIndexField );
384 $row =
$res->fetchRow();
385 foreach ( $indexColumns as $indexColumn ) {
386 $firstIndex[] = $row[$indexColumn];
389 # Discard the extra result row if there is one
390 if ( $numRows > $this->mLimit && $numRows > 1 ) {
391 $res->seek( $numRows - 1 );
392 $this->mPastTheEndRow =
$res->fetchObject();
393 foreach ( $indexColumns as $indexColumn ) {
394 $this->mPastTheEndIndex[] = $this->mPastTheEndRow->$indexColumn;
396 $res->seek( $numRows - 2 );
397 $row =
$res->fetchRow();
398 foreach ( $indexColumns as $indexColumn ) {
399 $lastIndex[] = $row[$indexColumn];
402 $this->mPastTheEndRow =
null;
403 $res->seek( $numRows - 1 );
404 $row =
$res->fetchRow();
405 foreach ( $indexColumns as $indexColumn ) {
406 $lastIndex[] = $row[$indexColumn];
411 if ( $this->mIsBackwards ) {
412 $this->mIsFirst = ( $numRows < $limit );
413 $this->mIsLast = $isFirst;
414 $this->mLastShown = $firstIndex;
415 $this->mFirstShown = $lastIndex;
417 $this->mIsFirst = $isFirst;
418 $this->mIsLast = ( $numRows < $limit );
419 $this->mLastShown = $lastIndex;
420 $this->mFirstShown = $firstIndex;
448 list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
451 return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
469 $tables = $info[
'tables'];
470 $fields = $info[
'fields'];
471 $conds = $info[
'conds'] ?? [];
472 $options = $info[
'options'] ?? [];
473 $join_conds = $info[
'join_conds'] ?? [];
474 $indexColumns = (array)$this->mIndexField;
475 $sortColumns = array_merge( $indexColumns, $this->mExtraSortFields );
477 if ( $order === self::QUERY_ASCENDING ) {
478 $options[
'ORDER BY'] = $sortColumns;
479 $operator = $this->mIncludeOffset ?
'>=' :
'>';
482 foreach ( $sortColumns as $col ) {
483 $orderBy[] = $col .
' DESC';
485 $options[
'ORDER BY'] = $orderBy;
486 $operator = $this->mIncludeOffset ?
'<=' :
'<';
488 if ( $offset !=
'' ) {
489 $offsets = explode(
'|', $offset, count( $indexColumns ) );
497 $options[
'LIMIT'] = intval( $limit );
498 return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
540 for ( $i = 1; $i <= count( $offsets ); $i++ ) {
542 array_slice( $offsets, 0, $i ),
543 array_slice( $columns, 0, $i ),
547 return $this->mDb->makeList( $innerConds, IDatabase::LIST_OR );
561 while ( count( $offsets ) > 1 ) {
562 $conds[] = $columns[0] .
'=' . $this->mDb->addQuotes( $offsets[0] );
563 array_shift( $columns );
564 array_shift( $offsets );
566 $conds[] = $columns[0] . $operator . $this->mDb->addQuotes( $offsets[0] );
567 return $this->mDb->makeList( $conds, IDatabase::LIST_AND );
589 if ( !$this->mQueryDone ) {
593 if ( $this->mResult->numRows() ) {
594 # Do any special query batches before display
598 # Don't use any extra rows returned by the query
599 $numRows = min( $this->mResult->numRows(), $this->mLimit );
603 if ( $this->mIsBackwards ) {
604 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
605 $this->mResult->seek( $i );
606 $row = $this->mResult->fetchObject();
610 $this->mResult->seek( 0 );
611 for ( $i = 0; $i < $numRows; $i++ ) {
612 $row = $this->mResult->fetchObject();
715 if ( !isset( $this->mDefaultQuery ) ) {
716 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
717 unset( $this->mDefaultQuery[
'title'] );
718 unset( $this->mDefaultQuery[
'dir'] );
719 unset( $this->mDefaultQuery[
'offset'] );
720 unset( $this->mDefaultQuery[
'limit'] );
721 unset( $this->mDefaultQuery[
'order'] );
722 unset( $this->mDefaultQuery[
'month'] );
723 unset( $this->mDefaultQuery[
'year'] );
748 if ( !$this->mQueryDone ) {
752 # Don't announce the limit everywhere if it's the default
753 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null :
$this->mLimit;
755 if ( $this->mIsFirst ) {
761 'offset' => implode(
'|', (array)$this->mFirstShown ),
764 $first = [
'limit' => $urlLimit ];
766 if ( $this->mIsLast ) {
770 $next = [
'offset' => implode(
'|', (array)$this->mLastShown ),
'limit' => $urlLimit ];
771 $last = [
'dir' =>
'prev',
'limit' => $urlLimit ];