Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 174 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
TraditionalImageGallery | |
0.00% |
0 / 174 |
|
0.00% |
0 / 15 |
4160 | |
0.00% |
0 / 1 |
toHTML | |
0.00% |
0 / 148 |
|
0.00% |
0 / 1 |
2352 | |||
getCaptionHtml | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
wrapGalleryText | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getThumbPadding | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGBPadding | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGBBorders | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCaptionLength | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllPadding | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getVPad | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getThumbParams | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getThumbDivWidth | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGBWidth | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGBWidthOverwrite | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getModules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
adjustImageParameters | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | use MediaWiki\HookContainer\HookRunner; |
4 | use MediaWiki\Html\Html; |
5 | use MediaWiki\Language\Language; |
6 | use MediaWiki\Linker\Linker; |
7 | use MediaWiki\Linker\LinkRenderer; |
8 | use MediaWiki\MainConfigNames; |
9 | use MediaWiki\MediaWikiServices; |
10 | use MediaWiki\Parser\Parser; |
11 | use MediaWiki\Parser\Sanitizer; |
12 | use MediaWiki\Title\Title; |
13 | use Wikimedia\Assert\Assert; |
14 | |
15 | /** |
16 | * Image gallery. |
17 | * |
18 | * This program is free software; you can redistribute it and/or modify |
19 | * it under the terms of the GNU General Public License as published by |
20 | * the Free Software Foundation; either version 2 of the License, or |
21 | * (at your option) any later version. |
22 | * |
23 | * This program is distributed in the hope that it will be useful, |
24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 | * GNU General Public License for more details. |
27 | * |
28 | * You should have received a copy of the GNU General Public License along |
29 | * with this program; if not, write to the Free Software Foundation, Inc., |
30 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
31 | * http://www.gnu.org/copyleft/gpl.html |
32 | * |
33 | * @file |
34 | */ |
35 | |
36 | class TraditionalImageGallery extends ImageGalleryBase { |
37 | /** |
38 | * Return a HTML representation of the image gallery |
39 | * |
40 | * For each image in the gallery, display |
41 | * - a thumbnail |
42 | * - the image name |
43 | * - the additional text provided when adding the image |
44 | * - the size of the image |
45 | * |
46 | * @return string |
47 | */ |
48 | public function toHTML() { |
49 | $resolveFilesViaParser = $this->mParser instanceof Parser; |
50 | if ( $resolveFilesViaParser ) { |
51 | $parserOutput = $this->mParser->getOutput(); |
52 | $repoGroup = null; |
53 | $linkRenderer = $this->mParser->getLinkRenderer(); |
54 | $badFileLookup = $this->mParser->getBadFileLookup(); |
55 | } else { |
56 | $parserOutput = $this->getOutput(); |
57 | $services = MediaWikiServices::getInstance(); |
58 | $repoGroup = $services->getRepoGroup(); |
59 | $linkRenderer = $services->getLinkRenderer(); |
60 | $badFileLookup = $services->getBadFileLookup(); |
61 | } |
62 | |
63 | if ( $this->mPerRow > 0 ) { |
64 | $maxwidth = $this->mPerRow * ( $this->mWidths + $this->getAllPadding() ); |
65 | $oldStyle = $this->mAttribs['style'] ?? ''; |
66 | $this->mAttribs['style'] = "max-width: {$maxwidth}px;" . $oldStyle; |
67 | } |
68 | |
69 | $attribs = Sanitizer::mergeAttributes( |
70 | [ 'class' => 'gallery mw-gallery-' . $this->mMode ], $this->mAttribs ); |
71 | |
72 | $parserOutput->addModules( $this->getModules() ); |
73 | $parserOutput->addModuleStyles( [ 'mediawiki.page.gallery.styles' ] ); |
74 | $output = Html::openElement( 'ul', $attribs ); |
75 | if ( $this->mCaption ) { |
76 | $output .= "\n\t" . Html::rawElement( 'li', [ 'class' => 'gallerycaption' ], $this->mCaption ); |
77 | } |
78 | |
79 | if ( $this->mShowFilename ) { |
80 | // Preload LinkCache info for when generating links |
81 | // of the filename below |
82 | $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory(); |
83 | $lb = $linkBatchFactory->newLinkBatch(); |
84 | foreach ( $this->mImages as [ $title, /* see below */ ] ) { |
85 | $lb->addObj( $title ); |
86 | } |
87 | $lb->execute(); |
88 | } |
89 | |
90 | $lang = $this->getRenderLang(); |
91 | $enableLegacyMediaDOM = |
92 | $this->getConfig()->get( MainConfigNames::ParserEnableLegacyMediaDOM ); |
93 | $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ); |
94 | |
95 | # Output each image... |
96 | foreach ( $this->mImages as [ $nt, $text, $alt, $link, $handlerOpts, $loading, $imageOptions ] ) { |
97 | // "text" means "caption" here |
98 | /** @var Title $nt */ |
99 | |
100 | $descQuery = false; |
101 | if ( $nt->inNamespace( NS_FILE ) && !$nt->isExternal() ) { |
102 | # Get the file... |
103 | if ( $resolveFilesViaParser ) { |
104 | # Give extensions a chance to select the file revision for us |
105 | $options = []; |
106 | $hookRunner->onBeforeParserFetchFileAndTitle( |
107 | // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args |
108 | $this->mParser, $nt, $options, $descQuery ); |
109 | # Fetch and register the file (file title may be different via hooks) |
110 | [ $img, $nt ] = $this->mParser->fetchFileAndTitle( $nt, $options ); |
111 | } else { |
112 | $img = $repoGroup->findFile( $nt ); |
113 | } |
114 | } else { |
115 | $img = false; |
116 | } |
117 | |
118 | $transformOptions = $this->getThumbParams( $img ) + $handlerOpts; |
119 | $thumb = $img ? $img->transform( $transformOptions ) : false; |
120 | |
121 | $rdfaType = 'mw:File'; |
122 | |
123 | $isBadFile = $img && $thumb && $this->mHideBadImages && |
124 | $badFileLookup->isBadFile( $nt->getDBkey(), $this->getContextTitle() ); |
125 | |
126 | if ( !$img || !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) { |
127 | $rdfaType = 'mw:Error ' . $rdfaType; |
128 | |
129 | if ( $enableLegacyMediaDOM ) { |
130 | if ( $isBadFile ) { |
131 | $thumbhtml = $linkRenderer->makeKnownLink( $nt, $nt->getText() ); |
132 | } else { |
133 | $thumbhtml = htmlspecialchars( $img ? $img->getLastError() : $nt->getText() ); |
134 | } |
135 | } else { |
136 | $currentExists = $img && $img->exists(); |
137 | if ( $currentExists && !$thumb ) { |
138 | $label = wfMessage( 'thumbnail_error', '' )->text(); |
139 | } elseif ( $thumb && $thumb->isError() ) { |
140 | Assert::invariant( |
141 | $thumb instanceof MediaTransformError, |
142 | 'Unknown MediaTransformOutput: ' . get_class( $thumb ) |
143 | ); |
144 | $label = $thumb->toText(); |
145 | } else { |
146 | $label = $alt ?? ''; |
147 | } |
148 | $thumbhtml = Linker::makeBrokenImageLinkObj( |
149 | $nt, $label, '', '', '', false, $transformOptions, $currentExists |
150 | ); |
151 | $thumbhtml = Html::rawElement( 'span', [ 'typeof' => $rdfaType ], $thumbhtml ); |
152 | } |
153 | |
154 | $thumbhtml = "\n\t\t\t" . Html::rawElement( |
155 | 'div', |
156 | [ |
157 | 'class' => 'thumb', |
158 | 'style' => 'height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;' |
159 | ], |
160 | $thumbhtml |
161 | ); |
162 | |
163 | if ( !$img && $resolveFilesViaParser ) { |
164 | $this->mParser->addTrackingCategory( 'broken-file-category' ); |
165 | } |
166 | } else { |
167 | /** @var MediaTransformOutput $thumb */ |
168 | $vpad = $this->getVPad( $this->mHeights, $thumb->getHeight() ); |
169 | |
170 | // Backwards compat before the $imageOptions existed |
171 | if ( $imageOptions === null ) { |
172 | $imageParameters = [ |
173 | 'desc-link' => true, |
174 | 'desc-query' => $descQuery, |
175 | 'alt' => $alt ?? '', |
176 | 'custom-url-link' => $link |
177 | ]; |
178 | } else { |
179 | $params = []; |
180 | // An empty alt indicates an image is not a key part of the |
181 | // content and that non-visual browsers may omit it from |
182 | // rendering. Only set the parameter if it's explicitly |
183 | // requested. |
184 | if ( $alt !== null ) { |
185 | $params['alt'] = $alt; |
186 | } |
187 | $params['title'] = $imageOptions['title']; |
188 | if ( !$enableLegacyMediaDOM ) { |
189 | $params['img-class'] = 'mw-file-element'; |
190 | } |
191 | $imageParameters = Linker::getImageLinkMTOParams( |
192 | $imageOptions, $descQuery, $this->mParser |
193 | ) + $params; |
194 | } |
195 | |
196 | if ( $loading === ImageGalleryBase::LOADING_LAZY ) { |
197 | $imageParameters['loading'] = 'lazy'; |
198 | } |
199 | |
200 | $this->adjustImageParameters( $thumb, $imageParameters ); |
201 | |
202 | Linker::processResponsiveImages( $img, $thumb, $transformOptions ); |
203 | |
204 | $thumbhtml = $thumb->toHtml( $imageParameters ); |
205 | |
206 | if ( !$enableLegacyMediaDOM ) { |
207 | $thumbhtml = Html::rawElement( |
208 | 'span', [ 'typeof' => $rdfaType ], $thumbhtml |
209 | ); |
210 | } else { |
211 | $thumbhtml = Html::rawElement( 'div', [ |
212 | # Auto-margin centering for block-level elements. Needed |
213 | # now that we have video handlers since they may emit block- |
214 | # level elements as opposed to simple <img> tags. ref |
215 | # http://css-discuss.incutio.com/?page=CenteringBlockElement |
216 | 'style' => "margin:{$vpad}px auto;", |
217 | ], $thumbhtml ); |
218 | } |
219 | |
220 | # Set both fixed width and min-height. |
221 | $width = $this->getThumbDivWidth( $thumb->getWidth() ); |
222 | $height = $this->getThumbPadding() + $this->mHeights; |
223 | $thumbhtml = "\n\t\t\t" . Html::rawElement( 'div', [ |
224 | 'class' => 'thumb', |
225 | 'style' => "width: {$width}px;" . |
226 | ( !$enableLegacyMediaDOM && $this->mMode === 'traditional' ? |
227 | " height: {$height}px;" : '' ), |
228 | ], $thumbhtml ); |
229 | |
230 | // Call parser transform hook |
231 | if ( $resolveFilesViaParser ) { |
232 | /** @var MediaHandler $handler */ |
233 | $handler = $img->getHandler(); |
234 | if ( $handler ) { |
235 | $handler->parserTransformHook( $this->mParser, $img ); |
236 | } |
237 | $this->mParser->modifyImageHtml( |
238 | $img, [ 'handler' => $imageParameters ], $thumbhtml ); |
239 | } |
240 | } |
241 | |
242 | $meta = []; |
243 | if ( $img ) { |
244 | if ( $this->mShowDimensions ) { |
245 | $meta[] = htmlspecialchars( $img->getDimensionsString() ); |
246 | } |
247 | if ( $this->mShowBytes ) { |
248 | $meta[] = htmlspecialchars( $lang->formatSize( $img->getSize() ) ); |
249 | } |
250 | } elseif ( $this->mShowDimensions || $this->mShowBytes ) { |
251 | $meta[] = $this->msg( 'filemissing' )->escaped(); |
252 | } |
253 | $meta = $lang->semicolonList( $meta ); |
254 | if ( $meta ) { |
255 | $meta .= Html::rawElement( 'br', [] ) . "\n"; |
256 | } |
257 | |
258 | $textlink = $this->mShowFilename ? |
259 | $this->getCaptionHtml( $nt, $lang, $linkRenderer ) : |
260 | ''; |
261 | |
262 | $galleryText = $this->wrapGalleryText( $textlink . $text . $meta, $thumb ); |
263 | |
264 | $gbWidth = $this->getGBWidthOverwrite( $thumb ) ?: $this->getGBWidth( $thumb ) . 'px'; |
265 | # Weird double wrapping (the extra div inside the li) needed due to FF2 bug |
266 | # Can be safely removed if FF2 falls completely out of existence |
267 | $output .= "\n\t\t" . |
268 | Html::rawElement( |
269 | 'li', |
270 | [ 'class' => 'gallerybox', 'style' => 'width: ' . $gbWidth ], |
271 | ( $enableLegacyMediaDOM ? Html::openElement( 'div', [ 'style' => 'width: ' . $gbWidth ] ) : '' ) |
272 | . $thumbhtml |
273 | . $galleryText |
274 | . "\n\t\t" |
275 | . ( $enableLegacyMediaDOM ? Html::closeElement( 'div' ) : '' ) |
276 | ); |
277 | } |
278 | $output .= "\n" . Html::closeElement( 'ul' ); |
279 | |
280 | return $output; |
281 | } |
282 | |
283 | /** |
284 | * @param Title $nt |
285 | * @param Language $lang |
286 | * @param LinkRenderer $linkRenderer |
287 | * @return string HTML |
288 | */ |
289 | protected function getCaptionHtml( Title $nt, Language $lang, LinkRenderer $linkRenderer ) { |
290 | // Preloaded into LinkCache in toHTML |
291 | return $linkRenderer->makeKnownLink( |
292 | $nt, |
293 | is_int( $this->getCaptionLength() ) ? |
294 | $lang->truncateForVisual( $nt->getText(), $this->getCaptionLength() ) : |
295 | $nt->getText(), |
296 | [ |
297 | 'class' => 'galleryfilename' . |
298 | ( $this->getCaptionLength() === true ? ' galleryfilename-truncate' : '' ) |
299 | ] |
300 | ) . "\n"; |
301 | } |
302 | |
303 | /** |
304 | * Add the wrapper html around the thumb's caption |
305 | * |
306 | * @param string $galleryText The caption |
307 | * @param MediaTransformOutput|false $thumb The thumb this caption is for |
308 | * or false for bad image. |
309 | * @return string |
310 | */ |
311 | protected function wrapGalleryText( $galleryText, $thumb ) { |
312 | return "\n\t\t\t" . Html::rawElement( 'div', [ 'class' => "gallerytext" ], $galleryText ); |
313 | } |
314 | |
315 | /** |
316 | * How much padding the thumb has between the image and the inner div |
317 | * that contains the border. This is for both vertical and horizontal |
318 | * padding. (However, it is cut in half in the vertical direction). |
319 | * @return int |
320 | */ |
321 | protected function getThumbPadding() { |
322 | return 30; |
323 | } |
324 | |
325 | /** |
326 | * @note GB stands for gallerybox (as in the <li class="gallerybox"> element) |
327 | * |
328 | * @return int |
329 | */ |
330 | protected function getGBPadding() { |
331 | return 5; |
332 | } |
333 | |
334 | /** |
335 | * Get how much extra space the borders around the image takes up. |
336 | * |
337 | * For this mode, it is 2px borders on each side + 2px implied padding on |
338 | * each side from the stylesheet, giving us 2*2+2*2 = 8. |
339 | * @return int |
340 | */ |
341 | protected function getGBBorders() { |
342 | return 8; |
343 | } |
344 | |
345 | /** |
346 | * Length (in characters) to truncate filename to in caption when using "showfilename" (if int). |
347 | * A value of 'true' will truncate the filename to one line using CSS, while |
348 | * 'false' will disable truncating. |
349 | * |
350 | * @return int|bool |
351 | */ |
352 | protected function getCaptionLength() { |
353 | return $this->mCaptionLength; |
354 | } |
355 | |
356 | /** |
357 | * Get total padding. |
358 | * |
359 | * @return int Number of pixels of whitespace surrounding the thumbnail. |
360 | */ |
361 | protected function getAllPadding() { |
362 | return $this->getThumbPadding() + $this->getGBPadding() + $this->getGBBorders(); |
363 | } |
364 | |
365 | /** |
366 | * Get vertical padding for a thumbnail |
367 | * |
368 | * Generally this is the total height minus how high the thumb is. |
369 | * |
370 | * @param int $boxHeight How high we want the box to be. |
371 | * @param int $thumbHeight How high the thumbnail is. |
372 | * @return float Vertical padding to add on each side. |
373 | */ |
374 | protected function getVPad( $boxHeight, $thumbHeight ) { |
375 | return ( $this->getThumbPadding() + $boxHeight - $thumbHeight ) / 2; |
376 | } |
377 | |
378 | /** |
379 | * Get the transform parameters for a thumbnail. |
380 | * |
381 | * @param File|false $img The file in question. May be false for invalid image |
382 | * @return array |
383 | */ |
384 | protected function getThumbParams( $img ) { |
385 | return [ |
386 | 'width' => $this->mWidths, |
387 | 'height' => $this->mHeights |
388 | ]; |
389 | } |
390 | |
391 | /** |
392 | * Get the width of the inner div that contains the thumbnail in |
393 | * question. This is the div with the class of "thumb". |
394 | * |
395 | * @param int $thumbWidth The width of the thumbnail. |
396 | * @return float Width of inner thumb div. |
397 | */ |
398 | protected function getThumbDivWidth( $thumbWidth ) { |
399 | return $this->mWidths + $this->getThumbPadding(); |
400 | } |
401 | |
402 | /** |
403 | * Computed width of gallerybox <li>. |
404 | * |
405 | * Generally is the width of the image, plus padding on image |
406 | * plus padding on gallerybox. |
407 | * |
408 | * @note Important: parameter will be false if no thumb used. |
409 | * @param MediaTransformOutput|false $thumb |
410 | * @return float Width of gallerybox element |
411 | */ |
412 | protected function getGBWidth( $thumb ) { |
413 | return $this->mWidths + $this->getThumbPadding() + $this->getGBPadding(); |
414 | } |
415 | |
416 | /** |
417 | * Allows overwriting the computed width of the gallerybox <li> with a string, |
418 | * like '100%'. |
419 | * |
420 | * Generally is the width of the image, plus padding on image |
421 | * plus padding on gallerybox. |
422 | * |
423 | * @note Important: parameter will be false if no thumb used. |
424 | * @param MediaTransformOutput|false $thumb |
425 | * @return string|false Ignored if false. |
426 | */ |
427 | protected function getGBWidthOverwrite( $thumb ) { |
428 | return false; |
429 | } |
430 | |
431 | /** |
432 | * Get a list of modules to include in the page. |
433 | * |
434 | * Primarily intended for subclasses. |
435 | * |
436 | * @return array Modules to include |
437 | */ |
438 | protected function getModules() { |
439 | return []; |
440 | } |
441 | |
442 | /** |
443 | * Adjust the image parameters for a thumbnail. |
444 | * |
445 | * Used by a subclass to insert extra high resolution images. |
446 | * @param MediaTransformOutput $thumb The thumbnail |
447 | * @param array &$imageParameters Array of options |
448 | */ |
449 | protected function adjustImageParameters( $thumb, &$imageParameters ) { |
450 | } |
451 | } |