xref: /template/strap/ComboStrap/MediaLink.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1*37748cd8SNickeau<?php
2*37748cd8SNickeau/**
3*37748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4*37748cd8SNickeau *
5*37748cd8SNickeau * This source code is licensed under the GPL license found in the
6*37748cd8SNickeau * COPYING  file in the root directory of this source tree.
7*37748cd8SNickeau *
8*37748cd8SNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9*37748cd8SNickeau * @author   ComboStrap <support@combostrap.com>
10*37748cd8SNickeau *
11*37748cd8SNickeau */
12*37748cd8SNickeau
13*37748cd8SNickeaunamespace ComboStrap;
14*37748cd8SNickeau
15*37748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin;
16*37748cd8SNickeauuse syntax_plugin_combo_media;
17*37748cd8SNickeau
18*37748cd8SNickeaurequire_once(__DIR__ . '/DokuPath.php');
19*37748cd8SNickeau
20*37748cd8SNickeau/**
21*37748cd8SNickeau * Class InternalMedia
22*37748cd8SNickeau * Represent a media link
23*37748cd8SNickeau *
24*37748cd8SNickeau *
25*37748cd8SNickeau * @package ComboStrap
26*37748cd8SNickeau *
27*37748cd8SNickeau * Wrapper around {@link Doku_Handler_Parse_Media}
28*37748cd8SNickeau *
29*37748cd8SNickeau * Not that for dokuwiki the `type` key of the attributes is the `call`
30*37748cd8SNickeau * and therefore determine the function in an render
31*37748cd8SNickeau * (ie {@link \Doku_Renderer::internalmedialink()} or {@link \Doku_Renderer::externalmedialink()}
32*37748cd8SNickeau *
33*37748cd8SNickeau * It's a HTML tag and a URL (in the dokuwiki mode) build around its file system path
34*37748cd8SNickeau */
35*37748cd8SNickeauabstract class MediaLink extends DokuPath
36*37748cd8SNickeau{
37*37748cd8SNickeau
38*37748cd8SNickeau
39*37748cd8SNickeau    /**
40*37748cd8SNickeau     * The dokuwiki type and mode name
41*37748cd8SNickeau     * (ie call)
42*37748cd8SNickeau     *  * ie {@link MediaLink::EXTERNAL_MEDIA_CALL_NAME}
43*37748cd8SNickeau     *  or {@link MediaLink::INTERNAL_MEDIA_CALL_NAME}
44*37748cd8SNickeau     *
45*37748cd8SNickeau     * The dokuwiki type (internalmedia/externalmedia)
46*37748cd8SNickeau     * is saved in a `type` key that clash with the
47*37748cd8SNickeau     * combostrap type. To avoid the clash, we renamed it
48*37748cd8SNickeau     */
49*37748cd8SNickeau    const MEDIA_DOKUWIKI_TYPE = 'dokuwiki_type';
50*37748cd8SNickeau    const INTERNAL_MEDIA_CALL_NAME = "internalmedia";
51*37748cd8SNickeau    const EXTERNAL_MEDIA_CALL_NAME = "externalmedia";
52*37748cd8SNickeau
53*37748cd8SNickeau    const CANONICAL = "image";
54*37748cd8SNickeau
55*37748cd8SNickeau    /**
56*37748cd8SNickeau     * This attributes does not apply
57*37748cd8SNickeau     * to a URL
58*37748cd8SNickeau     * They are only for the tag (img, svg, ...)
59*37748cd8SNickeau     * or internal
60*37748cd8SNickeau     */
61*37748cd8SNickeau    const NON_URL_ATTRIBUTES = [
62*37748cd8SNickeau        self::ALIGN_KEY,
63*37748cd8SNickeau        self::LINKING_KEY,
64*37748cd8SNickeau        TagAttributes::TITLE_KEY,
65*37748cd8SNickeau        Hover::ON_HOVER_ATTRIBUTE,
66*37748cd8SNickeau        Animation::ON_VIEW_ATTRIBUTE,
67*37748cd8SNickeau        MediaLink::MEDIA_DOKUWIKI_TYPE,
68*37748cd8SNickeau        MediaLink::DOKUWIKI_SRC
69*37748cd8SNickeau    ];
70*37748cd8SNickeau
71*37748cd8SNickeau    /**
72*37748cd8SNickeau     * This attribute applies
73*37748cd8SNickeau     * to a image url (img, svg, ...)
74*37748cd8SNickeau     */
75*37748cd8SNickeau    const URL_ATTRIBUTES = [
76*37748cd8SNickeau        Dimension::WIDTH_KEY,
77*37748cd8SNickeau        Dimension::HEIGHT_KEY,
78*37748cd8SNickeau        CacheMedia::CACHE_KEY,
79*37748cd8SNickeau    ];
80*37748cd8SNickeau
81*37748cd8SNickeau    /**
82*37748cd8SNickeau     * Default image linking value
83*37748cd8SNickeau     */
84*37748cd8SNickeau    const CONF_DEFAULT_LINKING = "defaultImageLinking";
85*37748cd8SNickeau    const LINKING_LINKONLY_VALUE = "linkonly";
86*37748cd8SNickeau    const LINKING_DETAILS_VALUE = 'details';
87*37748cd8SNickeau    const LINKING_NOLINK_VALUE = 'nolink';
88*37748cd8SNickeau
89*37748cd8SNickeau    /**
90*37748cd8SNickeau     * @deprecated 2021-06-12
91*37748cd8SNickeau     */
92*37748cd8SNickeau    const LINK_PATTERN = "{{\s*([^|\s]*)\s*\|?.*}}";
93*37748cd8SNickeau
94*37748cd8SNickeau    const LINKING_DIRECT_VALUE = 'direct';
95*37748cd8SNickeau
96*37748cd8SNickeau    /**
97*37748cd8SNickeau     * Only used by Dokuwiki
98*37748cd8SNickeau     * Contains the path and eventually an anchor
99*37748cd8SNickeau     * never query parameters
100*37748cd8SNickeau     */
101*37748cd8SNickeau    const DOKUWIKI_SRC = "src";
102*37748cd8SNickeau    /**
103*37748cd8SNickeau     * Link value:
104*37748cd8SNickeau     *   * 'nolink'
105*37748cd8SNickeau     *   * 'direct': directly to the image
106*37748cd8SNickeau     *   * 'linkonly': show only a url
107*37748cd8SNickeau     *   * 'details': go to the details media viewer
108*37748cd8SNickeau     *
109*37748cd8SNickeau     * @var
110*37748cd8SNickeau     */
111*37748cd8SNickeau    const LINKING_KEY = 'linking';
112*37748cd8SNickeau    const ALIGN_KEY = 'align';
113*37748cd8SNickeau
114*37748cd8SNickeau
115*37748cd8SNickeau    private $lazyLoad = null;
116*37748cd8SNickeau
117*37748cd8SNickeau
118*37748cd8SNickeau    /**
119*37748cd8SNickeau     * @var TagAttributes
120*37748cd8SNickeau     */
121*37748cd8SNickeau    protected $tagAttributes;
122*37748cd8SNickeau
123*37748cd8SNickeau
124*37748cd8SNickeau    /**
125*37748cd8SNickeau     * Image constructor.
126*37748cd8SNickeau     * @param $ref
127*37748cd8SNickeau     * @param TagAttributes $tagAttributes
128*37748cd8SNickeau     * @param string $rev - mtime
129*37748cd8SNickeau     *
130*37748cd8SNickeau     * Protected and not private
131*37748cd8SNickeau     * to allow cascading init
132*37748cd8SNickeau     * If private, the parent attributes are null
133*37748cd8SNickeau     *
134*37748cd8SNickeau     */
135*37748cd8SNickeau    protected function __construct($absolutePath, $tagAttributes = null, $rev = null)
136*37748cd8SNickeau    {
137*37748cd8SNickeau
138*37748cd8SNickeau        parent::__construct($absolutePath, DokuPath::MEDIA_TYPE, $rev);
139*37748cd8SNickeau
140*37748cd8SNickeau        if ($tagAttributes == null) {
141*37748cd8SNickeau            $this->tagAttributes = TagAttributes::createEmpty();
142*37748cd8SNickeau        } else {
143*37748cd8SNickeau            $this->tagAttributes = $tagAttributes;
144*37748cd8SNickeau        }
145*37748cd8SNickeau
146*37748cd8SNickeau    }
147*37748cd8SNickeau
148*37748cd8SNickeau
149*37748cd8SNickeau    /**
150*37748cd8SNickeau     * Create an image from dokuwiki internal call media attributes
151*37748cd8SNickeau     * @param array $callAttributes
152*37748cd8SNickeau     * @return MediaLink
153*37748cd8SNickeau     */
154*37748cd8SNickeau    public static function createFromIndexAttributes(array $callAttributes)
155*37748cd8SNickeau    {
156*37748cd8SNickeau        $id = $callAttributes[0]; // path
157*37748cd8SNickeau        $title = $callAttributes[1];
158*37748cd8SNickeau        $align = $callAttributes[2];
159*37748cd8SNickeau        $width = $callAttributes[3];
160*37748cd8SNickeau        $height = $callAttributes[4];
161*37748cd8SNickeau        $cache = $callAttributes[5];
162*37748cd8SNickeau        $linking = $callAttributes[6];
163*37748cd8SNickeau
164*37748cd8SNickeau        $tagAttributes = TagAttributes::createEmpty();
165*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(TagAttributes::TITLE_KEY, $title);
166*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(self::ALIGN_KEY, $align);
167*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, $width);
168*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(Dimension::HEIGHT_KEY, $height);
169*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(CacheMedia::CACHE_KEY, $cache);
170*37748cd8SNickeau        $tagAttributes->addComponentAttributeValue(self::LINKING_KEY, $linking);
171*37748cd8SNickeau
172*37748cd8SNickeau        return self::createMediaLinkFromNonQualifiedPath($id, $tagAttributes);
173*37748cd8SNickeau
174*37748cd8SNickeau    }
175*37748cd8SNickeau
176*37748cd8SNickeau    /**
177*37748cd8SNickeau     * A function to explicitly create an internal media from
178*37748cd8SNickeau     * a call stack array (ie key string and value) that we get in the {@link SyntaxPlugin::render()}
179*37748cd8SNickeau     * from the {@link MediaLink::toCallStackArray()}
180*37748cd8SNickeau     *
181*37748cd8SNickeau     * @param $attributes - the attributes created by the function {@link MediaLink::getParseAttributes()}
182*37748cd8SNickeau     * @param $rev - the mtime
183*37748cd8SNickeau     * @return MediaLink|RasterImageLink|SvgImageLink
184*37748cd8SNickeau     */
185*37748cd8SNickeau    public static function createFromCallStackArray($attributes, $rev = null)
186*37748cd8SNickeau    {
187*37748cd8SNickeau
188*37748cd8SNickeau        if (!is_array($attributes)) {
189*37748cd8SNickeau            // Debug for the key_exist below because of the following message:
190*37748cd8SNickeau            // `PHP Warning:  key_exists() expects parameter 2 to be array, array given`
191*37748cd8SNickeau            LogUtility::msg("The `attributes` parameter is not an array. Value ($attributes)", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
192*37748cd8SNickeau        }
193*37748cd8SNickeau
194*37748cd8SNickeau        /**
195*37748cd8SNickeau         * Media id are not cleaned
196*37748cd8SNickeau         * They are always absolute ?
197*37748cd8SNickeau         */
198*37748cd8SNickeau        if (!isset($attributes[DokuPath::PATH_ATTRIBUTE])) {
199*37748cd8SNickeau            $path = "notfound";
200*37748cd8SNickeau            LogUtility::msg("A path attribute is mandatory when creating a media link and was not found in the attributes " . print_r($attributes, true), LogUtility::LVL_MSG_ERROR, self::CANONICAL);
201*37748cd8SNickeau        } else {
202*37748cd8SNickeau            $path = $attributes[DokuPath::PATH_ATTRIBUTE];
203*37748cd8SNickeau            unset($attributes[DokuPath::PATH_ATTRIBUTE]);
204*37748cd8SNickeau        }
205*37748cd8SNickeau
206*37748cd8SNickeau
207*37748cd8SNickeau        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
208*37748cd8SNickeau
209*37748cd8SNickeau
210*37748cd8SNickeau        return self::createMediaLinkFromNonQualifiedPath($path, $rev, $tagAttributes);
211*37748cd8SNickeau
212*37748cd8SNickeau    }
213*37748cd8SNickeau
214*37748cd8SNickeau    /**
215*37748cd8SNickeau     * @param $match - the match of the renderer (just a shortcut)
216*37748cd8SNickeau     * @return MediaLink
217*37748cd8SNickeau     */
218*37748cd8SNickeau    public static function createFromRenderMatch($match)
219*37748cd8SNickeau    {
220*37748cd8SNickeau
221*37748cd8SNickeau        /**
222*37748cd8SNickeau         * The parsing function {@link Doku_Handler_Parse_Media} has some flow / problem
223*37748cd8SNickeau         *    * It keeps the anchor only if there is no query string
224*37748cd8SNickeau         *    * It takes the first digit as the width (ie media.pdf?page=31 would have a width of 31)
225*37748cd8SNickeau         *    * `src` is not only the media path but may have a anchor
226*37748cd8SNickeau         * We parse it then
227*37748cd8SNickeau         */
228*37748cd8SNickeau
229*37748cd8SNickeau
230*37748cd8SNickeau        /**
231*37748cd8SNickeau         *   * Delete the opening and closing character
232*37748cd8SNickeau         *   * create the url and description
233*37748cd8SNickeau         */
234*37748cd8SNickeau        $match = preg_replace(array('/^\{\{/', '/\}\}$/u'), '', $match);
235*37748cd8SNickeau        $parts = explode('|', $match, 2);
236*37748cd8SNickeau        $description = null;
237*37748cd8SNickeau        $url = $parts[0];
238*37748cd8SNickeau        if (isset($parts[1])) {
239*37748cd8SNickeau            $description = $parts[1];
240*37748cd8SNickeau        }
241*37748cd8SNickeau
242*37748cd8SNickeau        /**
243*37748cd8SNickeau         * Media Alignment
244*37748cd8SNickeau         */
245*37748cd8SNickeau        $rightAlign = (bool)preg_match('/^ /', $url);
246*37748cd8SNickeau        $leftAlign = (bool)preg_match('/ $/', $url);
247*37748cd8SNickeau        $url = trim($url);
248*37748cd8SNickeau
249*37748cd8SNickeau        // Logic = what's that ;)...
250*37748cd8SNickeau        if ($leftAlign & $rightAlign) {
251*37748cd8SNickeau            $align = 'center';
252*37748cd8SNickeau        } else if ($rightAlign) {
253*37748cd8SNickeau            $align = 'right';
254*37748cd8SNickeau        } else if ($leftAlign) {
255*37748cd8SNickeau            $align = 'left';
256*37748cd8SNickeau        } else {
257*37748cd8SNickeau            $align = null;
258*37748cd8SNickeau        }
259*37748cd8SNickeau
260*37748cd8SNickeau        /**
261*37748cd8SNickeau         * The combo attributes array
262*37748cd8SNickeau         */
263*37748cd8SNickeau        $parsedAttributes = DokuwikiUrl::createFromUrl($url)->toArray();
264*37748cd8SNickeau        $path = $parsedAttributes[DokuPath::PATH_ATTRIBUTE];
265*37748cd8SNickeau        if (!isset($parsedAttributes[MediaLink::LINKING_KEY])) {
266*37748cd8SNickeau            $parsedAttributes[MediaLink::LINKING_KEY] = PluginUtility::getConfValue(self::CONF_DEFAULT_LINKING, self::LINKING_DIRECT_VALUE);
267*37748cd8SNickeau        }
268*37748cd8SNickeau
269*37748cd8SNickeau        /**
270*37748cd8SNickeau         * Media Type
271*37748cd8SNickeau         */
272*37748cd8SNickeau        if (media_isexternal($path) || link_isinterwiki($path)) {
273*37748cd8SNickeau            $mediaType = MediaLink::EXTERNAL_MEDIA_CALL_NAME;
274*37748cd8SNickeau        } else {
275*37748cd8SNickeau            $mediaType = MediaLink::INTERNAL_MEDIA_CALL_NAME;
276*37748cd8SNickeau        }
277*37748cd8SNickeau
278*37748cd8SNickeau
279*37748cd8SNickeau        /**
280*37748cd8SNickeau         * src in dokuwiki is the path and the anchor if any
281*37748cd8SNickeau         */
282*37748cd8SNickeau        $src = $path;
283*37748cd8SNickeau        if (isset($parsedAttributes[DokuwikiUrl::ANCHOR_ATTRIBUTES]) != null) {
284*37748cd8SNickeau            $src = $src . "#" . $parsedAttributes[DokuwikiUrl::ANCHOR_ATTRIBUTES];
285*37748cd8SNickeau        }
286*37748cd8SNickeau
287*37748cd8SNickeau        /**
288*37748cd8SNickeau         * To avoid clash with the combostrap component type
289*37748cd8SNickeau         * ie this is also a ComboStrap attribute where we set the type of a SVG (icon, illustration, background)
290*37748cd8SNickeau         * we store the media type (ie external/internal) in another key
291*37748cd8SNickeau         *
292*37748cd8SNickeau         * There is no need to repeat the attributes as the arrays are merged
293*37748cd8SNickeau         * into on but this is also an informal code to show which attributes
294*37748cd8SNickeau         * are only Dokuwiki Native
295*37748cd8SNickeau         *
296*37748cd8SNickeau         */
297*37748cd8SNickeau        $dokuwikiAttributes = array(
298*37748cd8SNickeau            self::MEDIA_DOKUWIKI_TYPE => $mediaType,
299*37748cd8SNickeau            self::DOKUWIKI_SRC => $src,
300*37748cd8SNickeau            Dimension::WIDTH_KEY => $parsedAttributes[Dimension::WIDTH_KEY],
301*37748cd8SNickeau            Dimension::HEIGHT_KEY => $parsedAttributes[Dimension::HEIGHT_KEY],
302*37748cd8SNickeau            CacheMedia::CACHE_KEY => $parsedAttributes[CacheMedia::CACHE_KEY],
303*37748cd8SNickeau            'title' => $description,
304*37748cd8SNickeau            MediaLink::ALIGN_KEY => $align,
305*37748cd8SNickeau            MediaLink::LINKING_KEY => $parsedAttributes[MediaLink::LINKING_KEY],
306*37748cd8SNickeau        );
307*37748cd8SNickeau
308*37748cd8SNickeau        /**
309*37748cd8SNickeau         * Merge standard dokuwiki attributes and
310*37748cd8SNickeau         * parsed attributes
311*37748cd8SNickeau         */
312*37748cd8SNickeau        $mergedAttributes = PluginUtility::mergeAttributes($dokuwikiAttributes, $parsedAttributes);
313*37748cd8SNickeau
314*37748cd8SNickeau        /**
315*37748cd8SNickeau         * If this is an internal media,
316*37748cd8SNickeau         * we are using our implementation
317*37748cd8SNickeau         * and we have a change on attribute specification
318*37748cd8SNickeau         */
319*37748cd8SNickeau        if ($mediaType == MediaLink::INTERNAL_MEDIA_CALL_NAME) {
320*37748cd8SNickeau
321*37748cd8SNickeau            /**
322*37748cd8SNickeau             * The align attribute on an image parse
323*37748cd8SNickeau             * is a float right
324*37748cd8SNickeau             * ComboStrap does a difference between a block right and a float right
325*37748cd8SNickeau             */
326*37748cd8SNickeau            if ($mergedAttributes[self::ALIGN_KEY] === "right") {
327*37748cd8SNickeau                unset($mergedAttributes[self::ALIGN_KEY]);
328*37748cd8SNickeau                $mergedAttributes[FloatAttribute::FLOAT_KEY] = "right";
329*37748cd8SNickeau            }
330*37748cd8SNickeau
331*37748cd8SNickeau
332*37748cd8SNickeau        }
333*37748cd8SNickeau
334*37748cd8SNickeau        return self::createFromCallStackArray($mergedAttributes);
335*37748cd8SNickeau
336*37748cd8SNickeau    }
337*37748cd8SNickeau
338*37748cd8SNickeau
339*37748cd8SNickeau    public
340*37748cd8SNickeau    function setLazyLoad($false)
341*37748cd8SNickeau    {
342*37748cd8SNickeau        $this->lazyLoad = $false;
343*37748cd8SNickeau    }
344*37748cd8SNickeau
345*37748cd8SNickeau    public
346*37748cd8SNickeau    function getLazyLoad()
347*37748cd8SNickeau    {
348*37748cd8SNickeau        return $this->lazyLoad;
349*37748cd8SNickeau    }
350*37748cd8SNickeau
351*37748cd8SNickeau
352*37748cd8SNickeau    /**
353*37748cd8SNickeau     * Create a media link from a unknown type path (ie relative or absolute)
354*37748cd8SNickeau     *
355*37748cd8SNickeau     * This function transforms the path to absolute against the actual namespace of the requested page ID if the
356*37748cd8SNickeau     * path is relative.
357*37748cd8SNickeau     *
358*37748cd8SNickeau     * @param $nonQualifiedPath
359*37748cd8SNickeau     * @param TagAttributes $tagAttributes
360*37748cd8SNickeau     * @param string $rev
361*37748cd8SNickeau     * @return MediaLink
362*37748cd8SNickeau     */
363*37748cd8SNickeau    public
364*37748cd8SNickeau    static function createMediaLinkFromNonQualifiedPath($nonQualifiedPath, $rev = null, $tagAttributes = null)
365*37748cd8SNickeau    {
366*37748cd8SNickeau        if (is_object($rev)) {
367*37748cd8SNickeau            LogUtility::msg("rev should not be an object", LogUtility::LVL_MSG_ERROR, "support");
368*37748cd8SNickeau        }
369*37748cd8SNickeau        if ($tagAttributes == null) {
370*37748cd8SNickeau            $tagAttributes = TagAttributes::createEmpty();
371*37748cd8SNickeau        } else {
372*37748cd8SNickeau            if (!($tagAttributes instanceof TagAttributes)) {
373*37748cd8SNickeau                LogUtility::msg("TagAttributes is not an instance of Tag Attributes", LogUtility::LVL_MSG_ERROR, "support");
374*37748cd8SNickeau            }
375*37748cd8SNickeau        }
376*37748cd8SNickeau
377*37748cd8SNickeau        /**
378*37748cd8SNickeau         * Resolution
379*37748cd8SNickeau         */
380*37748cd8SNickeau        $qualifiedPath = $nonQualifiedPath;
381*37748cd8SNickeau        if(!media_isexternal($qualifiedPath)) {
382*37748cd8SNickeau            global $ID;
383*37748cd8SNickeau            $qualifiedId = $nonQualifiedPath;
384*37748cd8SNickeau            resolve_mediaid(getNS($ID), $qualifiedId, $exists);
385*37748cd8SNickeau            $qualifiedPath = DokuPath::PATH_SEPARATOR . $qualifiedId;
386*37748cd8SNickeau        }
387*37748cd8SNickeau
388*37748cd8SNickeau        /**
389*37748cd8SNickeau         * Processing
390*37748cd8SNickeau         */
391*37748cd8SNickeau        $dokuPath = DokuPath::createMediaPathFromAbsolutePath($qualifiedPath, $rev);
392*37748cd8SNickeau        if ($dokuPath->getExtension() == "svg") {
393*37748cd8SNickeau            /**
394*37748cd8SNickeau             * The mime type is set when uploading, not when
395*37748cd8SNickeau             * viewing.
396*37748cd8SNickeau             * Because they are internal image, the svg was already uploaded
397*37748cd8SNickeau             * Therefore, no authorization scheme here
398*37748cd8SNickeau             */
399*37748cd8SNickeau            $mime = "image/svg+xml";
400*37748cd8SNickeau        } else {
401*37748cd8SNickeau            $mime = $dokuPath->getKnownMime();
402*37748cd8SNickeau        }
403*37748cd8SNickeau
404*37748cd8SNickeau        if (substr($mime, 0, 5) == 'image') {
405*37748cd8SNickeau            if (substr($mime, 6) == "svg+xml") {
406*37748cd8SNickeau                // The require is here because Svg Image Link is child of Internal Media Link (extends)
407*37748cd8SNickeau                require_once(__DIR__ . '/SvgImageLink.php');
408*37748cd8SNickeau                $internalMedia = new SvgImageLink($qualifiedPath, $tagAttributes, $rev);
409*37748cd8SNickeau            } else {
410*37748cd8SNickeau                // The require is here because Raster Image Link is child of Internal Media Link (extends)
411*37748cd8SNickeau                require_once(__DIR__ . '/RasterImageLink.php');
412*37748cd8SNickeau                $internalMedia = new RasterImageLink($qualifiedPath, $tagAttributes);
413*37748cd8SNickeau            }
414*37748cd8SNickeau        } else {
415*37748cd8SNickeau            if ($mime == false) {
416*37748cd8SNickeau                LogUtility::msg("The mime type of the media ($nonQualifiedPath) is <a href=\"https://www.dokuwiki.org/mime\">unknown (not in the configuration file)</a>", LogUtility::LVL_MSG_ERROR, "support");
417*37748cd8SNickeau                $internalMedia = new RasterImageLink($qualifiedPath, $tagAttributes);
418*37748cd8SNickeau            } else {
419*37748cd8SNickeau                LogUtility::msg("The type ($mime) of media ($nonQualifiedPath) is not an image", LogUtility::LVL_MSG_DEBUG, "image");
420*37748cd8SNickeau                $internalMedia = new ThirdMediaLink($qualifiedPath, $tagAttributes);
421*37748cd8SNickeau            }
422*37748cd8SNickeau        }
423*37748cd8SNickeau
424*37748cd8SNickeau
425*37748cd8SNickeau        return $internalMedia;
426*37748cd8SNickeau    }
427*37748cd8SNickeau
428*37748cd8SNickeau
429*37748cd8SNickeau    /**
430*37748cd8SNickeau     * A function to set explicitly which array format
431*37748cd8SNickeau     * is used in the returned data of a {@link SyntaxPlugin::handle()}
432*37748cd8SNickeau     * (which ultimately is stored in the {@link CallStack)
433*37748cd8SNickeau     *
434*37748cd8SNickeau     * This is to make the difference with the {@link MediaLink::createFromIndexAttributes()}
435*37748cd8SNickeau     * that is indexed by number (ie without property name)
436*37748cd8SNickeau     *
437*37748cd8SNickeau     *
438*37748cd8SNickeau     * Return the same array than with the {@link self::parse()} method
439*37748cd8SNickeau     * that is used in the {@link CallStack}
440*37748cd8SNickeau     *
441*37748cd8SNickeau     * @return array of key string and value
442*37748cd8SNickeau     */
443*37748cd8SNickeau    public
444*37748cd8SNickeau    function toCallStackArray()
445*37748cd8SNickeau    {
446*37748cd8SNickeau        /**
447*37748cd8SNickeau         * Trying to stay inline with the dokuwiki key
448*37748cd8SNickeau         * We use the 'src' attributes as id
449*37748cd8SNickeau         *
450*37748cd8SNickeau         * src is a path (not an id)
451*37748cd8SNickeau         */
452*37748cd8SNickeau        $array = array(
453*37748cd8SNickeau            DokuPath::PATH_ATTRIBUTE => $this->getPath()
454*37748cd8SNickeau        );
455*37748cd8SNickeau
456*37748cd8SNickeau
457*37748cd8SNickeau        // Add the extra attribute
458*37748cd8SNickeau        return array_merge($this->tagAttributes->toCallStackArray(), $array);
459*37748cd8SNickeau
460*37748cd8SNickeau
461*37748cd8SNickeau    }
462*37748cd8SNickeau
463*37748cd8SNickeau
464*37748cd8SNickeau    /**
465*37748cd8SNickeau     * @return string the wiki syntax
466*37748cd8SNickeau     */
467*37748cd8SNickeau    public
468*37748cd8SNickeau    function getMarkupSyntax()
469*37748cd8SNickeau    {
470*37748cd8SNickeau        $descriptionPart = "";
471*37748cd8SNickeau        if ($this->tagAttributes->hasComponentAttribute(TagAttributes::TITLE_KEY)) {
472*37748cd8SNickeau            $descriptionPart = "|" . $this->tagAttributes->getValue(TagAttributes::TITLE_KEY);
473*37748cd8SNickeau        }
474*37748cd8SNickeau        return '{{:' . $this->getId() . $descriptionPart . '}}';
475*37748cd8SNickeau    }
476*37748cd8SNickeau
477*37748cd8SNickeau
478*37748cd8SNickeau    public
479*37748cd8SNickeau    static function isInternalMediaSyntax($text)
480*37748cd8SNickeau    {
481*37748cd8SNickeau        return preg_match(' / ' . syntax_plugin_combo_media::MEDIA_PATTERN . ' / msSi', $text);
482*37748cd8SNickeau    }
483*37748cd8SNickeau
484*37748cd8SNickeau
485*37748cd8SNickeau    public
486*37748cd8SNickeau    function getRequestedHeight()
487*37748cd8SNickeau    {
488*37748cd8SNickeau        return $this->tagAttributes->getValue(Dimension::HEIGHT_KEY);
489*37748cd8SNickeau    }
490*37748cd8SNickeau
491*37748cd8SNickeau
492*37748cd8SNickeau    /**
493*37748cd8SNickeau     * The requested width
494*37748cd8SNickeau     */
495*37748cd8SNickeau    public
496*37748cd8SNickeau    function getRequestedWidth()
497*37748cd8SNickeau    {
498*37748cd8SNickeau        return $this->tagAttributes->getValue(Dimension::WIDTH_KEY);
499*37748cd8SNickeau    }
500*37748cd8SNickeau
501*37748cd8SNickeau
502*37748cd8SNickeau    public
503*37748cd8SNickeau    function getCache()
504*37748cd8SNickeau    {
505*37748cd8SNickeau        return $this->tagAttributes->getValue(CacheMedia::CACHE_KEY);
506*37748cd8SNickeau    }
507*37748cd8SNickeau
508*37748cd8SNickeau    protected
509*37748cd8SNickeau    function getTitle()
510*37748cd8SNickeau    {
511*37748cd8SNickeau        return $this->tagAttributes->getValue(TagAttributes::TITLE_KEY);
512*37748cd8SNickeau    }
513*37748cd8SNickeau
514*37748cd8SNickeau
515*37748cd8SNickeau    public
516*37748cd8SNickeau    function __toString()
517*37748cd8SNickeau    {
518*37748cd8SNickeau        return $this->getId();
519*37748cd8SNickeau    }
520*37748cd8SNickeau
521*37748cd8SNickeau    private
522*37748cd8SNickeau    function getAlign()
523*37748cd8SNickeau    {
524*37748cd8SNickeau        return $this->getTagAttributes()->getComponentAttributeValue(self::ALIGN_KEY, null);
525*37748cd8SNickeau    }
526*37748cd8SNickeau
527*37748cd8SNickeau    private
528*37748cd8SNickeau    function getLinking()
529*37748cd8SNickeau    {
530*37748cd8SNickeau        return $this->getTagAttributes()->getComponentAttributeValue(self::LINKING_KEY, null);
531*37748cd8SNickeau    }
532*37748cd8SNickeau
533*37748cd8SNickeau
534*37748cd8SNickeau    public
535*37748cd8SNickeau    function &getTagAttributes()
536*37748cd8SNickeau    {
537*37748cd8SNickeau        return $this->tagAttributes;
538*37748cd8SNickeau    }
539*37748cd8SNickeau
540*37748cd8SNickeau    /**
541*37748cd8SNickeau     * @return string - the HTML of the image inside a link if asked
542*37748cd8SNickeau     */
543*37748cd8SNickeau    public
544*37748cd8SNickeau    function renderMediaTagWithLink()
545*37748cd8SNickeau    {
546*37748cd8SNickeau
547*37748cd8SNickeau        /**
548*37748cd8SNickeau         * Link to the media
549*37748cd8SNickeau         *
550*37748cd8SNickeau         */
551*37748cd8SNickeau        $imageLink = TagAttributes::createEmpty();
552*37748cd8SNickeau        // https://www.dokuwiki.org/config:target
553*37748cd8SNickeau        global $conf;
554*37748cd8SNickeau        $target = $conf['target']['media'];
555*37748cd8SNickeau        $imageLink->addHtmlAttributeValueIfNotEmpty("target", $target);
556*37748cd8SNickeau        if (!empty($target)) {
557*37748cd8SNickeau            $imageLink->addHtmlAttributeValue("rel", 'noopener');
558*37748cd8SNickeau        }
559*37748cd8SNickeau
560*37748cd8SNickeau        /**
561*37748cd8SNickeau         * Do we add a link to the image ?
562*37748cd8SNickeau         */
563*37748cd8SNickeau        $linking = $this->tagAttributes->getValue(self::LINKING_KEY);
564*37748cd8SNickeau        switch ($linking) {
565*37748cd8SNickeau            case self::LINKING_LINKONLY_VALUE: // show only a url
566*37748cd8SNickeau                $src = ml(
567*37748cd8SNickeau                    $this->getId(),
568*37748cd8SNickeau                    array(
569*37748cd8SNickeau                        'id' => $this->getId(),
570*37748cd8SNickeau                        'cache' => $this->getCache(),
571*37748cd8SNickeau                        'rev' => $this->getRevision()
572*37748cd8SNickeau                    )
573*37748cd8SNickeau                );
574*37748cd8SNickeau                $imageLink->addHtmlAttributeValue("href", $src);
575*37748cd8SNickeau                $title = $this->getTitle();
576*37748cd8SNickeau                if (empty($title)) {
577*37748cd8SNickeau                    $title = $this->getBaseName();
578*37748cd8SNickeau                }
579*37748cd8SNickeau                return $imageLink->toHtmlEnterTag("a") . $title . "</a>";
580*37748cd8SNickeau            case self::LINKING_NOLINK_VALUE:
581*37748cd8SNickeau                return $this->renderMediaTag();
582*37748cd8SNickeau            default:
583*37748cd8SNickeau            case self::LINKING_DIRECT_VALUE:
584*37748cd8SNickeau                //directly to the image
585*37748cd8SNickeau                $src = ml(
586*37748cd8SNickeau                    $this->getId(),
587*37748cd8SNickeau                    array(
588*37748cd8SNickeau                        'id' => $this->getId(),
589*37748cd8SNickeau                        'cache' => $this->getCache(),
590*37748cd8SNickeau                        'rev' => $this->getRevision()
591*37748cd8SNickeau                    ),
592*37748cd8SNickeau                    true
593*37748cd8SNickeau                );
594*37748cd8SNickeau                $imageLink->addHtmlAttributeValue("href", $src);
595*37748cd8SNickeau                return $imageLink->toHtmlEnterTag("a") .
596*37748cd8SNickeau                    $this->renderMediaTag() .
597*37748cd8SNickeau                    "</a>";
598*37748cd8SNickeau
599*37748cd8SNickeau            case self::LINKING_DETAILS_VALUE:
600*37748cd8SNickeau                //go to the details media viewer
601*37748cd8SNickeau                $src = ml(
602*37748cd8SNickeau                    $this->getId(),
603*37748cd8SNickeau                    array(
604*37748cd8SNickeau                        'id' => $this->getId(),
605*37748cd8SNickeau                        'cache' => $this->getCache(),
606*37748cd8SNickeau                        'rev' => $this->getRevision()
607*37748cd8SNickeau                    ),
608*37748cd8SNickeau                    false
609*37748cd8SNickeau                );
610*37748cd8SNickeau                $imageLink->addHtmlAttributeValue("href", $src);
611*37748cd8SNickeau                return $imageLink->toHtmlEnterTag("a") .
612*37748cd8SNickeau                    $this->renderMediaTag() .
613*37748cd8SNickeau                    "</a>";
614*37748cd8SNickeau
615*37748cd8SNickeau        }
616*37748cd8SNickeau
617*37748cd8SNickeau
618*37748cd8SNickeau    }
619*37748cd8SNickeau
620*37748cd8SNickeau    /**
621*37748cd8SNickeau     * @param $imgTagHeight
622*37748cd8SNickeau     * @param $imgTagWidth
623*37748cd8SNickeau     * @return float|mixed
624*37748cd8SNickeau     */
625*37748cd8SNickeau    public
626*37748cd8SNickeau    function checkWidthAndHeightRatioAndReturnTheGoodValue($imgTagWidth, $imgTagHeight)
627*37748cd8SNickeau    {
628*37748cd8SNickeau        /**
629*37748cd8SNickeau         * Check of height and width dimension
630*37748cd8SNickeau         * as specified here
631*37748cd8SNickeau         * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height
632*37748cd8SNickeau         */
633*37748cd8SNickeau        $targetRatio = $this->getTargetRatio();
634*37748cd8SNickeau        if (!(
635*37748cd8SNickeau            $imgTagHeight * $targetRatio >= $imgTagWidth - 0.5
636*37748cd8SNickeau            &&
637*37748cd8SNickeau            $imgTagHeight * $targetRatio <= $imgTagWidth + 0.5
638*37748cd8SNickeau        )) {
639*37748cd8SNickeau            // check the second statement
640*37748cd8SNickeau            if (!(
641*37748cd8SNickeau                $imgTagWidth / $targetRatio >= $imgTagHeight - 0.5
642*37748cd8SNickeau                &&
643*37748cd8SNickeau                $imgTagWidth / $targetRatio <= $imgTagHeight + 0.5
644*37748cd8SNickeau            )) {
645*37748cd8SNickeau                $requestedHeight = $this->getRequestedHeight();
646*37748cd8SNickeau                $requestedWidth = $this->getRequestedWidth();
647*37748cd8SNickeau                if (
648*37748cd8SNickeau                    !empty($requestedHeight)
649*37748cd8SNickeau                    && !empty($requestedWidth)
650*37748cd8SNickeau                ) {
651*37748cd8SNickeau                    /**
652*37748cd8SNickeau                     * The user has asked for a width and height
653*37748cd8SNickeau                     */
654*37748cd8SNickeau                    $imgTagWidth = round($imgTagHeight * $targetRatio);
655*37748cd8SNickeau                    LogUtility::msg("The width ($requestedWidth) and height ($requestedHeight) specified on the image ($this) does not follow the natural ratio as <a href=\"https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height\">required by HTML</a>. The width was then set to ($imgTagWidth).", LogUtility::LVL_MSG_INFO, self::CANONICAL);
656*37748cd8SNickeau                } else {
657*37748cd8SNickeau                    /**
658*37748cd8SNickeau                     * Programmatic error from the developer
659*37748cd8SNickeau                     */
660*37748cd8SNickeau                    $imgTagRatio = $imgTagWidth / $imgTagHeight;
661*37748cd8SNickeau                    LogUtility::msg("Internal Error: The width ($imgTagWidth) and height ($imgTagHeight) calculated for the image ($this) does not pass the ratio test. They have a ratio of ($imgTagRatio) while the natural dimension ratio is ($targetRatio)");
662*37748cd8SNickeau                }
663*37748cd8SNickeau            }
664*37748cd8SNickeau        }
665*37748cd8SNickeau        return $imgTagWidth;
666*37748cd8SNickeau    }
667*37748cd8SNickeau
668*37748cd8SNickeau    /**
669*37748cd8SNickeau     * Target ratio as explained here
670*37748cd8SNickeau     * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height
671*37748cd8SNickeau     * @return float|int|false
672*37748cd8SNickeau     * false if the image is not supported
673*37748cd8SNickeau     *
674*37748cd8SNickeau     * It's needed for an img tag to set the img `width` and `height` that pass the
675*37748cd8SNickeau     * {@link MediaLink::checkWidthAndHeightRatioAndReturnTheGoodValue() check}
676*37748cd8SNickeau     * to avoid layout shift
677*37748cd8SNickeau     *
678*37748cd8SNickeau     */
679*37748cd8SNickeau    protected function getTargetRatio()
680*37748cd8SNickeau    {
681*37748cd8SNickeau        if ($this->getMediaHeight() == null || $this->getMediaWidth() == null) {
682*37748cd8SNickeau            return false;
683*37748cd8SNickeau        } else {
684*37748cd8SNickeau            return $this->getMediaWidth() / $this->getMediaHeight();
685*37748cd8SNickeau        }
686*37748cd8SNickeau    }
687*37748cd8SNickeau
688*37748cd8SNickeau    /**
689*37748cd8SNickeau     * Return the height that the image should take on the screen
690*37748cd8SNickeau     * for the specified size
691*37748cd8SNickeau     *
692*37748cd8SNickeau     * @param null $localRequestedWidth - the width to derive the height from (in case the image is created for responsive lazy loading)
693*37748cd8SNickeau     * if not specified, the requested width and if not specified the intrinsic width
694*37748cd8SNickeau     * @return int the height value attribute in a img
695*37748cd8SNickeau     */
696*37748cd8SNickeau    public
697*37748cd8SNickeau    function getImgTagHeightValue($localRequestedWidth = null)
698*37748cd8SNickeau    {
699*37748cd8SNickeau
700*37748cd8SNickeau        /**
701*37748cd8SNickeau         * Cropping is not yet supported.
702*37748cd8SNickeau         */
703*37748cd8SNickeau        $requestedHeight = $this->getRequestedHeight();
704*37748cd8SNickeau        $requestedWidth = $this->getRequestedWidth();
705*37748cd8SNickeau        if (
706*37748cd8SNickeau            $requestedHeight != null
707*37748cd8SNickeau            && $requestedHeight != 0
708*37748cd8SNickeau            && $requestedWidth != null
709*37748cd8SNickeau            && $requestedWidth != 0
710*37748cd8SNickeau        ) {
711*37748cd8SNickeau            global $ID;
712*37748cd8SNickeau            if ($ID != "wiki:syntax") {
713*37748cd8SNickeau                /**
714*37748cd8SNickeau                 * Cropping
715*37748cd8SNickeau                 */
716*37748cd8SNickeau                LogUtility::msg("The width and height has been set on the image ($this) but we don't support yet cropping. Set only the width or the height (0x250)", LogUtility::LVL_MSG_WARNING, self::CANONICAL);
717*37748cd8SNickeau            }
718*37748cd8SNickeau        }
719*37748cd8SNickeau
720*37748cd8SNickeau        /**
721*37748cd8SNickeau         * If resize by height, the img tag height is the requested height
722*37748cd8SNickeau         */
723*37748cd8SNickeau        if ($localRequestedWidth == null) {
724*37748cd8SNickeau            if ($requestedHeight != null) {
725*37748cd8SNickeau                return $requestedHeight;
726*37748cd8SNickeau            } else {
727*37748cd8SNickeau                $localRequestedWidth = $this->getRequestedWidth();
728*37748cd8SNickeau                if (empty($localRequestedWidth)) {
729*37748cd8SNickeau                    $localRequestedWidth = $this->getMediaWidth();
730*37748cd8SNickeau                }
731*37748cd8SNickeau            }
732*37748cd8SNickeau        }
733*37748cd8SNickeau
734*37748cd8SNickeau        /**
735*37748cd8SNickeau         * Computation
736*37748cd8SNickeau         */
737*37748cd8SNickeau        $computedHeight = $this->getRequestedHeight();
738*37748cd8SNickeau        $targetRatio = $this->getTargetRatio();
739*37748cd8SNickeau        if ($targetRatio !== false) {
740*37748cd8SNickeau
741*37748cd8SNickeau            /**
742*37748cd8SNickeau             * Scale the height by target ratio
743*37748cd8SNickeau             */
744*37748cd8SNickeau            $computedHeight = $localRequestedWidth / $this->getTargetRatio();
745*37748cd8SNickeau
746*37748cd8SNickeau            /**
747*37748cd8SNickeau             * Check
748*37748cd8SNickeau             */
749*37748cd8SNickeau            if ($requestedHeight != null) {
750*37748cd8SNickeau                if ($requestedHeight < $computedHeight) {
751*37748cd8SNickeau                    LogUtility::msg("The computed height cannot be greater than the requested height");
752*37748cd8SNickeau                }
753*37748cd8SNickeau            }
754*37748cd8SNickeau
755*37748cd8SNickeau        }
756*37748cd8SNickeau
757*37748cd8SNickeau
758*37748cd8SNickeau        /**
759*37748cd8SNickeau         * Rounding to integer
760*37748cd8SNickeau         * The fetch.php file takes int as value for width and height
761*37748cd8SNickeau         * making a rounding if we pass a double (such as 37.5)
762*37748cd8SNickeau         * This is important because the security token is based on width and height
763*37748cd8SNickeau         * and therefore the fetch will failed
764*37748cd8SNickeau         *
765*37748cd8SNickeau         * And not directly {@link intval} because it will make from 3.6, 3 and not 4
766*37748cd8SNickeau         */
767*37748cd8SNickeau        return intval(round($computedHeight));
768*37748cd8SNickeau
769*37748cd8SNickeau    }
770*37748cd8SNickeau
771*37748cd8SNickeau    /**
772*37748cd8SNickeau     * @return int - the width value attribute in a img (in CSS pixel that the image should takes)
773*37748cd8SNickeau     */
774*37748cd8SNickeau    public
775*37748cd8SNickeau    function getImgTagWidthValue()
776*37748cd8SNickeau    {
777*37748cd8SNickeau        $linkWidth = $this->getRequestedWidth();
778*37748cd8SNickeau        if (empty($linkWidth)) {
779*37748cd8SNickeau            if (empty($this->getRequestedHeight())) {
780*37748cd8SNickeau
781*37748cd8SNickeau                $linkWidth = $this->getMediaWidth();
782*37748cd8SNickeau
783*37748cd8SNickeau            } else {
784*37748cd8SNickeau
785*37748cd8SNickeau                // Height is not empty
786*37748cd8SNickeau                // We derive the width from it
787*37748cd8SNickeau                if ($this->getMediaHeight() != 0
788*37748cd8SNickeau                    && !empty($this->getMediaHeight())
789*37748cd8SNickeau                    && !empty($this->getMediaWidth())
790*37748cd8SNickeau                ) {
791*37748cd8SNickeau                    $linkWidth = $this->getMediaWidth() * ($this->getRequestedHeight() / $this->getMediaHeight());
792*37748cd8SNickeau                }
793*37748cd8SNickeau
794*37748cd8SNickeau            }
795*37748cd8SNickeau        }
796*37748cd8SNickeau        /**
797*37748cd8SNickeau         * Rounding to integer
798*37748cd8SNickeau         * The fetch.php file takes int as value for width and height
799*37748cd8SNickeau         * making a rounding if we pass a double (such as 37.5)
800*37748cd8SNickeau         * This is important because the security token is based on width and height
801*37748cd8SNickeau         * and therefore the fetch will failed
802*37748cd8SNickeau         *
803*37748cd8SNickeau         * And this is also ask by the specification
804*37748cd8SNickeau         * a non-null positive integer
805*37748cd8SNickeau         * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height
806*37748cd8SNickeau         *
807*37748cd8SNickeau         * And not {@link intval} because it will make from 3.6, 3 and not 4
808*37748cd8SNickeau         */
809*37748cd8SNickeau        return intval(round($linkWidth));
810*37748cd8SNickeau    }
811*37748cd8SNickeau
812*37748cd8SNickeau    /**
813*37748cd8SNickeau     * @return string - the HTML of the image
814*37748cd8SNickeau     */
815*37748cd8SNickeau    public abstract function renderMediaTag();
816*37748cd8SNickeau
817*37748cd8SNickeau    /**
818*37748cd8SNickeau     * The Url
819*37748cd8SNickeau     * @return mixed
820*37748cd8SNickeau     */
821*37748cd8SNickeau    public abstract function getAbsoluteUrl();
822*37748cd8SNickeau
823*37748cd8SNickeau    /**
824*37748cd8SNickeau     * For a raster image, the internal width
825*37748cd8SNickeau     * for a svg, the defined viewBox
826*37748cd8SNickeau     *
827*37748cd8SNickeau     * This is needed to calculate the {@link MediaLink::getTargetRatio() target ratio}
828*37748cd8SNickeau     * and pass them to the img tag to avoid layout shift
829*37748cd8SNickeau     *
830*37748cd8SNickeau     * @return mixed
831*37748cd8SNickeau     */
832*37748cd8SNickeau    public abstract function getMediaWidth();
833*37748cd8SNickeau
834*37748cd8SNickeau    /**
835*37748cd8SNickeau     * For a raster image, the internal height
836*37748cd8SNickeau     * for a svg, the defined `viewBox` value
837*37748cd8SNickeau     *
838*37748cd8SNickeau     * This is needed to calculate the {@link MediaLink::getTargetRatio() target ratio}
839*37748cd8SNickeau     * and pass them to the img tag to avoid layout shift
840*37748cd8SNickeau     *
841*37748cd8SNickeau     * @return mixed
842*37748cd8SNickeau     */
843*37748cd8SNickeau    public abstract function getMediaHeight();
844*37748cd8SNickeau
845*37748cd8SNickeau
846*37748cd8SNickeau}
847