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 | /** |
51 | * @param string $date |
52 | * @return string |
53 | */ |
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'; |
62 | |
63 | $s = $this->isFirstHeaderRow() ? '' : $this->getEndGroup(); |
64 | $s .= Html::element( 'h4', [ |
65 | 'class' => $headingClass, |
66 | ], |
67 | $date |
68 | ); |
69 | $s .= $this->getStartGroup(); |
70 | return $s; |
71 | } |
72 | |
73 | /** |
74 | * Determines if a header row is needed based on the current state of the IndexPager. |
75 | * |
76 | * @since 1.38 |
77 | * @param string $date Formatted date header |
78 | * @return bool |
79 | */ |
80 | protected function isHeaderRowNeeded( string $date ): bool { |
81 | if ( !$this->mGroupByDate ) { |
82 | return false; |
83 | } |
84 | return $date && $this->lastHeaderDate !== $date; |
85 | } |
86 | |
87 | /** |
88 | * Determines whether the header row is the first that will be outputted to the page. |
89 | * |
90 | * @since 1.38 |
91 | * @return bool |
92 | */ |
93 | final protected function isFirstHeaderRow(): bool { |
94 | return $this->lastHeaderDate === null; |
95 | } |
96 | |
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() |
100 | * |
101 | * @since 1.40 |
102 | * @return string |
103 | */ |
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; |
107 | } |
108 | |
109 | /** |
110 | * Get date from the timestamp |
111 | * |
112 | * @since 1.38 |
113 | * @param string $timestamp |
114 | * @return string Formatted date header |
115 | */ |
116 | final protected function getDateFromTimestamp( string $timestamp ) { |
117 | return $this->getLanguage()->userDate( $timestamp, $this->getUser() ); |
118 | } |
119 | |
120 | /** |
121 | * @inheritDoc |
122 | */ |
123 | protected function getRow( $row ): string { |
124 | $s = ''; |
125 | |
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; |
132 | } |
133 | |
134 | $s .= $this->formatRow( $row ); |
135 | return $s; |
136 | } |
137 | |
138 | /** |
139 | * Start a new group of page rows. |
140 | * |
141 | * @stable to override |
142 | * @since 1.38 |
143 | * @return string |
144 | */ |
145 | protected function getStartGroup(): string { |
146 | return "<ul class=\"mw-contributions-list\">\n"; |
147 | } |
148 | |
149 | /** |
150 | * End an existing group of page rows. |
151 | * |
152 | * @stable to override |
153 | * @since 1.38 |
154 | * @return string |
155 | */ |
156 | protected function getEndGroup(): string { |
157 | return '</ul>'; |
158 | } |
159 | |
160 | /** |
161 | * @inheritDoc |
162 | */ |
163 | protected function getFooter(): string { |
164 | return $this->getEndGroup(); |
165 | } |
166 | |
167 | /** |
168 | * @stable to override |
169 | * @return string HTML |
170 | */ |
171 | public function getNavigationBar() { |
172 | if ( !$this->isNavigationBarShown() ) { |
173 | return ''; |
174 | } |
175 | |
176 | if ( isset( $this->mNavigationBar ) ) { |
177 | return $this->mNavigationBar; |
178 | } |
179 | |
180 | $navBuilder = $this->getNavigationBuilder() |
181 | ->setPrevMsg( 'pager-newer-n' ) |
182 | ->setNextMsg( 'pager-older-n' ) |
183 | ->setFirstMsg( 'histlast' ) |
184 | ->setLastMsg( 'histfirst' ); |
185 | |
186 | $this->mNavigationBar = $navBuilder->getHtml(); |
187 | |
188 | return $this->mNavigationBar; |
189 | } |
190 | |
191 | /** |
192 | * Set and return the offset timestamp such that we can get all revisions with |
193 | * a timestamp up to the specified parameters. |
194 | * |
195 | * @stable to override |
196 | * |
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 |
201 | */ |
202 | public function getDateCond( $year, $month, $day = -1 ) { |
203 | $year = (int)$year; |
204 | $month = (int)$month; |
205 | $day = (int)$day; |
206 | |
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; |
211 | } |
212 | |
213 | $timestamp = self::getOffsetDate( $year, $month, $day ); |
214 | |
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' ); |
219 | |
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; |
228 | } |
229 | |
230 | return $this->endOffset; |
231 | } |
232 | |
233 | /** |
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. |
238 | * |
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. |
242 | * |
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 |
247 | */ |
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--; |
262 | } |
263 | } |
264 | |
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; |
268 | |
269 | if ( $day && $day > 0 ) { |
270 | // If we have a day, we want up to the day immediately afterward |
271 | $day++; |
272 | |
273 | // Did we overflow the current month? |
274 | if ( !checkdate( $month, $day, $year ) ) { |
275 | $day = 1; |
276 | $month++; |
277 | } |
278 | } else { |
279 | // If no day, assume beginning of next month |
280 | $day = 1; |
281 | $month++; |
282 | } |
283 | |
284 | // Did we overflow the current year? |
285 | if ( $month > 12 ) { |
286 | $month = 1; |
287 | $year++; |
288 | } |
289 | |
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++; |
295 | } |
296 | |
297 | $ymd = sprintf( "%04d%02d%02d", $year, $month, $day ); |
298 | |
299 | return MWTimestamp::getInstance( "{$ymd}000000" ); |
300 | } |
301 | |
302 | /** |
303 | * Return the end offset, extensions can use this if they are not in the context of subclass. |
304 | * |
305 | * @since 1.40 |
306 | * @return string |
307 | */ |
308 | public function getEndOffset() { |
309 | return $this->endOffset; |
310 | } |
311 | |
312 | /** |
313 | * @inheritDoc |
314 | */ |
315 | protected function buildQueryInfo( $offset, $limit, $order ) { |
316 | [ $tables, $fields, $conds, $fname, $options, $join_conds ] = parent::buildQueryInfo( |
317 | $offset, |
318 | $limit, |
319 | $order |
320 | ); |
321 | if ( $this->endOffset ) { |
322 | $conds[] = $this->mDb->expr( $this->getTimestampField(), '<', $this->endOffset ); |
323 | } |
324 | |
325 | return [ $tables, $fields, $conds, $fname, $options, $join_conds ]; |
326 | } |
327 | } |
328 | |
329 | /** @deprecated class alias since 1.41 */ |
330 | class_alias( ReverseChronologicalPager::class, 'ReverseChronologicalPager' ); |