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