Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 167 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
ImageHistoryList | |
0.00% |
0 / 167 |
|
0.00% |
0 / 10 |
2550 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getImagePage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFile | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
beginImageHistoryList | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
endImageHistoryList | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
imageHistoryLine | |
0.00% |
0 / 113 |
|
0.00% |
0 / 1 |
930 | |||
getThumbForLine | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 | |||
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\Context\ContextSource; |
22 | use MediaWiki\HookContainer\ProtectedHookAccessorTrait; |
23 | use MediaWiki\Html\Html; |
24 | use MediaWiki\Linker\Linker; |
25 | use MediaWiki\MainConfigNames; |
26 | use MediaWiki\MediaWikiServices; |
27 | use MediaWiki\SpecialPage\SpecialPage; |
28 | use MediaWiki\Title\Title; |
29 | |
30 | /** |
31 | * Builds the image revision log shown on image pages |
32 | * |
33 | * @ingroup Media |
34 | */ |
35 | class ImageHistoryList extends ContextSource { |
36 | use ProtectedHookAccessorTrait; |
37 | |
38 | /** |
39 | * @var Title |
40 | */ |
41 | protected $title; |
42 | |
43 | /** |
44 | * @var File |
45 | */ |
46 | protected $img; |
47 | |
48 | /** |
49 | * @var ImagePage |
50 | */ |
51 | protected $imagePage; |
52 | |
53 | /** |
54 | * @var File |
55 | */ |
56 | protected $current; |
57 | |
58 | protected $repo, $showThumb; |
59 | protected $preventClickjacking = false; |
60 | |
61 | /** |
62 | * @param ImagePage $imagePage |
63 | */ |
64 | public function __construct( $imagePage ) { |
65 | $context = $imagePage->getContext(); |
66 | $this->current = $imagePage->getPage()->getFile(); |
67 | $this->img = $imagePage->getDisplayedFile(); |
68 | $this->title = $imagePage->getTitle(); |
69 | $this->imagePage = $imagePage; |
70 | $this->showThumb = $context->getConfig()->get( MainConfigNames::ShowArchiveThumbnails ) && |
71 | $this->img->canRender(); |
72 | $this->setContext( $context ); |
73 | } |
74 | |
75 | /** |
76 | * @return ImagePage |
77 | */ |
78 | public function getImagePage() { |
79 | return $this->imagePage; |
80 | } |
81 | |
82 | /** |
83 | * @return File |
84 | */ |
85 | public function getFile() { |
86 | return $this->img; |
87 | } |
88 | |
89 | /** |
90 | * @return string |
91 | */ |
92 | public function beginImageHistoryList() { |
93 | // Styles for class=history-deleted |
94 | $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' ); |
95 | |
96 | $html = ''; |
97 | $canDelete = $this->current->isLocal() && |
98 | $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' ); |
99 | |
100 | foreach ( [ |
101 | '', |
102 | $canDelete ? '' : null, |
103 | 'filehist-datetime', |
104 | $this->showThumb ? 'filehist-thumb' : null, |
105 | 'filehist-dimensions', |
106 | 'filehist-user', |
107 | 'filehist-comment', |
108 | ] as $key ) { |
109 | if ( $key !== null ) { |
110 | $html .= Html::element( 'th', [], $key ? $this->msg( $key )->text() : '' ); |
111 | } |
112 | } |
113 | |
114 | return Html::openElement( 'table', [ 'class' => 'wikitable filehistory' ] ) . "\n" |
115 | . Html::rawElement( 'tr', [], $html ) . "\n"; |
116 | } |
117 | |
118 | /** |
119 | * @return string |
120 | */ |
121 | public function endImageHistoryList() { |
122 | return Html::closeElement( 'table' ) . "\n"; |
123 | } |
124 | |
125 | /** |
126 | * @internal |
127 | * @param bool $iscur |
128 | * @param File $file |
129 | * @param string $formattedComment |
130 | * @return string |
131 | */ |
132 | public function imageHistoryLine( $iscur, $file, $formattedComment ) { |
133 | $user = $this->getUser(); |
134 | $lang = $this->getLanguage(); |
135 | $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); |
136 | $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); |
137 | // @phan-suppress-next-line PhanUndeclaredMethod |
138 | $img = $iscur ? $file->getName() : $file->getArchiveName(); |
139 | $uploader = $file->getUploader( File::FOR_THIS_USER, $user ); |
140 | |
141 | $local = $this->current->isLocal(); |
142 | $row = ''; |
143 | |
144 | // Deletion link |
145 | if ( $local && ( $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' ) ) ) { |
146 | $row .= Html::openElement( 'td' ); |
147 | # Link to hide content. Don't show useless link to people who cannot hide revisions. |
148 | if ( !$iscur && $this->getAuthority()->isAllowed( 'deleterevision' ) ) { |
149 | // If file is top revision, is missing or locked from this user, don't link |
150 | if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) || !$file->exists() ) { |
151 | $row .= Html::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] ); |
152 | } else { |
153 | $row .= Html::check( 'ids[' . explode( '!', $img, 2 )[0] . ']', false ); |
154 | } |
155 | if ( $this->getAuthority()->isAllowed( 'delete' ) ) { |
156 | $row .= ' '; |
157 | } |
158 | } |
159 | # Link to remove from history |
160 | if ( $this->getAuthority()->isAllowed( 'delete' ) ) { |
161 | if ( $file->exists() ) { |
162 | $row .= $linkRenderer->makeKnownLink( |
163 | $this->title, |
164 | $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->text(), |
165 | [], |
166 | [ 'action' => 'delete', 'oldimage' => $iscur ? null : $img ] |
167 | ); |
168 | } else { |
169 | // T244567: Non-existing file can not be deleted. |
170 | $row .= $this->msg( 'filehist-missing' )->escaped(); |
171 | } |
172 | |
173 | } |
174 | $row .= Html::closeElement( 'td' ); |
175 | } |
176 | |
177 | // Reversion link/current indicator |
178 | $row .= Html::openElement( 'td' ); |
179 | if ( $iscur ) { |
180 | $row .= $this->msg( 'filehist-current' )->escaped(); |
181 | } elseif ( $local && $this->getAuthority()->probablyCan( 'edit', $this->title ) |
182 | && $this->getAuthority()->probablyCan( 'upload', $this->title ) |
183 | ) { |
184 | if ( $file->isDeleted( File::DELETED_FILE ) ) { |
185 | $row .= $this->msg( 'filehist-revert' )->escaped(); |
186 | } elseif ( !$file->exists() ) { |
187 | // T328112: Lost file, in this case there's no version to revert back to. |
188 | $row .= $this->msg( 'filehist-missing' )->escaped(); |
189 | } else { |
190 | $row .= $linkRenderer->makeKnownLink( |
191 | $this->title, |
192 | $this->msg( 'filehist-revert' )->text(), |
193 | [], |
194 | [ |
195 | 'action' => 'revert', |
196 | 'oldimage' => $img, |
197 | ] |
198 | ); |
199 | } |
200 | } |
201 | $row .= Html::closeElement( 'td' ); |
202 | |
203 | // Date/time and image link |
204 | $selected = $file->getTimestamp() === $this->img->getTimestamp(); |
205 | $row .= Html::openElement( 'td', [ |
206 | 'class' => $selected ? 'filehistory-selected' : null, |
207 | 'style' => 'white-space: nowrap;' |
208 | ] ); |
209 | if ( !$file->userCan( File::DELETED_FILE, $user ) ) { |
210 | # Don't link to unviewable files |
211 | $row .= Html::element( 'span', [ 'class' => 'history-deleted' ], |
212 | $lang->userTimeAndDate( $timestamp, $user ) |
213 | ); |
214 | } elseif ( $file->isDeleted( File::DELETED_FILE ) ) { |
215 | $timeAndDate = $lang->userTimeAndDate( $timestamp, $user ); |
216 | if ( $local ) { |
217 | $this->setPreventClickjacking( true ); |
218 | # Make a link to review the image |
219 | $url = $linkRenderer->makeKnownLink( |
220 | SpecialPage::getTitleFor( 'Revisiondelete' ), |
221 | $timeAndDate, |
222 | [], |
223 | [ |
224 | 'target' => $this->title->getPrefixedText(), |
225 | 'file' => $img, |
226 | 'token' => $user->getEditToken( $img ) |
227 | ] |
228 | ); |
229 | } else { |
230 | $url = htmlspecialchars( $timeAndDate ); |
231 | } |
232 | $row .= Html::rawElement( 'span', [ 'class' => 'history-deleted' ], $url ); |
233 | } elseif ( !$file->exists() ) { |
234 | $row .= Html::element( 'span', [ 'class' => 'mw-file-missing' ], |
235 | $lang->userTimeAndDate( $timestamp, $user ) |
236 | ); |
237 | } else { |
238 | $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img ); |
239 | $row .= Html::element( 'a', [ 'href' => $url ], |
240 | $lang->userTimeAndDate( $timestamp, $user ) |
241 | ); |
242 | } |
243 | $row .= Html::closeElement( 'td' ); |
244 | |
245 | // Thumbnail |
246 | if ( $this->showThumb ) { |
247 | $row .= Html::rawElement( 'td', [], |
248 | $this->getThumbForLine( $file, $iscur ) ?? $this->msg( 'filehist-nothumb' )->escaped() |
249 | ); |
250 | } |
251 | |
252 | // Image dimensions + size |
253 | $row .= Html::openElement( 'td' ); |
254 | $row .= htmlspecialchars( $file->getDimensionsString() ); |
255 | $row .= $this->msg( 'word-separator' )->escaped(); |
256 | $row .= Html::element( 'span', [ 'style' => 'white-space: nowrap;' ], |
257 | $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->text() |
258 | ); |
259 | $row .= Html::closeElement( 'td' ); |
260 | |
261 | // Uploading user |
262 | $row .= Html::openElement( 'td' ); |
263 | // Hide deleted usernames |
264 | if ( $uploader && $local ) { |
265 | $row .= Linker::userLink( $uploader->getId(), $uploader->getName() ); |
266 | $row .= Html::rawElement( 'span', [ 'style' => 'white-space: nowrap;' ], |
267 | Linker::userToolLinks( $uploader->getId(), $uploader->getName() ) |
268 | ); |
269 | } elseif ( $uploader ) { |
270 | $row .= htmlspecialchars( $uploader->getName() ); |
271 | } else { |
272 | $row .= Html::element( 'span', [ 'class' => 'history-deleted' ], |
273 | $this->msg( 'rev-deleted-user' )->text() |
274 | ); |
275 | } |
276 | $row .= Html::closeElement( 'td' ); |
277 | |
278 | // Don't show deleted descriptions |
279 | if ( $file->isDeleted( File::DELETED_COMMENT ) ) { |
280 | $row .= Html::rawElement( 'td', [], |
281 | Html::element( 'span', [ 'class' => 'history-deleted' ], |
282 | $this->msg( 'rev-deleted-comment' )->text() |
283 | ) |
284 | ); |
285 | } else { |
286 | $contLang = MediaWikiServices::getInstance()->getContentLanguage(); |
287 | $row .= Html::rawElement( 'td', [ 'dir' => $contLang->getDir() ], $formattedComment ); |
288 | } |
289 | |
290 | $rowClass = null; |
291 | $this->getHookRunner()->onImagePageFileHistoryLine( $this, $file, $row, $rowClass ); |
292 | |
293 | return Html::rawElement( 'tr', [ 'class' => $rowClass ], $row ) . "\n"; |
294 | } |
295 | |
296 | /** |
297 | * @param File $file |
298 | * @param bool $iscur |
299 | * @return string|null |
300 | */ |
301 | protected function getThumbForLine( $file, $iscur ) { |
302 | $user = $this->getUser(); |
303 | if ( !$file->allowInlineDisplay() || |
304 | $file->isDeleted( File::DELETED_FILE ) || |
305 | !$file->userCan( File::DELETED_FILE, $user ) |
306 | ) { |
307 | return null; |
308 | } |
309 | |
310 | $thumbnail = $file->transform( |
311 | [ |
312 | 'width' => '120', |
313 | 'height' => '120', |
314 | 'isFilePageThumb' => $iscur // old revisions are already versioned |
315 | ] |
316 | ); |
317 | if ( !$thumbnail ) { |
318 | return null; |
319 | } |
320 | |
321 | $lang = $this->getLanguage(); |
322 | $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); |
323 | $alt = $this->msg( |
324 | 'filehist-thumbtext', |
325 | $lang->userTimeAndDate( $timestamp, $user ), |
326 | $lang->userDate( $timestamp, $user ), |
327 | $lang->userTime( $timestamp, $user ) |
328 | )->text(); |
329 | return $thumbnail->toHtml( [ 'alt' => $alt, 'file-link' => true, 'loading' => 'lazy' ] ); |
330 | } |
331 | |
332 | /** |
333 | * @param bool $enable |
334 | * @deprecated since 1.38, use ::setPreventClickjacking() instead |
335 | */ |
336 | protected function preventClickjacking( $enable = true ) { |
337 | $this->preventClickjacking = $enable; |
338 | } |
339 | |
340 | /** |
341 | * @param bool $enable |
342 | * @since 1.38 |
343 | */ |
344 | protected function setPreventClickjacking( bool $enable ) { |
345 | $this->preventClickjacking = $enable; |
346 | } |
347 | |
348 | /** |
349 | * @return bool |
350 | */ |
351 | public function getPreventClickjacking() { |
352 | return $this->preventClickjacking; |
353 | } |
354 | } |