1<?php
2
3
4use ComboStrap\AnalyticsDocument;
5use ComboStrap\CallStack;
6use ComboStrap\DokuFs;
7use ComboStrap\ExceptionComboRuntime;
8use ComboStrap\InternetPath;
9use ComboStrap\LogUtility;
10use ComboStrap\MediaLink;
11use ComboStrap\Metadata;
12use ComboStrap\PageImages;
13use ComboStrap\PagePath;
14use ComboStrap\PluginUtility;
15use ComboStrap\SvgDocument;
16use ComboStrap\TagAttributes;
17use ComboStrap\ThirdPartyPlugins;
18
19
20require_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
21
22
23/**
24 * Media
25 *
26 * Takes over the {@link \dokuwiki\Parsing\ParserMode\Media media mode}
27 * that is processed by {@link Doku_Handler_Parse_Media}
28 *
29 *
30 *
31 * It can be a internal / external media
32 *
33 *
34 * See:
35 * https://developers.google.com/search/docs/advanced/guidelines/google-images
36 */
37class syntax_plugin_combo_media extends DokuWiki_Syntax_Plugin
38{
39
40
41    const TAG = "media";
42
43    /**
44     * Used in the move plugin
45     * !!! The two last word of the plugin class !!!
46     */
47    const COMPONENT = 'combo_' . self::TAG;
48
49
50    /**
51     * Found at {@link \dokuwiki\Parsing\ParserMode\Media}
52     */
53    const MEDIA_PATTERN = "\{\{(?:[^>\}]|(?:\}[^\}]))+\}\}";
54
55    /**
56     * Enable or disable the image
57     */
58    const CONF_IMAGE_ENABLE = "imageEnable";
59
60    /**
61     * Svg Rendering error
62     */
63    const SVG_RENDERING_ERROR_CLASS = "combo-svg-rendering-error";
64
65    /**
66     * An attribute to set the class of the link if any
67     */
68    const LINK_CLASS_ATTRIBUTE = "link-class";
69
70    public static function registerFirstMedia(Doku_Renderer_metadata $renderer, $src)
71    {
72        /**
73         * {@link Doku_Renderer_metadata::$firstimage} is unfortunately protected
74         * and {@link Doku_Renderer_metadata::internalmedia()} does not allow svg as first image
75         */
76        if (!isset($renderer->meta[PageImages::FIRST_IMAGE_META_RELATION])) {
77            $renderer->meta[PageImages::FIRST_IMAGE_META_RELATION] = $src;
78        }
79
80    }
81
82
83    /**
84     * @param $attributes
85     * @param renderer_plugin_combo_analytics $renderer
86     */
87    public static function updateStatistics($attributes, renderer_plugin_combo_analytics $renderer)
88    {
89        $media = MediaLink::createFromCallStackArray($attributes);
90        $renderer->stats[AnalyticsDocument::MEDIA_COUNT]++;
91        $scheme = $media->getMedia()->getPath()->getScheme();
92        switch ($scheme) {
93            case DokuFs::SCHEME:
94                $renderer->stats[AnalyticsDocument::INTERNAL_MEDIA_COUNT]++;
95                if (!$media->getMedia()->exists()) {
96                    $renderer->stats[AnalyticsDocument::INTERNAL_BROKEN_MEDIA_COUNT]++;
97                }
98                break;
99            case InternetPath::scheme:
100                $renderer->stats[AnalyticsDocument::EXTERNAL_MEDIA_COUNT]++;
101                break;
102        }
103    }
104
105
106    function getType(): string
107    {
108        return 'formatting';
109    }
110
111    /**
112     * How Dokuwiki will add P element
113     *
114     *  * 'normal' - The plugin can be used inside paragraphs (inline)
115     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
116     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
117     *
118     * @see DokuWiki_Syntax_Plugin::getPType()
119     */
120    function getPType(): string
121    {
122        /**
123         * An image is not a block (it can be inside paragraph)
124         */
125        return 'normal';
126    }
127
128    function getAllowedTypes(): array
129    {
130        return array('substition', 'formatting', 'disabled');
131    }
132
133    /**
134     * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()}
135     * (It was 320 at the time of writing this code)
136     * @return int
137     *
138     */
139    function getSort(): int
140    {
141        return 319;
142    }
143
144
145    function connectTo($mode)
146    {
147        $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1);
148        if (!$enable) {
149
150            // Inside a card, we need to take over and enable it
151            $modes = [
152                PluginUtility::getModeFromTag(syntax_plugin_combo_card::TAG),
153            ];
154            $enable = in_array($mode, $modes);
155        }
156
157        if ($enable) {
158            if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) {
159                $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
160            }
161        }
162    }
163
164
165    function handle($match, $state, $pos, Doku_Handler $handler): array
166    {
167
168        switch ($state) {
169
170
171            // As this is a container, this cannot happens but yeah, now, you know
172            case DOKU_LEXER_SPECIAL :
173
174                /**
175                 * Note: The type of image for a svg (icon/illustration) is dedicated
176                 * by its structure or is expressly set on type
177                 */
178                $media = MediaLink::createFromRenderMatch($match);
179                $attributes = $media->toCallStackArray();
180
181                $callStack = CallStack::createFromHandler($handler);
182
183                /**
184                 * Parent
185                 */
186                $parent = $callStack->moveToParent();
187                $parentTag = "";
188                if (!empty($parent)) {
189                    $parentTag = $parent->getTagName();
190                    if (in_array($parentTag,
191                        [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) {
192                        /**
193                         * TODO: should be on the exit tag of the link / brand
194                         *   - The image is in a link, we don't want another link to the image
195                         *   - In a brand, there is also already a link to the home page, no link to the media
196                         */
197                        $attributes[MediaLink::LINKING_KEY] = MediaLink::LINKING_NOLINK_VALUE;
198                    }
199                }
200
201                return array(
202                    PluginUtility::STATE => $state,
203                    PluginUtility::ATTRIBUTES => $attributes,
204                    PluginUtility::CONTEXT => $parentTag
205                );
206
207
208        }
209        return array();
210
211    }
212
213    /**
214     * Render the output
215     * @param string $format
216     * @param Doku_Renderer $renderer
217     * @param array $data - what the function handle() return'ed
218     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
219     * @see DokuWiki_Syntax_Plugin::render()
220     *
221     *
222     */
223    function render($format, Doku_Renderer $renderer, $data): bool
224    {
225
226        switch ($format) {
227
228            case 'xhtml':
229
230                /** @var Doku_Renderer_xhtml $renderer */
231                $attributes = $data[PluginUtility::ATTRIBUTES];
232                $mediaLink = MediaLink::createFromCallStackArray($attributes, $renderer->date_at);
233                $media = $mediaLink->getMedia();
234                if ($media->getPath()->getScheme() == DokuFs::SCHEME) {
235                    if ($media->getPath()->getMime()->isImage() || $media->getPath()->getExtension() === "svg") {
236                        try {
237                            $renderer->doc .= $mediaLink->renderMediaTagWithLink();
238                        } catch (RuntimeException $e) {
239                            if (PluginUtility::isDevOrTest()) {
240                                throw new ExceptionComboRuntime("Media Rendering Error. {$e->getMessage()}", MediaLink::CANONICAL, 0, $e);
241                            } else {
242                                $errorClass = self::SVG_RENDERING_ERROR_CLASS;
243                                $message = "Media ({$media->getPath()}). Error while rendering: {$e->getMessage()}";
244                                $renderer->doc .= "<span class=\"text-alert $errorClass\">" . hsc(trim($message)) . "</span>";
245                                LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, MediaLink::CANONICAL);
246                            }
247                        }
248                        return true;
249                    }
250                }
251
252                /**
253                 * This is not an local internal media image (a video or an url image)
254                 * Dokuwiki takes over
255                 */
256                $type = $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE];
257                $src = $attributes['src'];
258                $title = $attributes['title'];
259                $align = $attributes['align'];
260                $width = $attributes['width'];
261                $height = $attributes['height'];
262                $cache = $attributes['cache'];
263                if ($cache == null) {
264                    // Dokuwiki needs a value
265                    // If their is no value it will output it without any value
266                    // in the query string.
267                    $cache = "cache";
268                }
269                $linking = $attributes['linking'];
270                switch ($type) {
271                    case MediaLink::INTERNAL_MEDIA_CALL_NAME:
272                        $renderer->doc .= $renderer->internalmedia($src, $title, $align, $width, $height, $cache, $linking, true);
273                        break;
274                    case MediaLink::EXTERNAL_MEDIA_CALL_NAME:
275                        $renderer->doc .= $renderer->externalmedia($src, $title, $align, $width, $height, $cache, $linking, true);
276                        break;
277                    default:
278                        LogUtility::msg("The dokuwiki media type ($type) is unknown");
279                        break;
280                }
281
282                return true;
283
284            case
285            "metadata":
286
287                /**
288                 * Keep track of the metadata
289                 * @var Doku_Renderer_metadata $renderer
290                 */
291                $attributes = $data[PluginUtility::ATTRIBUTES];
292                self::registerImageMeta($attributes, $renderer);
293                return true;
294
295            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
296
297                /**
298                 * Special pattern call
299                 * @var renderer_plugin_combo_analytics $renderer
300                 */
301                $attributes = $data[PluginUtility::ATTRIBUTES];
302                self::updateStatistics($attributes, $renderer);
303                return true;
304
305        }
306        // unsupported $mode
307        return false;
308    }
309
310    /**
311     * Update the index for the move plugin
312     * and {@link Metadata::FIRST_IMAGE_META_RELATION}
313     *
314     * @param array $attributes
315     * @param Doku_Renderer_metadata $renderer
316     */
317    static public function registerImageMeta($attributes, $renderer)
318    {
319        $type = $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE];
320        $src = $attributes['src'];
321        if ($src == null) {
322            $src = $attributes[PagePath::PROPERTY_NAME];
323        }
324        $title = $attributes['title'];
325        $align = $attributes['align'];
326        $width = $attributes['width'];
327        $height = $attributes['height'];
328        $cache = $attributes['cache']; // Cache: https://www.dokuwiki.org/images#caching
329        $linking = $attributes['linking'];
330
331        switch ($type) {
332            case MediaLink::INTERNAL_MEDIA_CALL_NAME:
333                if (substr($src, -4) === ".svg") {
334                    self::registerFirstMedia($renderer, $src);
335                }
336                $renderer->internalmedia($src, $title, $align, $width, $height, $cache, $linking);
337                break;
338            case MediaLink::EXTERNAL_MEDIA_CALL_NAME:
339                $renderer->externalmedia($src, $title, $align, $width, $height, $cache, $linking);
340                break;
341            default:
342                LogUtility::msg("The dokuwiki media type ($type)  for metadata registration is unknown");
343                break;
344        }
345
346    }
347
348
349}
350
351