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