Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 135 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
ImageHistoryPseudoPager | |
0.00% |
0 / 135 |
|
0.00% |
0 / 11 |
1560 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryInfo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndexField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
formatRow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getBody | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
132 | |||
doQuery | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
342 | |||
wrapWithActionButtons | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
preventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setPreventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPreventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
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 | use MediaWiki\Cache\LinkBatchFactory; |
22 | use MediaWiki\Html\Html; |
23 | use MediaWiki\MediaWikiServices; |
24 | use MediaWiki\Pager\ReverseChronologicalPager; |
25 | use MediaWiki\SpecialPage\SpecialPage; |
26 | use MediaWiki\Title\Title; |
27 | use Wikimedia\Timestamp\TimestampException; |
28 | |
29 | class ImageHistoryPseudoPager extends ReverseChronologicalPager { |
30 | /** @var bool */ |
31 | protected $preventClickjacking = false; |
32 | |
33 | /** |
34 | * @var File|null |
35 | */ |
36 | protected $mImg; |
37 | |
38 | /** |
39 | * @var Title |
40 | */ |
41 | protected $mTitle; |
42 | |
43 | /** |
44 | * @since 1.14 |
45 | * @var ImagePage |
46 | */ |
47 | public $mImagePage; |
48 | |
49 | /** |
50 | * @since 1.14 |
51 | * @var File[] |
52 | */ |
53 | public $mHist; |
54 | |
55 | /** |
56 | * @since 1.14 |
57 | * @var int[] |
58 | */ |
59 | public $mRange; |
60 | |
61 | /** @var LinkBatchFactory */ |
62 | private $linkBatchFactory; |
63 | |
64 | /** |
65 | * @param ImagePage $imagePage |
66 | * @param LinkBatchFactory|null $linkBatchFactory |
67 | */ |
68 | public function __construct( $imagePage, ?LinkBatchFactory $linkBatchFactory = null ) { |
69 | parent::__construct( $imagePage->getContext() ); |
70 | $this->mImagePage = $imagePage; |
71 | $this->mTitle = $imagePage->getTitle()->createFragmentTarget( 'filehistory' ); |
72 | $this->mImg = null; |
73 | $this->mHist = []; |
74 | $this->mRange = [ 0, 0 ]; // display range |
75 | |
76 | // Only display 10 revisions at once by default, otherwise the list is overwhelming |
77 | $this->mLimitsShown = array_merge( [ 10 ], $this->mLimitsShown ); |
78 | $this->mDefaultLimit = 10; |
79 | [ $this->mLimit, /* $offset */ ] = |
80 | $this->mRequest->getLimitOffsetForUser( |
81 | $this->getUser(), |
82 | $this->mDefaultLimit, |
83 | '' |
84 | ); |
85 | $this->linkBatchFactory = $linkBatchFactory ?? MediaWikiServices::getInstance()->getLinkBatchFactory(); |
86 | } |
87 | |
88 | /** |
89 | * @return Title |
90 | */ |
91 | public function getTitle() { |
92 | return $this->mTitle; |
93 | } |
94 | |
95 | public function getQueryInfo() { |
96 | return []; |
97 | } |
98 | |
99 | /** |
100 | * @return string |
101 | */ |
102 | public function getIndexField() { |
103 | return ''; |
104 | } |
105 | |
106 | /** |
107 | * @param stdClass $row |
108 | * @return string |
109 | */ |
110 | public function formatRow( $row ) { |
111 | return ''; |
112 | } |
113 | |
114 | /** |
115 | * @return string |
116 | */ |
117 | public function getBody() { |
118 | $s = ''; |
119 | $this->doQuery(); |
120 | if ( count( $this->mHist ) ) { |
121 | if ( $this->mImg->isLocal() ) { |
122 | // Do a batch existence check for user pages and talkpages. |
123 | $linkBatch = $this->linkBatchFactory->newLinkBatch(); |
124 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
125 | $file = $this->mHist[$i]; |
126 | $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() ); |
127 | if ( $uploader ) { |
128 | $linkBatch->add( NS_USER, $uploader->getName() ); |
129 | $linkBatch->add( NS_USER_TALK, $uploader->getName() ); |
130 | } |
131 | } |
132 | $linkBatch->execute(); |
133 | } |
134 | |
135 | // Batch-format comments |
136 | $comments = []; |
137 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
138 | $file = $this->mHist[$i]; |
139 | $comments[$i] = $file->getDescription( |
140 | File::FOR_THIS_USER, |
141 | $this->getAuthority() |
142 | ) ?: ''; |
143 | } |
144 | $formattedComments = MediaWikiServices::getInstance() |
145 | ->getCommentFormatter() |
146 | ->formatStrings( $comments, $this->getTitle() ); |
147 | |
148 | $list = new ImageHistoryList( $this->mImagePage ); |
149 | # Generate prev/next links |
150 | $navLink = $this->getNavigationBar(); |
151 | |
152 | $s = Html::element( 'h2', [ 'id' => 'filehistory' ], $this->msg( 'filehist' )->text() ) . "\n" |
153 | . Html::openElement( 'div', [ 'id' => 'mw-imagepage-section-filehistory' ] ) . "\n" |
154 | . $this->msg( 'filehist-help' )->parseAsBlock() |
155 | . $navLink . "\n"; |
156 | |
157 | $sList = $list->beginImageHistoryList(); |
158 | $onlyCurrentFile = true; |
159 | // Skip rows there just for paging links |
160 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
161 | $file = $this->mHist[$i]; |
162 | $sList .= $list->imageHistoryLine( !$file->isOld(), $file, $formattedComments[$i] ); |
163 | $onlyCurrentFile = !$file->isOld(); |
164 | } |
165 | $sList .= $list->endImageHistoryList(); |
166 | if ( $onlyCurrentFile || !$this->mImg->isLocal() ) { |
167 | // It is not possible to revision-delete the current file or foreign files, |
168 | // if there is only the current file or the file is not local, show no buttons |
169 | $s .= $sList; |
170 | } else { |
171 | $s .= $this->wrapWithActionButtons( $sList ); |
172 | } |
173 | $s .= $navLink . "\n" . Html::closeElement( 'div' ) . "\n"; |
174 | |
175 | if ( $list->getPreventClickjacking() ) { |
176 | $this->setPreventClickjacking( true ); |
177 | } |
178 | } |
179 | return $s; |
180 | } |
181 | |
182 | public function doQuery() { |
183 | if ( $this->mQueryDone ) { |
184 | return; |
185 | } |
186 | $this->mImg = $this->mImagePage->getPage()->getFile(); // ensure loading |
187 | if ( !$this->mImg->exists() ) { |
188 | return; |
189 | } |
190 | // Make sure the date (probably from user input) is valid; if not, drop it. |
191 | if ( $this->mOffset !== null ) { |
192 | try { |
193 | $this->mDb->timestamp( $this->mOffset ); |
194 | } catch ( TimestampException $e ) { |
195 | $this->mOffset = null; |
196 | } |
197 | } |
198 | $queryLimit = $this->mLimit + 1; // limit plus extra row |
199 | if ( $this->mIsBackwards ) { |
200 | // Fetch the file history |
201 | $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false ); |
202 | // The current rev may not meet the offset/limit |
203 | $numRows = count( $this->mHist ); |
204 | if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) { |
205 | $this->mHist = array_merge( [ $this->mImg ], $this->mHist ); |
206 | } |
207 | } else { |
208 | // The current rev may not meet the offset |
209 | if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) { |
210 | $this->mHist[] = $this->mImg; |
211 | } |
212 | // Old image versions (fetch extra row for nav links) |
213 | $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1; |
214 | // Fetch the file history |
215 | $this->mHist = array_merge( $this->mHist, |
216 | $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) ); |
217 | } |
218 | $numRows = count( $this->mHist ); // Total number of query results |
219 | if ( $numRows ) { |
220 | # Index value of top item in the list |
221 | $firstIndex = $this->mIsBackwards ? |
222 | [ $this->mHist[$numRows - 1]->getTimestamp() ] : [ $this->mHist[0]->getTimestamp() ]; |
223 | # Discard the extra result row if there is one |
224 | if ( $numRows > $this->mLimit && $numRows > 1 ) { |
225 | if ( $this->mIsBackwards ) { |
226 | # Index value of item past the index |
227 | $this->mPastTheEndIndex = [ $this->mHist[0]->getTimestamp() ]; |
228 | # Index value of bottom item in the list |
229 | $lastIndex = [ $this->mHist[1]->getTimestamp() ]; |
230 | # Display range |
231 | $this->mRange = [ 1, $numRows - 1 ]; |
232 | } else { |
233 | # Index value of item past the index |
234 | $this->mPastTheEndIndex = [ $this->mHist[$numRows - 1]->getTimestamp() ]; |
235 | # Index value of bottom item in the list |
236 | $lastIndex = [ $this->mHist[$numRows - 2]->getTimestamp() ]; |
237 | # Display range |
238 | $this->mRange = [ 0, $numRows - 2 ]; |
239 | } |
240 | } else { |
241 | # Setting indexes to an empty array means that they will be |
242 | # omitted if they would otherwise appear in URLs. It just so |
243 | # happens that this is the right thing to do in the standard |
244 | # UI, in all the relevant cases. |
245 | $this->mPastTheEndIndex = []; |
246 | # Index value of bottom item in the list |
247 | $lastIndex = $this->mIsBackwards ? |
248 | [ $this->mHist[0]->getTimestamp() ] : [ $this->mHist[$numRows - 1]->getTimestamp() ]; |
249 | # Display range |
250 | $this->mRange = [ 0, $numRows - 1 ]; |
251 | } |
252 | } else { |
253 | $firstIndex = []; |
254 | $lastIndex = []; |
255 | $this->mPastTheEndIndex = []; |
256 | } |
257 | if ( $this->mIsBackwards ) { |
258 | $this->mIsFirst = ( $numRows < $queryLimit ); |
259 | $this->mIsLast = ( $this->mOffset == '' ); |
260 | $this->mLastShown = $firstIndex; |
261 | $this->mFirstShown = $lastIndex; |
262 | } else { |
263 | $this->mIsFirst = ( $this->mOffset == '' ); |
264 | $this->mIsLast = ( $numRows < $queryLimit ); |
265 | $this->mLastShown = $lastIndex; |
266 | $this->mFirstShown = $firstIndex; |
267 | } |
268 | $this->mQueryDone = true; |
269 | } |
270 | |
271 | /** |
272 | * Wrap the content with action buttons at begin and end if the user |
273 | * is allow to use the action buttons. |
274 | * @param string $formcontents |
275 | * @return string |
276 | */ |
277 | private function wrapWithActionButtons( $formcontents ) { |
278 | if ( !$this->getAuthority()->isAllowed( 'deleterevision' ) ) { |
279 | return $formcontents; |
280 | } |
281 | |
282 | # Show button to hide log entries |
283 | $s = Html::openElement( |
284 | 'form', |
285 | [ 'action' => wfScript(), 'id' => 'mw-filehistory-deleterevision-submit' ] |
286 | ) . "\n"; |
287 | $s .= Html::hidden( 'target', $this->getTitle()->getPrefixedDBkey() ) . "\n"; |
288 | $s .= Html::hidden( 'type', 'oldimage' ) . "\n"; |
289 | $this->setPreventClickjacking( true ); |
290 | |
291 | $buttons = Html::element( |
292 | 'button', |
293 | [ |
294 | 'type' => 'submit', |
295 | 'name' => 'title', |
296 | 'value' => SpecialPage::getTitleFor( 'Revisiondelete' )->getPrefixedDBkey(), |
297 | 'class' => "deleterevision-filehistory-submit mw-filehistory-deleterevision-button mw-ui-button" |
298 | ], |
299 | $this->msg( 'showhideselectedfileversions' )->text() |
300 | ) . "\n"; |
301 | |
302 | $s .= $buttons . $formcontents . $buttons; |
303 | $s .= Html::closeElement( 'form' ); |
304 | |
305 | return $s; |
306 | } |
307 | |
308 | /** |
309 | * @param bool $enable |
310 | * @deprecated since 1.38, use ::setPreventClickjacking() |
311 | */ |
312 | protected function preventClickjacking( $enable = true ) { |
313 | $this->preventClickjacking = $enable; |
314 | } |
315 | |
316 | /** |
317 | * @param bool $enable |
318 | * @since 1.38 |
319 | */ |
320 | protected function setPreventClickjacking( bool $enable ) { |
321 | $this->preventClickjacking = $enable; |
322 | } |
323 | |
324 | /** |
325 | * @return bool |
326 | */ |
327 | public function getPreventClickjacking() { |
328 | return $this->preventClickjacking; |
329 | } |
330 | |
331 | } |