Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / pager / ReverseChronologicalPager.php
blob5201cd9d874d2f87e4cb05642e6df98b98d16dbc
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
21 namespace MediaWiki\Pager;
23 use DateTime;
24 use MediaWiki\Html\Html;
25 use MediaWiki\Utils\MWTimestamp;
26 use Wikimedia\Timestamp\TimestampException;
28 /**
29 * IndexPager with a formatted navigation bar.
31 * @stable to extend
32 * @ingroup Pager
34 abstract class ReverseChronologicalPager extends IndexPager {
35 /** @var bool */
36 public $mDefaultDirection = IndexPager::DIR_DESCENDING;
37 /** @var bool Whether to group items by date */
38 public $mGroupByDate = false;
39 /** @var int */
40 public $mYear;
41 /** @var int */
42 public $mMonth;
43 /** @var int */
44 public $mDay;
45 /** @var string */
46 private $lastHeaderDate;
47 /** @var string */
48 protected $endOffset;
50 /**
51 * @param string $date
52 * @return string
54 protected function getHeaderRow( string $date ): string {
55 $headingClass = $this->isFirstHeaderRow() ?
56 // We use mw-index-pager- prefix here on the anticipation that this method will
57 // eventually be upstreamed to apply to other pagers. For now we constrain the
58 // change to ReverseChronologicalPager to reduce the risk of pages this touches
59 // in case there are any bugs.
60 'mw-index-pager-list-header-first mw-index-pager-list-header' :
61 'mw-index-pager-list-header';
63 $s = $this->isFirstHeaderRow() ? '' : $this->getEndGroup();
64 $s .= Html::element( 'h4', [
65 'class' => $headingClass,
67 $date
69 $s .= $this->getStartGroup();
70 return $s;
73 /**
74 * Determines if a header row is needed based on the current state of the IndexPager.
76 * @since 1.38
77 * @param string $date Formatted date header
78 * @return bool
80 protected function isHeaderRowNeeded( string $date ): bool {
81 if ( !$this->mGroupByDate ) {
82 return false;
84 return $date && $this->lastHeaderDate !== $date;
87 /**
88 * Determines whether the header row is the first that will be outputted to the page.
90 * @since 1.38
91 * @return bool
93 final protected function isFirstHeaderRow(): bool {
94 return $this->lastHeaderDate === null;
97 /**
98 * Returns the name of the timestamp field. Subclass can override this to provide the
99 * timestamp field if they are using a aliased field for getIndexField()
101 * @since 1.40
102 * @return string
104 public function getTimestampField() {
105 // This is a chronological pager, so the first column should be some kind of timestamp
106 return is_array( $this->mIndexField ) ? $this->mIndexField[0] : $this->mIndexField;
110 * Get date from the timestamp
112 * @since 1.38
113 * @param string $timestamp
114 * @return string Formatted date header
116 final protected function getDateFromTimestamp( string $timestamp ) {
117 return $this->getLanguage()->userDate( $timestamp, $this->getUser() );
121 * @inheritDoc
123 protected function getRow( $row ): string {
124 $s = '';
126 $timestampField = $this->getTimestampField();
127 $timestamp = $row->$timestampField ?? null;
128 $date = $timestamp ? $this->getDateFromTimestamp( $timestamp ) : null;
129 if ( $date && $this->isHeaderRowNeeded( $date ) ) {
130 $s .= $this->getHeaderRow( $date );
131 $this->lastHeaderDate = $date;
134 $s .= $this->formatRow( $row );
135 return $s;
139 * Start a new group of page rows.
141 * @stable to override
142 * @since 1.38
143 * @return string
145 protected function getStartGroup(): string {
146 return "<ul class=\"mw-contributions-list\">\n";
150 * End an existing group of page rows.
152 * @stable to override
153 * @since 1.38
154 * @return string
156 protected function getEndGroup(): string {
157 return '</ul>';
161 * @inheritDoc
163 protected function getFooter(): string {
164 return $this->getEndGroup();
168 * @stable to override
169 * @return string HTML
171 public function getNavigationBar() {
172 if ( !$this->isNavigationBarShown() ) {
173 return '';
176 if ( $this->mNavigationBar !== null ) {
177 return $this->mNavigationBar;
180 $navBuilder = $this->getNavigationBuilder()
181 ->setPrevMsg( 'pager-newer-n' )
182 ->setNextMsg( 'pager-older-n' )
183 ->setFirstMsg( 'histlast' )
184 ->setLastMsg( 'histfirst' );
186 $this->mNavigationBar = $navBuilder->getHtml();
188 return $this->mNavigationBar;
192 * Set and return the offset timestamp such that we can get all revisions with
193 * a timestamp up to the specified parameters.
195 * @stable to override
197 * @param int $year Year up to which we want revisions
198 * @param int $month Month up to which we want revisions
199 * @param int $day [optional] Day up to which we want revisions. Default is end of month.
200 * @return string|null Timestamp or null if year and month are false/invalid
202 public function getDateCond( $year, $month, $day = -1 ) {
203 $year = (int)$year;
204 $month = (int)$month;
205 $day = (int)$day;
207 // Basic validity checks for year and month
208 // If year and month are invalid, don't update the offset
209 if ( $year <= 0 && ( $month <= 0 || $month >= 13 ) ) {
210 return null;
213 $timestamp = self::getOffsetDate( $year, $month, $day );
215 try {
216 // The timestamp used for DB queries is at midnight of the *next* day after the selected date.
217 $selectedDate = new DateTime( $timestamp->getTimestamp( TS_ISO_8601 ) );
218 $selectedDate = $selectedDate->modify( '-1 day' );
220 $this->mYear = (int)$selectedDate->format( 'Y' );
221 $this->mMonth = (int)$selectedDate->format( 'm' );
222 $this->mDay = (int)$selectedDate->format( 'd' );
223 // Don't mess with mOffset which IndexPager uses
224 $this->endOffset = $this->mDb->timestamp( $timestamp->getTimestamp() );
225 } catch ( TimestampException $e ) {
226 // Invalid user provided timestamp (T149257)
227 return null;
230 return $this->endOffset;
234 * Core logic of determining the offset timestamp such that we can get all items with
235 * a timestamp up to the specified parameters. Given parameters for a day up to which to get
236 * items, this function finds the timestamp of the day just after the end of the range for use
237 * in a database strict inequality filter.
239 * This is separate from getDateCond so we can use this logic in other places, such as in
240 * RangeChronologicalPager, where this function is used to convert year/month/day filter options
241 * into a timestamp.
243 * @param int $year Year up to which we want revisions
244 * @param int $month Month up to which we want revisions
245 * @param int $day [optional] Day up to which we want revisions. Default is end of month.
246 * @return MWTimestamp Timestamp or null if year and month are false/invalid
248 public static function getOffsetDate( $year, $month, $day = -1 ) {
249 // Given an optional year, month, and day, we need to generate a timestamp
250 // to use as "WHERE rev_timestamp <= result"
251 // Examples: year = 2006 equals < 20070101 (+000000)
252 // year=2005, month=1 equals < 20050201
253 // year=2005, month=12 equals < 20060101
254 // year=2005, month=12, day=5 equals < 20051206
255 if ( $year <= 0 ) {
256 // If no year given, assume the current one
257 $timestamp = MWTimestamp::getInstance();
258 $year = $timestamp->format( 'Y' );
259 // If this month hasn't happened yet this year, go back to last year's month
260 if ( $month > $timestamp->format( 'n' ) ) {
261 $year--;
265 if ( $month && $month > 0 && $month < 13 ) {
266 // Day validity check after we have month and year checked
267 $day = checkdate( $month, $day, $year ) ? $day : false;
269 if ( $day && $day > 0 ) {
270 // If we have a day, we want up to the day immediately afterward
271 $day++;
273 // Did we overflow the current month?
274 if ( !checkdate( $month, $day, $year ) ) {
275 $day = 1;
276 $month++;
278 } else {
279 // If no day, assume beginning of next month
280 $day = 1;
281 $month++;
284 // Did we overflow the current year?
285 if ( $month > 12 ) {
286 $month = 1;
287 $year++;
290 } else {
291 // No month implies we want up to the end of the year in question
292 $month = 1;
293 $day = 1;
294 $year++;
297 $ymd = sprintf( "%04d%02d%02d", $year, $month, $day );
299 return MWTimestamp::getInstance( "{$ymd}000000" );
303 * Return the end offset, extensions can use this if they are not in the context of subclass.
305 * @since 1.40
306 * @return string
308 public function getEndOffset() {
309 return $this->endOffset;
313 * @inheritDoc
315 protected function buildQueryInfo( $offset, $limit, $order ) {
316 [ $tables, $fields, $conds, $fname, $options, $join_conds ] = parent::buildQueryInfo(
317 $offset,
318 $limit,
319 $order
321 if ( $this->endOffset ) {
322 $conds[] = $this->mDb->expr( $this->getTimestampField(), '<', $this->endOffset );
325 return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
329 /** @deprecated class alias since 1.41 */
330 class_alias( ReverseChronologicalPager::class, 'ReverseChronologicalPager' );