xref: /plugin/combo/syntax/media.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
121913ab3SNickeau<?php
221913ab3SNickeau
321913ab3SNickeau
437748cd8SNickeauuse ComboStrap\CallStack;
5*04fd306cSNickeauuse ComboStrap\CardTag;
6*04fd306cSNickeauuse ComboStrap\Dimension;
7*04fd306cSNickeauuse ComboStrap\Display;
8*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
9*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
10*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
11*04fd306cSNickeauuse ComboStrap\ExceptionNotExists;
12*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
13*04fd306cSNickeauuse ComboStrap\ExceptionRuntime;
14*04fd306cSNickeauuse ComboStrap\FetcherSvg;
15*04fd306cSNickeauuse ComboStrap\FileSystems;
16*04fd306cSNickeauuse ComboStrap\FirstRasterImage;
17*04fd306cSNickeauuse ComboStrap\FirstSvgIllustration;
18*04fd306cSNickeauuse ComboStrap\FeaturedIcon;
19*04fd306cSNickeauuse ComboStrap\IFetcherAbs;
2023723136Sgerardnicouse ComboStrap\LogUtility;
21*04fd306cSNickeauuse ComboStrap\MarkupRef;
2223723136Sgerardnicouse ComboStrap\MediaLink;
23*04fd306cSNickeauuse ComboStrap\MediaMarkup;
24*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
25*04fd306cSNickeauuse ComboStrap\Mime;
26*04fd306cSNickeauuse ComboStrap\Path;
2721913ab3SNickeauuse ComboStrap\PluginUtility;
284cadd4f8SNickeauuse ComboStrap\TagAttributes;
2937748cd8SNickeauuse ComboStrap\ThirdPartyPlugins;
30*04fd306cSNickeauuse ComboStrap\WikiPath;
3121913ab3SNickeau
3221913ab3SNickeau
3337748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
3421913ab3SNickeau
3521913ab3SNickeau
3621913ab3SNickeau/**
3723723136Sgerardnico * Media
3823723136Sgerardnico *
3923723136Sgerardnico * Takes over the {@link \dokuwiki\Parsing\ParserMode\Media media mode}
4023723136Sgerardnico * that is processed by {@link Doku_Handler_Parse_Media}
4123723136Sgerardnico *
4223723136Sgerardnico *
4323723136Sgerardnico *
4423723136Sgerardnico * It can be a internal / external media
4537748cd8SNickeau *
461fa8c418SNickeau *
4737748cd8SNickeau * See:
4837748cd8SNickeau * https://developers.google.com/search/docs/advanced/guidelines/google-images
4921913ab3SNickeau */
5021913ab3SNickeauclass syntax_plugin_combo_media extends DokuWiki_Syntax_Plugin
5121913ab3SNickeau{
5221913ab3SNickeau
5321913ab3SNickeau
5421913ab3SNickeau    const TAG = "media";
5521913ab3SNickeau
5621913ab3SNickeau    /**
5721913ab3SNickeau     * Used in the move plugin
5821913ab3SNickeau     * !!! The two last word of the plugin class !!!
5921913ab3SNickeau     */
6021913ab3SNickeau    const COMPONENT = 'combo_' . self::TAG;
6121913ab3SNickeau
6221913ab3SNickeau
6321913ab3SNickeau    /**
6423723136Sgerardnico     * Found at {@link \dokuwiki\Parsing\ParserMode\Media}
6521913ab3SNickeau     */
6623723136Sgerardnico    const MEDIA_PATTERN = "\{\{(?:[^>\}]|(?:\}[^\}]))+\}\}";
6721913ab3SNickeau
68531e725cSNickeau    /**
69531e725cSNickeau     * Enable or disable the image
70531e725cSNickeau     */
71531e725cSNickeau    const CONF_IMAGE_ENABLE = "imageEnable";
72531e725cSNickeau
7337748cd8SNickeau    /**
7437748cd8SNickeau     * Svg Rendering error
7537748cd8SNickeau     */
7637748cd8SNickeau    const SVG_RENDERING_ERROR_CLASS = "combo-svg-rendering-error";
77*04fd306cSNickeau    const CANONICAL = "media";
7837748cd8SNickeau
794cadd4f8SNickeau
80*04fd306cSNickeau    public static function registerFirstImage(Doku_Renderer_metadata $renderer, Path $path)
8182a60d03SNickeau    {
8282a60d03SNickeau        /**
8382a60d03SNickeau         * {@link Doku_Renderer_metadata::$firstimage} is unfortunately protected
8482a60d03SNickeau         * and {@link Doku_Renderer_metadata::internalmedia()} does not allow svg as first image
8582a60d03SNickeau         */
86*04fd306cSNickeau        if (!($path instanceof WikiPath)) {
87*04fd306cSNickeau            return;
88*04fd306cSNickeau        }
89*04fd306cSNickeau        if (!FileSystems::exists($path)) {
90*04fd306cSNickeau            return;
91*04fd306cSNickeau        }
92*04fd306cSNickeau        /**
93*04fd306cSNickeau         * Image Id check
94*04fd306cSNickeau         */
95*04fd306cSNickeau        $wikiId = $path->getWikiId();
96*04fd306cSNickeau        if (media_isexternal($wikiId)) {
97*04fd306cSNickeau            // The first image is not a local image
98*04fd306cSNickeau            // Don't set
99*04fd306cSNickeau            return;
100*04fd306cSNickeau        }
101*04fd306cSNickeau        try {
102*04fd306cSNickeau            $mime = $path->getMime();
103*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
104*04fd306cSNickeau            LogUtility::internalError("The mime for the path ($path) was not found", self::CANONICAL, $e);
105*04fd306cSNickeau            return;
106*04fd306cSNickeau        }
107*04fd306cSNickeau        if (!isset($renderer->meta[FirstRasterImage::PROPERTY_NAME])) {
108*04fd306cSNickeau            if ($mime->isSupportedRasterImage()) {
109*04fd306cSNickeau                $renderer->meta[FirstRasterImage::PROPERTY_NAME] = $wikiId;
110*04fd306cSNickeau                return;
111*04fd306cSNickeau            }
112*04fd306cSNickeau        }
113*04fd306cSNickeau        if (!isset($renderer->meta[FirstSvgIllustration::PROPERTY_NAME]) || !isset($renderer->meta[FeaturedIcon::FIRST_ICON_PARSED])) {
114*04fd306cSNickeau            if ($mime->toString() === Mime::SVG) {
115*04fd306cSNickeau                try {
116*04fd306cSNickeau                    $isIcon = FetcherSvg::createSvgFromPath(WikiPath::createMediaPathFromId($wikiId))
117*04fd306cSNickeau                        ->isIconStructure();
118*04fd306cSNickeau                } catch (Exception $e) {
119*04fd306cSNickeau                    return;
120*04fd306cSNickeau                }
121*04fd306cSNickeau                if (!$isIcon) {
122*04fd306cSNickeau                    $renderer->meta[FirstSvgIllustration::PROPERTY_NAME] = $wikiId;
123*04fd306cSNickeau                } else {
124*04fd306cSNickeau                    $renderer->meta[FeaturedIcon::FIRST_ICON_PARSED] = $wikiId;
125*04fd306cSNickeau                }
126*04fd306cSNickeau            }
12782a60d03SNickeau        }
12882a60d03SNickeau
12982a60d03SNickeau    }
13082a60d03SNickeau
13137748cd8SNickeau
13237748cd8SNickeau    /**
13337748cd8SNickeau     * @param $attributes
13437748cd8SNickeau     * @param renderer_plugin_combo_analytics $renderer
13537748cd8SNickeau     */
13637748cd8SNickeau    public static function updateStatistics($attributes, renderer_plugin_combo_analytics $renderer)
13737748cd8SNickeau    {
138*04fd306cSNickeau        $markupUrlString = $attributes[MarkupRef::REF_ATTRIBUTE];
139*04fd306cSNickeau        $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT]++;
140*04fd306cSNickeau        try {
141*04fd306cSNickeau            $markupUrl = MediaMarkup::createFromRef($markupUrlString);
142*04fd306cSNickeau        } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
143*04fd306cSNickeau            LogUtility::error("media update statistics: cannot create the media markup", "media", $e);
144*04fd306cSNickeau            return;
145*04fd306cSNickeau        }
146*04fd306cSNickeau        switch ($markupUrl->getInternalExternalType()) {
147*04fd306cSNickeau            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
148*04fd306cSNickeau                $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT]++;
149*04fd306cSNickeau                try {
150*04fd306cSNickeau                    $path = $markupUrl->getPath();
151*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
152*04fd306cSNickeau                    LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG);
153*04fd306cSNickeau                    return;
154*04fd306cSNickeau                }
155*04fd306cSNickeau                if (!FileSystems::exists($path)) {
156*04fd306cSNickeau                    $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT]++;
15737748cd8SNickeau                }
15837748cd8SNickeau                break;
159*04fd306cSNickeau            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
160*04fd306cSNickeau                $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT]++;
16137748cd8SNickeau                break;
16237748cd8SNickeau        }
16337748cd8SNickeau    }
16437748cd8SNickeau
16521913ab3SNickeau
1664cadd4f8SNickeau    function getType(): string
16721913ab3SNickeau    {
16821913ab3SNickeau        return 'formatting';
16921913ab3SNickeau    }
17021913ab3SNickeau
17121913ab3SNickeau    /**
17221913ab3SNickeau     * How Dokuwiki will add P element
17321913ab3SNickeau     *
17421913ab3SNickeau     *  * 'normal' - The plugin can be used inside paragraphs (inline)
17521913ab3SNickeau     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
17621913ab3SNickeau     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
17721913ab3SNickeau     *
17821913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::getPType()
17921913ab3SNickeau     */
1804cadd4f8SNickeau    function getPType(): string
18121913ab3SNickeau    {
18223723136Sgerardnico        /**
18323723136Sgerardnico         * An image is not a block (it can be inside paragraph)
18423723136Sgerardnico         */
18521913ab3SNickeau        return 'normal';
18621913ab3SNickeau    }
18721913ab3SNickeau
1884cadd4f8SNickeau    function getAllowedTypes(): array
18921913ab3SNickeau    {
19021913ab3SNickeau        return array('substition', 'formatting', 'disabled');
19121913ab3SNickeau    }
19221913ab3SNickeau
19323723136Sgerardnico    /**
19423723136Sgerardnico     * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()}
19523723136Sgerardnico     * (It was 320 at the time of writing this code)
19623723136Sgerardnico     * @return int
19723723136Sgerardnico     *
19823723136Sgerardnico     */
1994cadd4f8SNickeau    function getSort(): int
20021913ab3SNickeau    {
20123723136Sgerardnico        return 319;
20221913ab3SNickeau    }
20321913ab3SNickeau
20421913ab3SNickeau
20521913ab3SNickeau    function connectTo($mode)
20621913ab3SNickeau    {
207531e725cSNickeau        $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1);
20821913ab3SNickeau        if (!$enable) {
20921913ab3SNickeau
21021913ab3SNickeau            // Inside a card, we need to take over and enable it
21121913ab3SNickeau            $modes = [
212*04fd306cSNickeau                PluginUtility::getModeFromTag(CardTag::CARD_TAG),
21321913ab3SNickeau            ];
21421913ab3SNickeau            $enable = in_array($mode, $modes);
21521913ab3SNickeau        }
21621913ab3SNickeau
21721913ab3SNickeau        if ($enable) {
21837748cd8SNickeau            if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) {
2199337a630SNickeau                $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
22021913ab3SNickeau            }
22121913ab3SNickeau        }
22237748cd8SNickeau    }
22321913ab3SNickeau
22421913ab3SNickeau
2251fa8c418SNickeau    function handle($match, $state, $pos, Doku_Handler $handler): array
22621913ab3SNickeau    {
22721913ab3SNickeau
22821913ab3SNickeau        // As this is a container, this cannot happens but yeah, now, you know
229*04fd306cSNickeau        if ($state == DOKU_LEXER_SPECIAL) {
23037748cd8SNickeau
231*04fd306cSNickeau            try {
232*04fd306cSNickeau                $mediaMarkup = MediaMarkup::createFromMarkup($match);
233*04fd306cSNickeau            } catch (ExceptionCompile $e) {
234*04fd306cSNickeau                $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}";
235*04fd306cSNickeau                // to get the trace on test run
236*04fd306cSNickeau                LogUtility::error($message, self::TAG, $e);
237*04fd306cSNickeau                return [];
238*04fd306cSNickeau            }
23937748cd8SNickeau
24037748cd8SNickeau            /**
24137748cd8SNickeau             * Parent
24237748cd8SNickeau             */
243*04fd306cSNickeau            $callStack = CallStack::createFromHandler($handler);
24437748cd8SNickeau            $parent = $callStack->moveToParent();
24521913ab3SNickeau            $parentTag = "";
24621913ab3SNickeau            if (!empty($parent)) {
24737748cd8SNickeau                $parentTag = $parent->getTagName();
2484cadd4f8SNickeau                if (in_array($parentTag,
2494cadd4f8SNickeau                    [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) {
25021913ab3SNickeau                    /**
251*04fd306cSNickeau                     * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link}
252*04fd306cSNickeau                     *   / {@link syntax_plugin_combo_brand::handle()} brand
2534cadd4f8SNickeau                     *   - The image is in a link, we don't want another link to the image
2544cadd4f8SNickeau                     *   - In a brand, there is also already a link to the home page, no link to the media
25521913ab3SNickeau                     */
256*04fd306cSNickeau                    $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE);
25721913ab3SNickeau                }
25821913ab3SNickeau            }
25937748cd8SNickeau
260*04fd306cSNickeau            $callStackArray = $mediaMarkup->toCallStackArray();
26121913ab3SNickeau            return array(
26221913ab3SNickeau                PluginUtility::STATE => $state,
263*04fd306cSNickeau                PluginUtility::ATTRIBUTES => $callStackArray,
264*04fd306cSNickeau                PluginUtility::CONTEXT => $parentTag,
265*04fd306cSNickeau                PluginUtility::TAG => MediaMarkup::TAG
26621913ab3SNickeau            );
26721913ab3SNickeau        }
26821913ab3SNickeau        return array();
26921913ab3SNickeau
27021913ab3SNickeau    }
27121913ab3SNickeau
27221913ab3SNickeau    /**
27321913ab3SNickeau     * Render the output
27421913ab3SNickeau     * @param string $format
27521913ab3SNickeau     * @param Doku_Renderer $renderer
27621913ab3SNickeau     * @param array $data - what the function handle() return'ed
27721913ab3SNickeau     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
27821913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::render()
27921913ab3SNickeau     *
28021913ab3SNickeau     */
2814cadd4f8SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
28221913ab3SNickeau    {
28321913ab3SNickeau
28421913ab3SNickeau        switch ($format) {
28521913ab3SNickeau
28621913ab3SNickeau            case 'xhtml':
28721913ab3SNickeau                /**
288*04fd306cSNickeau                 * @var Doku_Renderer_xhtml $renderer
28921913ab3SNickeau                 */
290*04fd306cSNickeau                $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer);
29123723136Sgerardnico                return true;
29221913ab3SNickeau
293*04fd306cSNickeau            case "metadata":
29421913ab3SNickeau
29521913ab3SNickeau                /**
29621913ab3SNickeau                 * @var Doku_Renderer_metadata $renderer
29721913ab3SNickeau                 */
298*04fd306cSNickeau                MediaMarkup::metadata($data, $renderer);
29923723136Sgerardnico                return true;
30021913ab3SNickeau
301e8b2ff59SNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
302e8b2ff59SNickeau
303e8b2ff59SNickeau                /**
304e8b2ff59SNickeau                 * @var renderer_plugin_combo_analytics $renderer
305e8b2ff59SNickeau                 */
306*04fd306cSNickeau                MediaMarkup::analytics($data, $renderer);
307e8b2ff59SNickeau                return true;
308e8b2ff59SNickeau
30921913ab3SNickeau        }
31021913ab3SNickeau        // unsupported $mode
31121913ab3SNickeau        return false;
31221913ab3SNickeau    }
31321913ab3SNickeau
31421913ab3SNickeau    /**
31537748cd8SNickeau     * Update the index for the move plugin
316c3437056SNickeau     * and {@link Metadata::FIRST_IMAGE_META_RELATION}
31721913ab3SNickeau     * @param array $attributes
31821913ab3SNickeau     * @param Doku_Renderer_metadata $renderer
31921913ab3SNickeau     */
320*04fd306cSNickeau    static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer)
32121913ab3SNickeau    {
322*04fd306cSNickeau        try {
323*04fd306cSNickeau            $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes);
324*04fd306cSNickeau        } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) {
325*04fd306cSNickeau            LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}");
326*04fd306cSNickeau            return;
327*04fd306cSNickeau        } catch (ExceptionNotExists $e) {
328*04fd306cSNickeau            return;
329531e725cSNickeau        }
330*04fd306cSNickeau        try {
331*04fd306cSNickeau            $label = $mediaMarkup->getLabel();
332*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
333*04fd306cSNickeau            $label = "";
33482a60d03SNickeau        }
335*04fd306cSNickeau        $internalExternalType = $mediaMarkup->getInternalExternalType();
336*04fd306cSNickeau        try {
337*04fd306cSNickeau            $src = $mediaMarkup->getSrc();
338*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
339*04fd306cSNickeau            LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e);
340*04fd306cSNickeau            return;
341*04fd306cSNickeau        }
342*04fd306cSNickeau        switch ($internalExternalType) {
343*04fd306cSNickeau            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
344*04fd306cSNickeau                try {
345*04fd306cSNickeau                    $path = $mediaMarkup->getMarkupRef()->getPath();
346*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
347*04fd306cSNickeau                    LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG);
348*04fd306cSNickeau                    return;
349*04fd306cSNickeau                }
350*04fd306cSNickeau                self::registerFirstImage($renderer, $path);
351*04fd306cSNickeau                $renderer->internalmedia($src, $label);
35223723136Sgerardnico                break;
353*04fd306cSNickeau            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
354*04fd306cSNickeau                $renderer->externalmedia($src, $label);
35523723136Sgerardnico                break;
35623723136Sgerardnico            default:
357*04fd306cSNickeau                LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown");
35823723136Sgerardnico                break;
35923723136Sgerardnico        }
36023723136Sgerardnico
36121913ab3SNickeau    }
36221913ab3SNickeau
36321913ab3SNickeau
36421913ab3SNickeau}
36521913ab3SNickeau
366