139 # NB: the offset is quoted, not validated. It is treated as an
140 # arbitrary string to support the widest variety of index types. Be
141 # careful outputting it into HTML!
142 $this->mOffset = $this->mRequest->getText(
'offset' );
144 # Use consistent behavior for the limit options
145 $this->mDefaultLimit = $this->
getUser()->getIntOption(
'rclimit' );
146 if ( !$this->mLimit ) {
148 list( $this->mLimit, ) = $this->mRequest->getLimitOffset();
151 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
152 # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
157 $order = $this->mRequest->getVal(
'order' );
158 if ( is_array( $index ) && isset( $index[$order] ) ) {
159 $this->mOrderType = $order;
160 $this->mIndexField = $index[$order];
161 $this->mExtraSortFields = isset( $extraSort[$order] )
162 ? (
array)$extraSort[$order]
164 } elseif ( is_array( $index ) ) {
165 # First element is the default
166 $this->mIndexField = reset( $index );
167 $this->mOrderType =
key( $index );
168 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
169 ? (
array)$extraSort[$this->mOrderType]
172 # $index is not an array
173 $this->mOrderType =
null;
174 $this->mIndexField = $index;
175 $this->mExtraSortFields = (
array)$extraSort;
178 if ( !isset( $this->mDefaultDirection ) ) {
180 $this->mDefaultDirection = is_array( $dir )
201 # Use the child class name for profiling
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();
248 $this->mOffset = $offset;
259 $limit = (int)$limit;
261 if ( $limit > 5000 ) {
265 $this->mLimit = $limit;
286 $this->mIncludeOffset = $include;
299 $numRows =
$res->numRows();
301 # Remove any table prefix from index field
302 $parts = explode(
'.', $this->mIndexField );
303 $indexColumn = end( $parts );
305 $row =
$res->fetchRow();
306 $firstIndex = $row[$indexColumn];
308 # Discard the extra result row if there is one
309 if ( $numRows > $this->mLimit && $numRows > 1 ) {
310 $res->seek( $numRows - 1 );
311 $this->mPastTheEndRow =
$res->fetchObject();
312 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
313 $res->seek( $numRows - 2 );
314 $row =
$res->fetchRow();
315 $lastIndex = $row[$indexColumn];
317 $this->mPastTheEndRow =
null;
318 # Setting indexes to an empty string means that they will be
319 # omitted if they would otherwise appear in URLs. It just so
320 # happens that this is the right thing to do in the standard
321 # UI, in all the relevant cases.
322 $this->mPastTheEndIndex =
'';
323 $res->seek( $numRows - 1 );
324 $row =
$res->fetchRow();
325 $lastIndex = $row[$indexColumn];
330 $this->mPastTheEndRow =
null;
331 $this->mPastTheEndIndex =
'';
334 if ( $this->mIsBackwards ) {
335 $this->mIsFirst = ( $numRows < $limit );
336 $this->mIsLast = $isFirst;
337 $this->mLastShown = $firstIndex;
338 $this->mFirstShown = $lastIndex;
340 $this->mIsFirst = $isFirst;
341 $this->mIsLast = ( $numRows < $limit );
342 $this->mLastShown = $lastIndex;
343 $this->mFirstShown = $firstIndex;
384 $fields = $info[
'fields'];
385 $conds = $info[
'conds'] ?? [];
387 $join_conds = $info[
'join_conds'] ?? [];
388 $sortColumns = array_merge( [ $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 );
403 $options[
'LIMIT'] = intval( $limit );
422 if ( !$this->mQueryDone ) {
426 if ( $this->mResult->numRows() ) {
427 # Do any special query batches before display
431 # Don't use any extra rows returned by the query
436 if ( $this->mIsBackwards ) {
437 for ( $i = $numRows - 1; $i >= 0; $i-- ) {
438 $this->mResult->seek( $i );
439 $row = $this->mResult->fetchObject();
443 $this->mResult->seek( 0 );
444 for ( $i = 0; $i < $numRows; $i++ ) {
445 $row = $this->mResult->fetchObject();
471 if ( in_array(
$type, [
'prev',
'next' ] ) ) {
472 $attrs[
'rel'] =
$type;
475 if ( in_array(
$type, [
'asc',
'desc' ] ) ) {
476 $attrs[
'title'] = $this->
msg(
$type ==
'asc' ?
'sort-ascending' :
'sort-descending' )->text();
480 $attrs[
'class'] =
"mw-{$type}link";
538 if ( !isset( $this->mDefaultQuery ) ) {
539 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
540 unset( $this->mDefaultQuery[
'title'] );
541 unset( $this->mDefaultQuery[
'dir'] );
542 unset( $this->mDefaultQuery[
'offset'] );
543 unset( $this->mDefaultQuery[
'limit'] );
544 unset( $this->mDefaultQuery[
'order'] );
545 unset( $this->mDefaultQuery[
'month'] );
546 unset( $this->mDefaultQuery[
'year'] );
557 if ( !$this->mQueryDone ) {
560 return $this->mResult->numRows();
569 if ( !$this->mQueryDone ) {
573 # Don't announce the limit everywhere if it's the default
574 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null :
$this->mLimit;
576 if ( $this->mIsFirst ) {
585 $first = [
'limit' => $urlLimit ];
587 if ( $this->mIsLast ) {
592 $last = [
'dir' =>
'prev',
'limit' => $urlLimit ];
608 if ( !$this->mQueryDone ) {
636 } elseif ( isset( $disabledTexts[
$type] ) ) {
648 if ( $this->mIsBackwards ) {
653 foreach ( $this->mLimitsShown
as $limit ) {
656 [
'offset' => $offset,
'limit' => $limit ],