1 <?php
2 
3 
4 namespace ComboStrap;
5 
6 
7 use ComboStrap\TagAttribute\Align;
8 use ComboStrap\TagAttribute\Animation;
9 use ComboStrap\TagAttribute\Shadow;
10 use ComboStrap\Web\Url;
11 use Doku_Renderer_metadata;
12 use Doku_Renderer_xhtml;
13 use renderer_plugin_combo_analytics;
14 use syntax_plugin_combo_media;
15 
16 /**
17  * This class represents a media markup:
18  *   - with a {@link MediaMarkup::getFetcher() fetcher}
19  *   - and {@link MediaMarkup::getExtraMediaTagAttributes() tag/styling attributes}
20  *
21  * You can create it:
22  *   * via a {@link MediaMarkup::createFromRef() Markup Ref} (The string ref in the document)
23  *   * via a {@link MediaMarkup::createFromFetcher() Fetcher}
24  *   * via a {@link MediaMarkup::createFromFetchUrl() Fetch Url}
25  *   * via a {@link MediaMarkup::createFromCallStackArray() callstack array} of {@link syntax_plugin_combo_media::render()}
26  *   * via a {@link MediaMarkup::createFromMarkup() string match} of {@link syntax_plugin_combo_media::handle()}
27  *
28  *
29  */
30 class MediaMarkup
31 {
32 
33     /**
34      * No name as this is {{ --- }}
35      */
36     public const TAG = "media";
37 
38     /**
39      * The dokuwiki type and mode name
40      * (ie call)
41      *  * ie {@link MediaMarkup::EXTERNAL_MEDIA_CALL_NAME}
42      *  or {@link MediaMarkup::INTERNAL_MEDIA_CALL_NAME}
43      *
44      * The dokuwiki type (internalmedia/externalmedia)
45      *
46      */
47     public const MEDIA_DOKUWIKI_TYPE = 'dokuwiki_media_type';
48     public const EXTERNAL_MEDIA_CALL_NAME = "externalmedia";
49     public const INTERNAL_MEDIA_CALL_NAME = "internalmedia";
50 
51     /**
52      * Link value:
53      *   * 'nolink'
54      *   * 'direct': directly to the image
55      *   * 'linkonly': show only a url
56      *   * 'details': go to the details media viewer
57      *
58      * @var
59      */
60     public const LINKING_KEY = 'linking';
61     public const LINKING_DETAILS_VALUE = 'details';
62     public const LINKING_DIRECT_VALUE = 'direct';
63     /**
64      * Only used by Dokuwiki
65      * Contains the path and eventually an anchor
66      * never query parameters
67      */
68     public const DOKUWIKI_SRC = "src";
69     public const LINKING_LINKONLY_VALUE = "linkonly";
70     public const LINKING_NOLINK_VALUE = 'nolink';
71     /**
72      * Default image linking value
73      */
74     public const CONF_DEFAULT_LINKING = "defaultImageLinking";
75 
76     const CANONICAL = "media";
77 
78     /**
79      * This attributes does not apply
80      * to a fetch (URL)
81      * They are only for the tag (img, svg, ...)
82      * or internal
83      */
84     public const STYLE_ATTRIBUTES = [
85         TagAttributes::TITLE_KEY,
86         Hover::ON_HOVER_ATTRIBUTE,
87         Animation::ON_VIEW_ATTRIBUTE,
88         Shadow::SHADOW_ATT,
89         Opacity::OPACITY_ATTRIBUTE,
90         TagAttributes::CLASS_KEY,
91     ];
92 
93     /**
94      * An attribute to set the class of the link if any
95      */
96     public const LINK_CLASS_ATTRIBUTE = "link-class";
97     public static string $MEDIA_QUERY_PARAMETER = "media";
98 
99 
100     private ?string $align = null;
101     private ?string $label = null;
102     private ?MarkupRef $markupRef = null;
103     private ?string $linking = null;
104     private ?string $lazyLoadMethod = null;
105     private TagAttributes $extraMediaTagAttributes;
106     private ?string $linkingClass = null;
107     private IFetcher $fetcher;
108     private Url $fetchUrl;
109 
110     private function __construct()
111     {
112         $this->extraMediaTagAttributes = TagAttributes::createEmpty();
113     }
114 
115 
116     /**
117      * Private method use {@link MediaMarkup::createFromRef()} to create a media markup via a ref
118      *
119      * Set and parse a media wiki ref that you can found in the first part of a media markup
120      *
121      * @param string $markupRef
122      * @return MediaMarkup
123      * @throws ExceptionBadArgument
124      * @throws ExceptionBadSyntax
125      * @throws ExceptionNotFound
126      */
127     private function setMarkupRef(string $markupRef): MediaMarkup
128     {
129 
130         $markupRef = trim($markupRef);
131         $this->markupRef = MarkupRef::createMediaFromRef($markupRef);
132 
133         $refUrl = $this->markupRef->getUrl();
134         $this->setUrl($refUrl);
135 
136         return $this;
137     }
138 
139     /**
140      * @param $callStackArray
141      * @return MediaMarkup
142      * @throws ExceptionBadArgument
143      * @throws ExceptionBadSyntax
144      * @throws ExceptionNotFound
145      * @throws ExceptionNotExists
146      */
147     public static function createFromCallStackArray($callStackArray): MediaMarkup
148     {
149 
150         $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray);
151         $ref = $tagAttributes->getValueAndRemoveIfPresent(MarkupRef::REF_ATTRIBUTE);
152         if ($ref === null) {
153             $ref = $tagAttributes->getValueAndRemoveIfPresent(MediaMarkup::DOKUWIKI_SRC);
154             if ($ref === null) {
155                 throw new ExceptionBadArgument("The media reference was not found in the callstack array", self::CANONICAL);
156             }
157         }
158         return self::createFromRef($ref)
159             ->buildFromTagAttributes($tagAttributes);
160 
161 
162     }
163 
164     /**
165      * @throws ExceptionBadArgument
166      * @throws ExceptionBadSyntax
167      * @throws ExceptionNotExists
168      * @throws ExceptionNotFound
169      * @throws ExceptionInternal
170      */
171     public static function createFromFetchUrl(Url $fetchUrl): MediaMarkup
172     {
173         return (new MediaMarkup())->setUrl($fetchUrl);
174     }
175 
176     public static function createFromFetcher(IFetcher $fetcher): MediaMarkup
177     {
178         return (new MediaMarkup())
179             ->setFetcher($fetcher);
180     }
181 
182     public static function renderSpecial(array $data, Doku_Renderer_xhtml $renderer)
183     {
184 
185         $callStackArray = $data[PluginUtility::ATTRIBUTES];
186         $display = $callStackArray[Display::DISPLAY] ?? null;
187         if ($display === Display::DISPLAY_NONE_VALUE) {
188             /**
189              * Used primarly to not show the featured images
190              * in the outline {@link Outline::toHtmlSectionOutlineCallsRecurse()}
191              * for item page
192              * But we keep the metadata to move them if any
193              */
194             return false;
195         }
196 
197         /** @var Doku_Renderer_xhtml $renderer */
198         try {
199             $mediaMarkup = MediaMarkup::createFromCallStackArray($callStackArray);
200         } catch (ExceptionCompile $e) {
201             return $e->getMessage();
202         }
203 
204 
205         if (
206             $mediaMarkup->getInternalExternalType() === MediaMarkup::INTERNAL_MEDIA_CALL_NAME
207         ) {
208             try {
209                 $isImage = $mediaMarkup->getFetcher()->getMime()->isImage();
210             } catch (\Exception $e) {
211                 $isImage = false;
212             }
213             if ($isImage) {
214                 try {
215                     return MediaLink::createFromMediaMarkup($mediaMarkup)->renderMediaTag();
216                 } catch (ExceptionCompile $e) {
217                     if (PluginUtility::isDevOrTest()) {
218                         throw new ExceptionRuntime("Media Rendering Error. {$e->getMessage()}", MediaLink::CANONICAL, 0, $e);
219                     } else {
220                         $errorClass = syntax_plugin_combo_media::SVG_RENDERING_ERROR_CLASS;
221                         $message = "Media ({$mediaMarkup}). Error while rendering: {$e->getMessage()}";
222                         LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, MediaLink::CANONICAL);
223                         return "<span class=\"text-danger $errorClass\">" . hsc(trim($message)) . "</span>";
224 
225                     }
226                 }
227             }
228 
229         }
230 
231 
232         /**
233          * This is not an local internal media image (a video or an url image)
234          * Dokuwiki takes over
235          */
236         $mediaType = $mediaMarkup->getInternalExternalType();
237         try {
238             $title = $mediaMarkup->getLabel();
239         } catch (ExceptionNotFound $e) {
240             $title = null;
241         }
242         try {
243             $linking = $mediaMarkup->getLinking();
244         } catch (ExceptionNotFound $e) {
245             $linking = null;
246         }
247         try {
248             $align = $mediaMarkup->getAlign();
249         } catch (ExceptionNotFound $e) {
250             $align = null;
251         }
252         try {
253             /**
254              * We use the markup ref url
255              * because we don't support http/https (external) url
256              * And there is therefore no fetcher available
257              */
258             $markupUrl = $mediaMarkup->getMarkupRef()->getUrl();
259 
260         } catch (ExceptionNotFound $e) {
261             // the
262             LogUtility::internalError("As the media markup is created from a markup in the syntax component, it should be available");
263             return "";
264         }
265 
266         try {
267             $src = $mediaMarkup->getSrc();
268         } catch (ExceptionNotFound $e) {
269             LogUtility::internalError("For an external markup, the src should not be empty", self::CANONICAL);
270             return "";
271         }
272         try {
273             $isImage = FileSystems::getMime($markupUrl)->isImage();
274         } catch (ExceptionNotFound $e) {
275             $isImage = false;
276         }
277         if ($isImage) {
278             /**
279              * We need to delete the
280              * wXh and other properties
281              * Dokuwiki does not accept it in its function
282              */
283             try {
284                 $src = Url::createEmpty()
285                     ->setScheme($markupUrl->getScheme())
286                     ->setHost($markupUrl->getHost())
287                     ->setPath($markupUrl->getPath())
288                     ->toString();
289             } catch (ExceptionNotFound $e) {
290 
291             }
292         }
293         try {
294             $width = $markupUrl->getQueryPropertyValue(Dimension::WIDTH_KEY);
295         } catch
296         (ExceptionNotFound $e) {
297             $width = null;
298         }
299         try {
300             $height = $markupUrl->getQueryPropertyValue(Dimension::HEIGHT_KEY);
301         } catch (ExceptionNotFound $e) {
302             $height = null;
303         }
304         try {
305             $cache = $markupUrl->getQueryPropertyValue(IFetcherAbs::CACHE_KEY);
306         } catch (ExceptionNotFound $e) {
307             // Dokuwiki needs a value
308             // If their is no value it will output it without any value
309             // in the query string.
310             $cache = IFetcherAbs::CACHE_DEFAULT_VALUE;
311         }
312         switch ($mediaType) {
313             case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
314                 return $renderer->internalmedia($src, $title, $align, $width, $height, $cache, $linking, true);
315             case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
316                 return $renderer->externalmedia($src, $title, $align, $width, $height, $cache, $linking, true);
317             default:
318                 LogUtility::msg("The dokuwiki media type ($mediaType) is unknown");
319                 return "";
320         }
321     }
322 
323 
324     /**
325      * Compliance: src in dokuwiki is the id and the anchor if any
326      * Dokuwiki does not understand other property and the reference metadata
327      * may not work if we send back the `ref`
328      * @throws ExceptionNotFound
329      */
330     public function getSrc(): string
331     {
332         $internalExternalType = $this->getInternalExternalType();
333         switch ($internalExternalType) {
334             case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
335                 /**
336                  * Absolute id because dokuwiki resolve a relatif id
337                  * to the actual namespace
338                  */
339                 $src = $this->getPath()->toAbsoluteId();
340                 try {
341                     $src = "$src#{$this->markupRef->getUrl()->getFragment()}";
342                 } catch (ExceptionNotFound $e) {
343                     // ok
344                 }
345                 return $src;
346             case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
347                 return $this->getMarkupRef()->getRef();
348             default:
349                 LogUtility::internalError("The internal/external type value ($internalExternalType) is unknown");
350                 return $this->getMarkupRef()->getRef();
351         }
352 
353     }
354 
355     /**
356      * Media Type Needed by Dokuwiki
357      */
358     public function getInternalExternalType(): string
359     {
360         try {
361             // if there is a path, this is internal
362             // if interwiki this, wiki id, ...
363             $this->markupRef->getPath();
364             return self::INTERNAL_MEDIA_CALL_NAME;
365         } catch (ExceptionNotFound $e) {
366             return self::EXTERNAL_MEDIA_CALL_NAME;
367         }
368 
369     }
370 
371 
372     /**
373      * @throws ExceptionBadSyntax
374      * @throws ExceptionBadArgument
375      * @throws ExceptionNotFound
376      */
377     public static function createFromRef(string $markupRef): MediaMarkup
378     {
379         return (new MediaMarkup())->setMarkupRef($markupRef);
380     }
381 
382     /**
383      * Keep track of the metadata
384      * @param array $data
385      * @param Doku_Renderer_metadata $renderer
386      * @return void
387      */
388     public static function metadata(array $data, Doku_Renderer_metadata $renderer)
389     {
390 
391         $tagAttributes = $data[PluginUtility::ATTRIBUTES];
392         if ($tagAttributes === null) {
393             // error on handle
394             return;
395         }
396         syntax_plugin_combo_media::registerImageMeta($tagAttributes, $renderer);
397 
398     }
399 
400     /**
401      * Special pattern call
402      * @param array $data
403      * @param renderer_plugin_combo_analytics $renderer
404      * @return void
405      * @deprecated - for metadata but yeah ...
406      */
407     public static function analytics(array $data, renderer_plugin_combo_analytics $renderer)
408     {
409 
410         $tagAttributes = $data[PluginUtility::ATTRIBUTES];
411         syntax_plugin_combo_media::updateStatistics($tagAttributes, $renderer);
412 
413     }
414 
415 
416     /**
417      * @return Url - an url that has query property as a fetch url
418      * It permits to select the fetch class
419      * @deprecated use {@link MediaMarkup::getFetcher()}->getUrl instead
420      */
421     public function getFetchUrl(): Url
422     {
423         return $this->getFetcher()->getFetchUrl();
424     }
425 
426 
427     /**
428      * @param string $match - the match of the renderer
429      * @throws ExceptionBadSyntax - if no ref was found
430      * @throws ExceptionBadArgument
431      * @throws ExceptionNotFound|ExceptionNotExists
432      * @throws ExceptionInternal
433      */
434     public static function createFromMarkup(string $match): MediaMarkup
435     {
436 
437         $mediaMarkup = new MediaMarkup();
438 
439         /**
440          *   * Delete the opening and closing character
441          *   * create the url and description
442          */
443         $match = preg_replace(array('/^{{/', '/}}$/u'), '', $match);
444         $parts = explode('|', $match, 2);
445 
446         $ref = $parts[0];
447         if ($ref === null) {
448             throw new ExceptionBadSyntax("No ref was found");
449         }
450         $mediaMarkup->setMarkupRef($ref);
451         if (isset($parts[1])) {
452             $mediaMarkup->setLabel($parts[1]);
453         }
454 
455 
456         /**
457          * Media Alignment
458          */
459         $rightAlign = (bool)preg_match('/^ /', $ref);
460         $leftAlign = (bool)preg_match('/ $/', $ref);
461         $align = null;
462         // Logic = what's that ;)...
463         if ($leftAlign & $rightAlign) {
464             $align = 'center';
465         } else if ($rightAlign) {
466             $align = 'right';
467         } else if ($leftAlign) {
468             $align = 'left';
469         }
470         if ($align !== null) {
471             $mediaMarkup->setAlign($align);
472         }
473 
474         return $mediaMarkup;
475 
476 
477     }
478 
479     public function setAlign(string $align): MediaMarkup
480     {
481         $this->align = $align;
482         return $this;
483     }
484 
485     public function setLabel(string $label): MediaMarkup
486     {
487         $this->label = $label;
488         return $this;
489     }
490 
491     /**
492      * just FYI, not used
493      *
494      * Create an image from dokuwiki {@link Internallink internal call media attributes}
495      *
496      * Dokuwiki extracts already the width, height and align property
497      * @param array $callAttributes
498      * @return MediaMarkup
499      */
500     public static function createFromIndexAttributes(array $callAttributes)
501     {
502         $src = $callAttributes[0];
503         $title = $callAttributes[1];
504         $align = $callAttributes[2];
505         $width = $callAttributes[3];
506         $height = $callAttributes[4];
507         $cache = $callAttributes[5];
508         $linking = $callAttributes[6];
509 
510         $ref = "$src?{$width}x$height&$cache";
511         return (new MediaMarkup())
512             ->setMarkupRef($ref)
513             ->setAlign($align)
514             ->setLabel($title)
515             ->setLinking($linking);
516 
517     }
518 
519     /**
520      * A function to set explicitly which array format
521      * is used in the returned data of a {@link SyntaxPlugin::handle()}
522      * (which ultimately is stored in the {@link CallStack)
523      *
524      * This is to make the difference with the {@link MediaLink::createFromIndexAttributes()}
525      * that is indexed by number (ie without property name)
526      *
527      *
528      * Return the array that is used in the {@link CallStack}
529      *
530      * @return array of key string and value
531      */
532     public function toCallStackArray(): array
533     {
534         /**
535          * We store linking as attribute (to make it possible to change the linking by other plugin)
536          * (ie no linking in heading , ...)
537          */
538         $attributes[MediaMarkup::LINKING_KEY] = $this->linking;
539         $attributes[MarkupRef::REF_ATTRIBUTE] = $this->markupRef->getRef();
540         $attributes[Align::ALIGN_ATTRIBUTE] = $this->align;
541         $attributes[TagAttributes::TITLE_KEY] = $this->label;
542         return $attributes;
543 
544     }
545 
546     public function setLinking(string $linking): MediaMarkup
547     {
548         $this->linking = $linking;
549         return $this;
550     }
551 
552     /**
553      * @throws ExceptionNotFound
554      */
555     public function getLinking(): string
556     {
557         /**
558          * Linking
559          */
560         $linking = $this->linking;
561         if ($linking !== null) {
562             return $linking;
563         }
564         throw new ExceptionNotFound("No linking set");
565 
566 
567     }
568 
569     /**
570      * Align on the url has precedence
571      * if present
572      * @throws ExceptionNotFound
573      */
574     public function getAlign(): string
575     {
576 
577         if ($this->align !== null) {
578             return $this->align;
579         }
580         throw new ExceptionNotFound("No align was specified");
581     }
582 
583 
584     public function toTagAttributes()
585     {
586 
587 
588         /**
589          * The align attribute on an image parse
590          * is a float right
591          * ComboStrap does a difference between a block right and a float right
592          */
593         try {
594             $align = $this->getAlign();
595             if ($align === "right") {
596                 $this->extraMediaTagAttributes->addComponentAttributeValue(FloatAttribute::FLOAT_KEY, "right");
597             } else {
598                 $this->extraMediaTagAttributes->addComponentAttributeValue(Align::ALIGN_ATTRIBUTE, $align);
599             }
600         } catch (ExceptionNotFound $e) {
601             // ok
602         }
603 
604         return $this->extraMediaTagAttributes;
605 
606     }
607 
608     /**
609      * @throws ExceptionNotFound
610      */
611     public function getLabel(): string
612     {
613         if (empty($this->label)) {
614             throw new ExceptionNotFound("No label specified");
615         }
616         return $this->label;
617     }
618 
619     public
620     function setLazyLoadMethod($false): MediaMarkup
621     {
622         $this->lazyLoadMethod = $false;
623         return $this;
624     }
625 
626     /**
627      * @throws ExceptionNotFound
628      */
629     public
630     function getLazyLoadMethod(): string
631     {
632 
633         if ($this->lazyLoadMethod !== null) {
634             return $this->lazyLoadMethod;
635         }
636         throw new ExceptionNotFound("Lazy method is not specified");
637 
638     }
639 
640     public
641     function getLazyLoadMethodOrDefault(): string
642     {
643         try {
644             return $this->getLazyLoadMethod();
645         } catch (ExceptionNotFound $e) {
646             return LazyLoad::getDefault();
647         }
648 
649     }
650 
651 
652     public
653     static function isInternalMediaSyntax($text)
654     {
655         return preg_match(' / ' . syntax_plugin_combo_media::MEDIA_PATTERN . ' / msSi', $text);
656     }
657 
658 
659     public function isLazy(): bool
660     {
661 
662         return $this->getLazyLoadMethodOrDefault() !== LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE;
663 
664     }
665 
666     public function getExtraMediaTagAttributes(): TagAttributes
667     {
668         try {
669             $this->extraMediaTagAttributes->addComponentAttributeValue(Align::ALIGN_ATTRIBUTE, $this->getAlign());
670         } catch (ExceptionNotFound $e) {
671             // ok
672         }
673         return $this->extraMediaTagAttributes;
674     }
675 
676 
677     public function __toString()
678     {
679         return $this->toMarkupSyntax();
680     }
681 
682     public function setLazyLoad(bool $true): MediaMarkup
683     {
684         if ($true) {
685             $this->lazyLoadMethod = LazyLoad::getDefault();
686         } else {
687             $this->lazyLoadMethod = LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE;
688         }
689         return $this;
690     }
691 
692     /**
693      * Get and delete the attribute for the link
694      * (The rest is for the image)
695      */
696     public
697     function getLinkingClass()
698     {
699         return $this->linkingClass;
700     }
701 
702     /**
703      * @throws ExceptionNotFound
704      */
705     public function getMarkupRef(): MarkupRef
706     {
707         if ($this->markupRef === null) {
708             throw new ExceptionNotFound("No markup, this media markup was not created from a markup");
709         }
710         return $this->markupRef;
711     }
712 
713 
714     /**
715      * @param TagAttributes $tagAttributes - the attributes in a tag format
716      * @return $this
717      */
718     public function buildFromTagAttributes(TagAttributes $tagAttributes): MediaMarkup
719     {
720 
721         $linking = $tagAttributes->getValueAndRemoveIfPresent(self::LINKING_KEY);
722         if ($linking !== null) {
723             $this->setLinking($linking);
724         }
725         $label = $tagAttributes->getValueAndRemoveIfPresent(TagAttributes::TITLE_KEY);
726         if ($label !== null) {
727             $this->setLabel($label);
728         }
729         $align = $tagAttributes->getValueAndRemoveIfPresent(Align::ALIGN_ATTRIBUTE);
730         if ($align !== null) {
731             $this->setAlign($align);
732         }
733         $lazy = $tagAttributes->getValueAndRemoveIfPresent(LazyLoad::LAZY_LOAD_METHOD);
734         if ($lazy !== null) {
735             $this->setLazyLoadMethod($lazy);
736         }
737 
738         /**
739          * dokuwiki attribute
740          */
741         if (isset($this->fetchUrl)) {
742             $width = $tagAttributes->getValueAndRemoveIfPresent(Dimension::WIDTH_KEY);
743             if ($width !== null) {
744                 $this->fetchUrl->addQueryParameterIfNotPresent(Dimension::WIDTH_KEY, $width);
745             }
746             $height = $tagAttributes->getValueAndRemoveIfPresent(Dimension::HEIGHT_KEY);
747             if ($height !== null) {
748                 $this->fetchUrl->addQueryParameterIfNotPresent(Dimension::HEIGHT_KEY, $height);
749             }
750             $ratio = $tagAttributes->getValueAndRemoveIfPresent(Dimension::RATIO_ATTRIBUTE);
751             if ($ratio !== null) {
752                 $this->fetchUrl->addQueryParameterIfNotPresent(Dimension::RATIO_ATTRIBUTE, $ratio);
753             }
754         }
755 
756         foreach ($tagAttributes->getComponentAttributes() as $key => $value) {
757             $this->extraMediaTagAttributes->addComponentAttributeValue($key, $value);
758         }
759 
760         foreach ($tagAttributes->getStyleDeclarations() as $key => $value) {
761             $this->extraMediaTagAttributes->addStyleDeclarationIfNotSet($key, $value);
762         }
763 
764         return $this;
765     }
766 
767     /**
768      * @throws ExceptionBadArgument
769      * @throws ExceptionBadSyntax
770      * @throws ExceptionNotExists
771      * @throws ExceptionNotFound
772      */
773     public function toHtml(): string
774     {
775         return MediaLink::createFromMediaMarkup($this)
776             ->renderMediaTag();
777     }
778 
779     /**
780      * @throws ExceptionBadSyntax
781      * @throws ExceptionBadArgument
782      * @throws ExceptionNotExists
783      * @throws ExceptionNotFound
784      */
785     public function getMediaLink()
786     {
787         return MediaLink::createFromMediaMarkup($this);
788     }
789 
790     private
791     function setLinkingClass($value): MediaMarkup
792     {
793         $this->linkingClass = $value;
794         return $this;
795     }
796 
797     /**
798      * @return string the wiki syntax
799      */
800     public function toMarkupSyntax(): string
801     {
802         $descriptionPart = "";
803         try {
804             $descriptionPart = "|" . $this->getLabel();
805         } catch (ExceptionNotFound $e) {
806             // ok
807         }
808         try {
809             $ref = $this->getRef();
810         } catch (ExceptionNotFound $e) {
811             $ref = $this->getFetchUrl()->toString();
812         }
813         return '{{' . $ref . $descriptionPart . '}}';
814     }
815 
816     /**
817      *
818      * Private method use {@link MediaMarkup::createFromFetchUrl()} to create a media markup via a Url
819      *
820      * @throws ExceptionNotFound
821      */
822     private function setUrl(Url $fetchUrl): MediaMarkup
823     {
824 
825         /**
826          * Tag Attributes
827          */
828         try {
829             $this->align = $fetchUrl->getQueryPropertyValueAndRemoveIfPresent(Align::ALIGN_ATTRIBUTE);
830         } catch (ExceptionNotFound $e) {
831             // ok
832         }
833         try {
834             $this->linking = $fetchUrl->getQueryPropertyValueAndRemoveIfPresent(self::LINKING_KEY);
835         } catch (ExceptionNotFound $e) {
836             // ok
837         }
838         try {
839             $this->lazyLoadMethod = $fetchUrl->getQueryPropertyValueAndRemoveIfPresent(LazyLoad::LAZY_LOAD_METHOD);
840         } catch (ExceptionNotFound $e) {
841             // ok
842         }
843         try {
844             $this->linkingClass = $fetchUrl->getQueryPropertyValueAndRemoveIfPresent(self::LINK_CLASS_ATTRIBUTE);
845         } catch (ExceptionNotFound $e) {
846             // ok
847         }
848 
849         foreach (self::STYLE_ATTRIBUTES as $nonUrlAttribute) {
850             try {
851                 $value = $fetchUrl->getQueryPropertyValueAndRemoveIfPresent($nonUrlAttribute);
852                 $this->extraMediaTagAttributes->addComponentAttributeValue($nonUrlAttribute, $value);
853             } catch (ExceptionNotFound $e) {
854                 // ok
855             }
856         }
857 
858         $this->fetchUrl = $fetchUrl;
859         return $this;
860     }
861 
862     /**
863      * @throws ExceptionNotFound
864      */
865     private function getRef(): string
866     {
867         if ($this->markupRef === null) {
868             throw new ExceptionNotFound("No ref was specified");
869         }
870         return $this->markupRef->getRef();
871     }
872 
873     /**
874      * @throws ExceptionNotFound - if this markup does not have a path origin
875      * @deprecated use the {@link self::getFetcher()} instead
876      * A media may be generated (ie {@link FetcherVignette}
877      * therefore the path may be not present
878      *
879      * If you want to known the mime use {@link self::getFetcher()} then {@link IFetcher::getMime()}
880      */
881     public function getPath(): WikiPath
882     {
883         try {
884 
885             return $this->getMarkupRef()->getPath();
886 
887         } catch (ExceptionNotFound $e) {
888 
889             if ($this->fetcher instanceof IFetcherSource) {
890                 return $this->fetcher->getSourcePath();
891             }
892             throw $e;
893 
894         }
895     }
896 
897     /**
898      * Private method use {@link MediaMarkup::createFromFetcher()} to create a media markup via a Fetcher
899      * @param IFetcher $fetcher
900      * @return MediaMarkup
901      */
902     private function setFetcher(IFetcher $fetcher): MediaMarkup
903     {
904         $this->fetcher = $fetcher;
905         return $this;
906     }
907 
908 
909     /**
910      * @return IFetcher
911      */
912     public function getFetcher(): IFetcher
913     {
914         if (!isset($this->fetcher)) {
915             if (!isset($this->fetchUrl)) {
916                 throw new ExceptionRuntimeInternal("No fetcher or url was set");
917             }
918             /**
919              * Fetcher is build later
920              * because for a raster image
921              * actually, we can't built it
922              * if the file does not exists.
923              * It will throw an error immediatly and we may want not.
924              * For resources, we want to build the url even if the image does not exists.
925              */
926             try {
927                 $this->fetcher = FetcherSystem::createPathFetcherFromUrl($this->fetchUrl);
928             } catch (ExceptionBadArgument|ExceptionInternal|ExceptionNotFound $e) {
929                 try {
930                     // we don't support http fetch
931                     if (!($this->getMarkupRef()->getSchemeType() === MarkupRef::WEB_URI)) {
932                         throw ExceptionRuntimeInternal::withMessageAndError("we don't support http fetch", $e);
933                     }
934                 } catch (ExceptionNotFound $e) {
935                     // ok no markup ref
936                 }
937             }
938         }
939         return $this->fetcher;
940     }
941 
942 
943 }
944