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