138 # NB: the offset is quoted, not validated. It is treated as an
139 # arbitrary string to support the widest variety of index types. Be
140 # careful outputting it into HTML!
141 $this->mOffset = $this->mRequest->getText(
'offset' );
143 # Use consistent behavior for the limit options
144 $this->mDefaultLimit = $this->
getUser()->getIntOption(
'rclimit' );
145 if ( !$this->mLimit ) {
147 list( $this->mLimit, ) = $this->mRequest->getLimitOffset();
150 $this->mIsBackwards = ( $this->mRequest->getVal(
'dir' ) ==
'prev' );
151 # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
156 $order = $this->mRequest->getVal(
'order' );
157 if ( is_array( $index ) && isset( $index[$order] ) ) {
158 $this->mOrderType = $order;
159 $this->mIndexField = $index[$order];
160 $this->mExtraSortFields = isset( $extraSort[$order] )
161 ? (
array)$extraSort[$order]
163 } elseif ( is_array( $index ) ) {
164 # First element is the default
166 list( $this->mOrderType, $this->mIndexField ) = each( $index );
167 $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
168 ? (
array)$extraSort[$this->mOrderType]
171 # $index is not an array
172 $this->mOrderType =
null;
173 $this->mIndexField = $index;
174 $this->mExtraSortFields = (
array)$extraSort;
177 if ( !isset( $this->mDefaultDirection ) ) {
179 $this->mDefaultDirection = is_array(
$dir )
200 # Use the child class name for profiling
206 # Plus an extra row so that we can tell the "next" link should be shown
207 $queryLimit = $this->mLimit + 1;
209 if ( $this->mOffset ==
'' ) {
217 $isFirst = !$this->
reallyDoQuery( $this->mOffset, 1, !$descending )->numRows();
218 $this->mIncludeOffset = $oldIncludeOffset;
228 $this->mQueryDone =
true;
231 $this->mResult->rewind();
247 $this->mOffset = $offset;
258 $limit = (int)$limit;
260 if ( $limit > 5000 ) {
264 $this->mLimit = $limit;
285 $this->mIncludeOffset = $include;
298 $numRows =
$res->numRows();
300 # Remove any table prefix from index field
301 $parts = explode(
'.', $this->mIndexField );
302 $indexColumn = end( $parts );
304 $row =
$res->fetchRow();
305 $firstIndex = $row[$indexColumn];
307 # Discard the extra result row if there is one
308 if ( $numRows > $this->mLimit && $numRows > 1 ) {
309 $res->seek( $numRows - 1 );
310 $this->mPastTheEndRow =
$res->fetchObject();
311 $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
312 $res->seek( $numRows - 2 );
313 $row =
$res->fetchRow();
314 $lastIndex = $row[$indexColumn];
316 $this->mPastTheEndRow =
null;
317 # Setting indexes to an empty string means that they will be
318 # omitted if they would otherwise appear in URLs. It just so
319 # happens that this is the right thing to do in the standard
320 # UI, in all the relevant cases.
321 $this->mPastTheEndIndex =
'';
322 $res->seek( $numRows - 1 );
323 $row =
$res->fetchRow();
324 $lastIndex = $row[$indexColumn];
329 $this->mPastTheEndRow =
null;
330 $this->mPastTheEndIndex =
'';
333 if ( $this->mIsBackwards ) {
334 $this->mIsFirst = ( $numRows < $limit );
335 $this->mIsLast = $isFirst;
336 $this->mLastShown = $firstIndex;
337 $this->mFirstShown = $lastIndex;
339 $this->mIsFirst = $isFirst;
340 $this->mIsLast = ( $numRows < $limit );
341 $this->mLastShown = $lastIndex;
342 $this->mFirstShown = $firstIndex;
383 $fields = $info[
'fields'];
384 $conds = isset( $info[
'conds'] ) ? $info[
'conds'] : [];
385 $options = isset( $info[
'options'] ) ? $info[
'options'] : [];
386 $join_conds = isset( $info[
'join_conds'] ) ? $info[
'join_conds'] : [];
387 $sortColumns = array_merge( [ $this->mIndexField ], $this->mExtraSortFields );
389 $options[
'ORDER BY'] = $sortColumns;
390 $operator = $this->mIncludeOffset ?
'>=' :
'>';
393 foreach ( $sortColumns
as $col ) {
394 $orderBy[] = $col .
' DESC';
397 $operator = $this->mIncludeOffset ?
'<=' :
'<';
399 if ( $offset !=
'' ) {
400 $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
402 $options[
'LIMIT'] = intval( $limit );
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, [
'prev',
'next' ] ) ) {
471 $attrs[
'rel'] =
$type;
474 if ( in_array(
$type, [
'asc',
'desc' ] ) ) {
475 $attrs[
'title'] =
wfMessage(
$type ==
'asc' ?
'sort-ascending' :
'sort-descending' )->text();
479 $attrs[
'class'] =
"mw-{$type}link";
537 if ( !isset( $this->mDefaultQuery ) ) {
538 $this->mDefaultQuery = $this->
getRequest()->getQueryValues();
539 unset( $this->mDefaultQuery[
'title'] );
540 unset( $this->mDefaultQuery[
'dir'] );
541 unset( $this->mDefaultQuery[
'offset'] );
542 unset( $this->mDefaultQuery[
'limit'] );
543 unset( $this->mDefaultQuery[
'order'] );
544 unset( $this->mDefaultQuery[
'month'] );
545 unset( $this->mDefaultQuery[
'year'] );
556 if ( !$this->mQueryDone ) {
559 return $this->mResult->numRows();
568 if ( !$this->mQueryDone ) {
572 # Don't announce the limit everywhere if it's the default
573 $urlLimit = $this->mLimit == $this->mDefaultLimit ? null :
$this->mLimit;
575 if ( $this->mIsFirst ) {
584 $first = [
'limit' => $urlLimit ];
586 if ( $this->mIsLast ) {
591 $last = [
'dir' =>
'prev',
'limit' => $urlLimit ];
607 if ( !$this->mQueryDone ) {
635 } elseif ( isset( $disabledTexts[
$type] ) ) {
647 if ( $this->mIsBackwards ) {
652 foreach ( $this->mLimitsShown
as $limit ) {
655 [
'offset' => $offset,
'limit' => $limit ],