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