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