Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
13.79% |
12 / 87 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
ReverseChronologicalPager | |
13.95% |
12 / 86 |
|
0.00% |
0 / 14 |
1008.01 | |
0.00% |
0 / 1 |
getHeaderRow | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
isHeaderRowNeeded | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
isFirstHeaderRow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTimestampField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getDateFromTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRow | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
getStartGroup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getEndGroup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFooter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNavigationBar | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
getDateCond | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
5.20 | |||
getOffsetDate | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
132 | |||
getEndOffset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
buildQueryInfo | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 |
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. |
7 | * |
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. |
12 | * |
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 |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Pager; |
22 | |
23 | use DateTime; |
24 | use MediaWiki\Html\Html; |
25 | use MediaWiki\Utils\MWTimestamp; |
26 | use Wikimedia\Timestamp\TimestampException; |
27 | |
28 | /** |
29 | * IndexPager with a formatted navigation bar. |
30 | * |
31 | * @stable to extend |
32 | * @ingroup Pager |
33 | */ |
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; |
49 | |
50 | protected function getHeaderRow( string $date ): string { |
51 | $headingClass = $this->isFirstHeaderRow() ? |
52 | // We use mw-index-pager- prefix here on the anticipation that this method will |
53 | // eventually be upstreamed to apply to other pagers. For now we constrain the |
54 | // change to ReverseChronologicalPager to reduce the risk of pages this touches |
55 | // in case there are any bugs. |
56 | 'mw-index-pager-list-header-first mw-index-pager-list-header' : |
57 | 'mw-index-pager-list-header'; |
58 | |
59 | $s = $this->isFirstHeaderRow() ? '' : $this->getEndGroup(); |
60 | $s .= Html::element( 'h4', [ |
61 | 'class' => $headingClass, |
62 | ], |
63 | $date |
64 | ); |
65 | $s .= $this->getStartGroup(); |
66 | return $s; |
67 | } |
68 | |
69 | /** |
70 | * Determines if a header row is needed based on the current state of the IndexPager. |
71 | * |
72 | * @since 1.38 |
73 | * @param string $date Formatted date header |
74 | * @return bool |
75 | */ |
76 | protected function isHeaderRowNeeded( string $date ): bool { |
77 | if ( !$this->mGroupByDate ) { |
78 | return false; |
79 | } |
80 | return $date && $this->lastHeaderDate !== $date; |
81 | } |
82 | |
83 | /** |
84 | * Determines whether the header row is the first that will be outputted to the page. |
85 | * |
86 | * @since 1.38 |
87 | * @return bool |
88 | */ |
89 | final protected function isFirstHeaderRow(): bool { |
90 | return $this->lastHeaderDate === null; |
91 | } |
92 | |
93 | /** |
94 | * Returns the name of the timestamp field. Subclass can override this to provide the |
95 | * timestamp field if they are using a aliased field for getIndexField() |
96 | * |
97 | * @since 1.40 |
98 | * @return string |
99 | */ |
100 | public function getTimestampField() { |
101 | // This is a chronological pager, so the first column should be some kind of timestamp |
102 | return is_array( $this->mIndexField ) ? $this->mIndexField[0] : $this->mIndexField; |
103 | } |
104 | |
105 | /** |
106 | * Get date from the timestamp |
107 | * |
108 | * @since 1.38 |
109 | * @param string $timestamp |
110 | * @return string Formatted date header |
111 | */ |
112 | final protected function getDateFromTimestamp( string $timestamp ) { |
113 | return $this->getLanguage()->userDate( $timestamp, $this->getUser() ); |
114 | } |
115 | |
116 | /** |
117 | * @inheritDoc |
118 | */ |
119 | protected function getRow( $row ): string { |
120 | $s = ''; |
121 | |
122 | $timestampField = $this->getTimestampField(); |
123 | $timestamp = $row->$timestampField ?? null; |
124 | $date = $timestamp ? $this->getDateFromTimestamp( $timestamp ) : null; |
125 | if ( $date && $this->isHeaderRowNeeded( $date ) ) { |
126 | $s .= $this->getHeaderRow( $date ); |
127 | $this->lastHeaderDate = $date; |
128 | } |
129 | |
130 | $s .= $this->formatRow( $row ); |
131 | return $s; |
132 | } |
133 | |
134 | /** |
135 | * Start a new group of page rows. |
136 | * |
137 | * @stable to override |
138 | * @since 1.38 |
139 | * @return string |
140 | */ |
141 | protected function getStartGroup(): string { |
142 | return "<ul class=\"mw-contributions-list\">\n"; |
143 | } |
144 | |
145 | /** |
146 | * End an existing group of page rows. |
147 | * |
148 | * @stable to override |
149 | * @since 1.38 |
150 | * @return string |
151 | */ |
152 | protected function getEndGroup(): string { |
153 | return '</ul>'; |
154 | } |
155 | |
156 | /** |
157 | * @inheritDoc |
158 | */ |
159 | protected function getFooter(): string { |
160 | return $this->getEndGroup(); |
161 | } |
162 | |
163 | /** |
164 | * @stable to override |
165 | * @return string HTML |
166 | */ |
167 | public function getNavigationBar() { |
168 | if ( !$this->isNavigationBarShown() ) { |
169 | return ''; |
170 | } |
171 | |
172 | if ( $this->mNavigationBar !== null ) { |
173 | return $this->mNavigationBar; |
174 | } |
175 | |
176 | $navBuilder = $this->getNavigationBuilder() |
177 | ->setPrevMsg( 'pager-newer-n' ) |
178 | ->setNextMsg( 'pager-older-n' ) |
179 | ->setFirstMsg( 'histlast' ) |
180 | ->setLastMsg( 'histfirst' ); |
181 | |
182 | $this->mNavigationBar = $navBuilder->getHtml(); |
183 | |
184 | return $this->mNavigationBar; |
185 | } |
186 | |
187 | /** |
188 | * Set and return the offset timestamp such that we can get all revisions with |
189 | * a timestamp up to the specified parameters. |
190 | * |
191 | * @stable to override |
192 | * |
193 | * @param int $year Year up to which we want revisions |
194 | * @param int $month Month up to which we want revisions |
195 | * @param int $day [optional] Day up to which we want revisions. Default is end of month. |
196 | * @return string|null Timestamp or null if year and month are false/invalid |
197 | */ |
198 | public function getDateCond( $year, $month, $day = -1 ) { |
199 | $year = (int)$year; |
200 | $month = (int)$month; |
201 | $day = (int)$day; |
202 | |
203 | // Basic validity checks for year and month |
204 | // If year and month are invalid, don't update the offset |
205 | if ( $year <= 0 && ( $month <= 0 || $month >= 13 ) ) { |
206 | return null; |
207 | } |
208 | |
209 | $timestamp = self::getOffsetDate( $year, $month, $day ); |
210 | |
211 | try { |
212 | // The timestamp used for DB queries is at midnight of the *next* day after the selected date. |
213 | $selectedDate = new DateTime( $timestamp->getTimestamp( TS_ISO_8601 ) ); |
214 | $selectedDate = $selectedDate->modify( '-1 day' ); |
215 | |
216 | $this->mYear = (int)$selectedDate->format( 'Y' ); |
217 | $this->mMonth = (int)$selectedDate->format( 'm' ); |
218 | $this->mDay = (int)$selectedDate->format( 'd' ); |
219 | // Don't mess with mOffset which IndexPager uses |
220 | $this->endOffset = $this->mDb->timestamp( $timestamp->getTimestamp() ); |
221 | } catch ( TimestampException $e ) { |
222 | // Invalid user provided timestamp (T149257) |
223 | return null; |
224 | } |
225 | |
226 | return $this->endOffset; |
227 | } |
228 | |
229 | /** |
230 | * Core logic of determining the offset timestamp such that we can get all items with |
231 | * a timestamp up to the specified parameters. Given parameters for a day up to which to get |
232 | * items, this function finds the timestamp of the day just after the end of the range for use |
233 | * in a database strict inequality filter. |
234 | * |
235 | * This is separate from getDateCond so we can use this logic in other places, such as in |
236 | * RangeChronologicalPager, where this function is used to convert year/month/day filter options |
237 | * into a timestamp. |
238 | * |
239 | * @param int $year Year up to which we want revisions |
240 | * @param int $month Month up to which we want revisions |
241 | * @param int $day [optional] Day up to which we want revisions. Default is end of month. |
242 | * @return MWTimestamp Timestamp or null if year and month are false/invalid |
243 | */ |
244 | public static function getOffsetDate( $year, $month, $day = -1 ) { |
245 | // Given an optional year, month, and day, we need to generate a timestamp |
246 | // to use as "WHERE rev_timestamp <= result" |
247 | // Examples: year = 2006 equals < 20070101 (+000000) |
248 | // year=2005, month=1 equals < 20050201 |
249 | // year=2005, month=12 equals < 20060101 |
250 | // year=2005, month=12, day=5 equals < 20051206 |
251 | if ( $year <= 0 ) { |
252 | // If no year given, assume the current one |
253 | $timestamp = MWTimestamp::getInstance(); |
254 | $year = $timestamp->format( 'Y' ); |
255 | // If this month hasn't happened yet this year, go back to last year's month |
256 | if ( $month > $timestamp->format( 'n' ) ) { |
257 | $year--; |
258 | } |
259 | } |
260 | |
261 | if ( $month && $month > 0 && $month < 13 ) { |
262 | // Day validity check after we have month and year checked |
263 | $day = checkdate( $month, $day, $year ) ? $day : false; |
264 | |
265 | if ( $day && $day > 0 ) { |
266 | // If we have a day, we want up to the day immediately afterward |
267 | $day++; |
268 | |
269 | // Did we overflow the current month? |
270 | if ( !checkdate( $month, $day, $year ) ) { |
271 | $day = 1; |
272 | $month++; |
273 | } |
274 | } else { |
275 | // If no day, assume beginning of next month |
276 | $day = 1; |
277 | $month++; |
278 | } |
279 | |
280 | // Did we overflow the current year? |
281 | if ( $month > 12 ) { |
282 | $month = 1; |
283 | $year++; |
284 | } |
285 | |
286 | } else { |
287 | // No month implies we want up to the end of the year in question |
288 | $month = 1; |
289 | $day = 1; |
290 | $year++; |
291 | } |
292 | |
293 | $ymd = sprintf( "%04d%02d%02d", $year, $month, $day ); |
294 | |
295 | return MWTimestamp::getInstance( "{$ymd}000000" ); |
296 | } |
297 | |
298 | /** |
299 | * Return the end offset, extensions can use this if they are not in the context of subclass. |
300 | * |
301 | * @since 1.40 |
302 | * @return string |
303 | */ |
304 | public function getEndOffset() { |
305 | return $this->endOffset; |
306 | } |
307 | |
308 | /** |
309 | * @inheritDoc |
310 | */ |
311 | protected function buildQueryInfo( $offset, $limit, $order ) { |
312 | [ $tables, $fields, $conds, $fname, $options, $join_conds ] = parent::buildQueryInfo( |
313 | $offset, |
314 | $limit, |
315 | $order |
316 | ); |
317 | if ( $this->endOffset ) { |
318 | $conds[] = $this->mDb->expr( $this->getTimestampField(), '<', $this->endOffset ); |
319 | } |
320 | |
321 | return [ $tables, $fields, $conds, $fname, $options, $join_conds ]; |
322 | } |
323 | } |
324 | |
325 | /** @deprecated class alias since 1.41 */ |
326 | class_alias( ReverseChronologicalPager::class, 'ReverseChronologicalPager' ); |