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