1<?php
2
3
4use ComboStrap\CallStack;
5use ComboStrap\CardTag;
6use ComboStrap\Dimension;
7use ComboStrap\Display;
8use ComboStrap\ExceptionBadArgument;
9use ComboStrap\ExceptionBadSyntax;
10use ComboStrap\ExceptionCompile;
11use ComboStrap\ExceptionNotExists;
12use ComboStrap\ExceptionNotFound;
13use ComboStrap\ExceptionRuntime;
14use ComboStrap\FetcherSvg;
15use ComboStrap\FileSystems;
16use ComboStrap\FirstRasterImage;
17use ComboStrap\FirstSvgIllustration;
18use ComboStrap\FeaturedIcon;
19use ComboStrap\IFetcherAbs;
20use ComboStrap\LogUtility;
21use ComboStrap\MarkupRef;
22use ComboStrap\MediaLink;
23use ComboStrap\MediaMarkup;
24use ComboStrap\Meta\Api\Metadata;
25use ComboStrap\Mime;
26use ComboStrap\Path;
27use ComboStrap\PluginUtility;
28use ComboStrap\TagAttributes;
29use ComboStrap\ThirdPartyPlugins;
30use ComboStrap\WikiPath;
31
32
33require_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
34
35
36/**
37 * Media
38 *
39 * Takes over the {@link \dokuwiki\Parsing\ParserMode\Media media mode}
40 * that is processed by {@link Doku_Handler_Parse_Media}
41 *
42 *
43 *
44 * It can be a internal / external media
45 *
46 *
47 * See:
48 * https://developers.google.com/search/docs/advanced/guidelines/google-images
49 */
50class syntax_plugin_combo_media extends DokuWiki_Syntax_Plugin
51{
52
53
54    const TAG = "media";
55
56    /**
57     * Used in the move plugin
58     * !!! The two last word of the plugin class !!!
59     */
60    const COMPONENT = 'combo_' . self::TAG;
61
62
63    /**
64     * Found at {@link \dokuwiki\Parsing\ParserMode\Media}
65     */
66    const MEDIA_PATTERN = "\{\{(?:[^>\}]|(?:\}[^\}]))+\}\}";
67
68    /**
69     * Enable or disable the image
70     */
71    const CONF_IMAGE_ENABLE = "imageEnable";
72
73    /**
74     * Svg Rendering error
75     */
76    const SVG_RENDERING_ERROR_CLASS = "combo-svg-rendering-error";
77    const CANONICAL = "media";
78
79
80    public static function registerFirstImage(Doku_Renderer_metadata $renderer, Path $path)
81    {
82        /**
83         * {@link Doku_Renderer_metadata::$firstimage} is unfortunately protected
84         * and {@link Doku_Renderer_metadata::internalmedia()} does not allow svg as first image
85         */
86        if (!($path instanceof WikiPath)) {
87            return;
88        }
89        if (!FileSystems::exists($path)) {
90            return;
91        }
92        /**
93         * Image Id check
94         */
95        $wikiId = $path->getWikiId();
96        if (media_isexternal($wikiId)) {
97            // The first image is not a local image
98            // Don't set
99            return;
100        }
101        try {
102            $mime = $path->getMime();
103        } catch (ExceptionNotFound $e) {
104            LogUtility::internalError("The mime for the path ($path) was not found", self::CANONICAL, $e);
105            return;
106        }
107        if (!isset($renderer->meta[FirstRasterImage::PROPERTY_NAME])) {
108            if ($mime->isSupportedRasterImage()) {
109                $renderer->meta[FirstRasterImage::PROPERTY_NAME] = $wikiId;
110                return;
111            }
112        }
113        if (!isset($renderer->meta[FirstSvgIllustration::PROPERTY_NAME]) || !isset($renderer->meta[FeaturedIcon::FIRST_ICON_PARSED])) {
114            if ($mime->toString() === Mime::SVG) {
115                try {
116                    $isIcon = FetcherSvg::createSvgFromPath(WikiPath::createMediaPathFromId($wikiId))
117                        ->isIconStructure();
118                } catch (Exception $e) {
119                    return;
120                }
121                if (!$isIcon) {
122                    $renderer->meta[FirstSvgIllustration::PROPERTY_NAME] = $wikiId;
123                } else {
124                    $renderer->meta[FeaturedIcon::FIRST_ICON_PARSED] = $wikiId;
125                }
126            }
127        }
128
129    }
130
131
132    /**
133     * @param $attributes
134     * @param renderer_plugin_combo_analytics $renderer
135     */
136    public static function updateStatistics($attributes, renderer_plugin_combo_analytics $renderer)
137    {
138        $markupUrlString = $attributes[MarkupRef::REF_ATTRIBUTE];
139        $actualMediaCount = $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] ?? 0;
140        $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] = $actualMediaCount + 1;
141        try {
142            $markupUrl = MediaMarkup::createFromRef($markupUrlString);
143        } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
144            LogUtility::error("media update statistics: cannot create the media markup", "media", $e);
145            return;
146        }
147        switch ($markupUrl->getInternalExternalType()) {
148            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
149                $actualInternalMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] ?? 0;
150                $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] = $actualInternalMediaCount + 1;
151                try {
152                    $path = $markupUrl->getPath();
153                } catch (ExceptionNotFound $e) {
154                    LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG);
155                    return;
156                }
157                if (!FileSystems::exists($path)) {
158                    $brokenMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] ?? 0;
159                    $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] = $brokenMediaCount + 1;
160                }
161                break;
162            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
163                $mediaCount = $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] ?? 0;
164                $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] = $mediaCount + 1;
165                break;
166        }
167    }
168
169
170    function getType(): string
171    {
172        return 'formatting';
173    }
174
175    /**
176     * How Dokuwiki will add P element
177     *
178     *  * 'normal' - The plugin can be used inside paragraphs (inline)
179     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
180     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
181     *
182     * @see DokuWiki_Syntax_Plugin::getPType()
183     */
184    function getPType(): string
185    {
186        /**
187         * An image is not a block (it can be inside paragraph)
188         */
189        return 'normal';
190    }
191
192    function getAllowedTypes(): array
193    {
194        return array('substition', 'formatting', 'disabled');
195    }
196
197    /**
198     * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()}
199     * (It was 320 at the time of writing this code)
200     * @return int
201     *
202     */
203    function getSort(): int
204    {
205        return 319;
206    }
207
208
209    function connectTo($mode)
210    {
211        $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1);
212        if (!$enable) {
213
214            // Inside a card, we need to take over and enable it
215            $modes = [
216                PluginUtility::getModeFromTag(CardTag::CARD_TAG),
217            ];
218            $enable = in_array($mode, $modes);
219        }
220
221        if ($enable) {
222            if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) {
223                $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
224            }
225        }
226    }
227
228
229    function handle($match, $state, $pos, Doku_Handler $handler): array
230    {
231
232        // As this is a container, this cannot happens but yeah, now, you know
233        if ($state == DOKU_LEXER_SPECIAL) {
234
235            try {
236                $mediaMarkup = MediaMarkup::createFromMarkup($match);
237            } catch (ExceptionCompile $e) {
238                $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}";
239                // to get the trace on test run
240                LogUtility::error($message, self::TAG, $e);
241                return [];
242            }
243
244            /**
245             * Parent
246             */
247            $callStack = CallStack::createFromHandler($handler);
248            $parent = $callStack->moveToParent();
249            $parentTag = "";
250            if (!empty($parent)) {
251                $parentTag = $parent->getTagName();
252                if (in_array($parentTag,
253                    [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) {
254                    /**
255                     * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link}
256                     *   / {@link syntax_plugin_combo_brand::handle()} brand
257                     *   - The image is in a link, we don't want another link to the image
258                     *   - In a brand, there is also already a link to the home page, no link to the media
259                     */
260                    $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE);
261                }
262            }
263
264            $callStackArray = $mediaMarkup->toCallStackArray();
265            return array(
266                PluginUtility::STATE => $state,
267                PluginUtility::ATTRIBUTES => $callStackArray,
268                PluginUtility::CONTEXT => $parentTag,
269                PluginUtility::TAG => MediaMarkup::TAG
270            );
271        }
272        return array();
273
274    }
275
276    /**
277     * Render the output
278     * @param string $format
279     * @param Doku_Renderer $renderer
280     * @param array $data - what the function handle() return'ed
281     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
282     * @see DokuWiki_Syntax_Plugin::render()
283     *
284     */
285    function render($format, Doku_Renderer $renderer, $data): bool
286    {
287
288        switch ($format) {
289
290            case 'xhtml':
291                /**
292                 * @var Doku_Renderer_xhtml $renderer
293                 */
294                $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer);
295                return true;
296
297            case "metadata":
298
299                /**
300                 * @var Doku_Renderer_metadata $renderer
301                 */
302                MediaMarkup::metadata($data, $renderer);
303                return true;
304
305            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
306
307                /**
308                 * @var renderer_plugin_combo_analytics $renderer
309                 */
310                MediaMarkup::analytics($data, $renderer);
311                return true;
312
313        }
314        // unsupported $mode
315        return false;
316    }
317
318    /**
319     * Update the index for the move plugin
320     * and {@link Metadata::FIRST_IMAGE_META_RELATION}
321     * @param array $attributes
322     * @param Doku_Renderer_metadata $renderer
323     */
324    static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer)
325    {
326        try {
327            $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes);
328        } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) {
329            LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}");
330            return;
331        } catch (ExceptionNotExists $e) {
332            return;
333        }
334        try {
335            $label = $mediaMarkup->getLabel();
336        } catch (ExceptionNotFound $e) {
337            $label = "";
338        }
339        $internalExternalType = $mediaMarkup->getInternalExternalType();
340        try {
341            $src = $mediaMarkup->getSrc();
342        } catch (ExceptionNotFound $e) {
343            LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e);
344            return;
345        }
346        switch ($internalExternalType) {
347            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
348                try {
349                    $path = $mediaMarkup->getMarkupRef()->getPath();
350                } catch (ExceptionNotFound $e) {
351                    LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG);
352                    return;
353                }
354                self::registerFirstImage($renderer, $path);
355                $renderer->internalmedia($src, $label);
356                break;
357            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
358                $renderer->externalmedia($src, $label);
359                break;
360            default:
361                LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown");
362                break;
363        }
364
365    }
366
367
368}
369
370