xref: /plugin/combo/syntax/media.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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        $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT]++;
140        try {
141            $markupUrl = MediaMarkup::createFromRef($markupUrlString);
142        } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
143            LogUtility::error("media update statistics: cannot create the media markup", "media", $e);
144            return;
145        }
146        switch ($markupUrl->getInternalExternalType()) {
147            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
148                $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT]++;
149                try {
150                    $path = $markupUrl->getPath();
151                } catch (ExceptionNotFound $e) {
152                    LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG);
153                    return;
154                }
155                if (!FileSystems::exists($path)) {
156                    $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT]++;
157                }
158                break;
159            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
160                $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT]++;
161                break;
162        }
163    }
164
165
166    function getType(): string
167    {
168        return 'formatting';
169    }
170
171    /**
172     * How Dokuwiki will add P element
173     *
174     *  * 'normal' - The plugin can be used inside paragraphs (inline)
175     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
176     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
177     *
178     * @see DokuWiki_Syntax_Plugin::getPType()
179     */
180    function getPType(): string
181    {
182        /**
183         * An image is not a block (it can be inside paragraph)
184         */
185        return 'normal';
186    }
187
188    function getAllowedTypes(): array
189    {
190        return array('substition', 'formatting', 'disabled');
191    }
192
193    /**
194     * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()}
195     * (It was 320 at the time of writing this code)
196     * @return int
197     *
198     */
199    function getSort(): int
200    {
201        return 319;
202    }
203
204
205    function connectTo($mode)
206    {
207        $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1);
208        if (!$enable) {
209
210            // Inside a card, we need to take over and enable it
211            $modes = [
212                PluginUtility::getModeFromTag(CardTag::CARD_TAG),
213            ];
214            $enable = in_array($mode, $modes);
215        }
216
217        if ($enable) {
218            if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) {
219                $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
220            }
221        }
222    }
223
224
225    function handle($match, $state, $pos, Doku_Handler $handler): array
226    {
227
228        // As this is a container, this cannot happens but yeah, now, you know
229        if ($state == DOKU_LEXER_SPECIAL) {
230
231            try {
232                $mediaMarkup = MediaMarkup::createFromMarkup($match);
233            } catch (ExceptionCompile $e) {
234                $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}";
235                // to get the trace on test run
236                LogUtility::error($message, self::TAG, $e);
237                return [];
238            }
239
240            /**
241             * Parent
242             */
243            $callStack = CallStack::createFromHandler($handler);
244            $parent = $callStack->moveToParent();
245            $parentTag = "";
246            if (!empty($parent)) {
247                $parentTag = $parent->getTagName();
248                if (in_array($parentTag,
249                    [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) {
250                    /**
251                     * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link}
252                     *   / {@link syntax_plugin_combo_brand::handle()} brand
253                     *   - The image is in a link, we don't want another link to the image
254                     *   - In a brand, there is also already a link to the home page, no link to the media
255                     */
256                    $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE);
257                }
258            }
259
260            $callStackArray = $mediaMarkup->toCallStackArray();
261            return array(
262                PluginUtility::STATE => $state,
263                PluginUtility::ATTRIBUTES => $callStackArray,
264                PluginUtility::CONTEXT => $parentTag,
265                PluginUtility::TAG => MediaMarkup::TAG
266            );
267        }
268        return array();
269
270    }
271
272    /**
273     * Render the output
274     * @param string $format
275     * @param Doku_Renderer $renderer
276     * @param array $data - what the function handle() return'ed
277     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
278     * @see DokuWiki_Syntax_Plugin::render()
279     *
280     */
281    function render($format, Doku_Renderer $renderer, $data): bool
282    {
283
284        switch ($format) {
285
286            case 'xhtml':
287                /**
288                 * @var Doku_Renderer_xhtml $renderer
289                 */
290                $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer);
291                return true;
292
293            case "metadata":
294
295                /**
296                 * @var Doku_Renderer_metadata $renderer
297                 */
298                MediaMarkup::metadata($data, $renderer);
299                return true;
300
301            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
302
303                /**
304                 * @var renderer_plugin_combo_analytics $renderer
305                 */
306                MediaMarkup::analytics($data, $renderer);
307                return true;
308
309        }
310        // unsupported $mode
311        return false;
312    }
313
314    /**
315     * Update the index for the move plugin
316     * and {@link Metadata::FIRST_IMAGE_META_RELATION}
317     * @param array $attributes
318     * @param Doku_Renderer_metadata $renderer
319     */
320    static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer)
321    {
322        try {
323            $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes);
324        } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) {
325            LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}");
326            return;
327        } catch (ExceptionNotExists $e) {
328            return;
329        }
330        try {
331            $label = $mediaMarkup->getLabel();
332        } catch (ExceptionNotFound $e) {
333            $label = "";
334        }
335        $internalExternalType = $mediaMarkup->getInternalExternalType();
336        try {
337            $src = $mediaMarkup->getSrc();
338        } catch (ExceptionNotFound $e) {
339            LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e);
340            return;
341        }
342        switch ($internalExternalType) {
343            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
344                try {
345                    $path = $mediaMarkup->getMarkupRef()->getPath();
346                } catch (ExceptionNotFound $e) {
347                    LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG);
348                    return;
349                }
350                self::registerFirstImage($renderer, $path);
351                $renderer->internalmedia($src, $label);
352                break;
353            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
354                $renderer->externalmedia($src, $label);
355                break;
356            default:
357                LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown");
358                break;
359        }
360
361    }
362
363
364}
365
366