xref: /plugin/combo/syntax/media.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
121913ab3SNickeau<?php
221913ab3SNickeau
321913ab3SNickeau
437748cd8SNickeauuse ComboStrap\CallStack;
504fd306cSNickeauuse ComboStrap\CardTag;
604fd306cSNickeauuse ComboStrap\Dimension;
704fd306cSNickeauuse ComboStrap\Display;
804fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
904fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
1004fd306cSNickeauuse ComboStrap\ExceptionCompile;
1104fd306cSNickeauuse ComboStrap\ExceptionNotExists;
1204fd306cSNickeauuse ComboStrap\ExceptionNotFound;
1304fd306cSNickeauuse ComboStrap\ExceptionRuntime;
1404fd306cSNickeauuse ComboStrap\FetcherSvg;
1504fd306cSNickeauuse ComboStrap\FileSystems;
1604fd306cSNickeauuse ComboStrap\FirstRasterImage;
1704fd306cSNickeauuse ComboStrap\FirstSvgIllustration;
1804fd306cSNickeauuse ComboStrap\FeaturedIcon;
1904fd306cSNickeauuse ComboStrap\IFetcherAbs;
2023723136Sgerardnicouse ComboStrap\LogUtility;
2104fd306cSNickeauuse ComboStrap\MarkupRef;
2223723136Sgerardnicouse ComboStrap\MediaLink;
2304fd306cSNickeauuse ComboStrap\MediaMarkup;
2404fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
2504fd306cSNickeauuse ComboStrap\Mime;
2604fd306cSNickeauuse ComboStrap\Path;
2721913ab3SNickeauuse ComboStrap\PluginUtility;
284cadd4f8SNickeauuse ComboStrap\TagAttributes;
2937748cd8SNickeauuse ComboStrap\ThirdPartyPlugins;
3004fd306cSNickeauuse 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";
7704fd306cSNickeau    const CANONICAL = "media";
7837748cd8SNickeau
794cadd4f8SNickeau
8004fd306cSNickeau    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         */
8604fd306cSNickeau        if (!($path instanceof WikiPath)) {
8704fd306cSNickeau            return;
8804fd306cSNickeau        }
8904fd306cSNickeau        if (!FileSystems::exists($path)) {
9004fd306cSNickeau            return;
9104fd306cSNickeau        }
9204fd306cSNickeau        /**
9304fd306cSNickeau         * Image Id check
9404fd306cSNickeau         */
9504fd306cSNickeau        $wikiId = $path->getWikiId();
9604fd306cSNickeau        if (media_isexternal($wikiId)) {
9704fd306cSNickeau            // The first image is not a local image
9804fd306cSNickeau            // Don't set
9904fd306cSNickeau            return;
10004fd306cSNickeau        }
10104fd306cSNickeau        try {
10204fd306cSNickeau            $mime = $path->getMime();
10304fd306cSNickeau        } catch (ExceptionNotFound $e) {
10404fd306cSNickeau            LogUtility::internalError("The mime for the path ($path) was not found", self::CANONICAL, $e);
10504fd306cSNickeau            return;
10604fd306cSNickeau        }
10704fd306cSNickeau        if (!isset($renderer->meta[FirstRasterImage::PROPERTY_NAME])) {
10804fd306cSNickeau            if ($mime->isSupportedRasterImage()) {
10904fd306cSNickeau                $renderer->meta[FirstRasterImage::PROPERTY_NAME] = $wikiId;
11004fd306cSNickeau                return;
11104fd306cSNickeau            }
11204fd306cSNickeau        }
11304fd306cSNickeau        if (!isset($renderer->meta[FirstSvgIllustration::PROPERTY_NAME]) || !isset($renderer->meta[FeaturedIcon::FIRST_ICON_PARSED])) {
11404fd306cSNickeau            if ($mime->toString() === Mime::SVG) {
11504fd306cSNickeau                try {
11604fd306cSNickeau                    $isIcon = FetcherSvg::createSvgFromPath(WikiPath::createMediaPathFromId($wikiId))
11704fd306cSNickeau                        ->isIconStructure();
11804fd306cSNickeau                } catch (Exception $e) {
11904fd306cSNickeau                    return;
12004fd306cSNickeau                }
12104fd306cSNickeau                if (!$isIcon) {
12204fd306cSNickeau                    $renderer->meta[FirstSvgIllustration::PROPERTY_NAME] = $wikiId;
12304fd306cSNickeau                } else {
12404fd306cSNickeau                    $renderer->meta[FeaturedIcon::FIRST_ICON_PARSED] = $wikiId;
12504fd306cSNickeau                }
12604fd306cSNickeau            }
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    {
13804fd306cSNickeau        $markupUrlString = $attributes[MarkupRef::REF_ATTRIBUTE];
139*70bbd7f1Sgerardnico        $actualMediaCount = $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] ?? 0;
140*70bbd7f1Sgerardnico        $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] = $actualMediaCount + 1;
14104fd306cSNickeau        try {
14204fd306cSNickeau            $markupUrl = MediaMarkup::createFromRef($markupUrlString);
14304fd306cSNickeau        } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
14404fd306cSNickeau            LogUtility::error("media update statistics: cannot create the media markup", "media", $e);
14504fd306cSNickeau            return;
14604fd306cSNickeau        }
14704fd306cSNickeau        switch ($markupUrl->getInternalExternalType()) {
14804fd306cSNickeau            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
149*70bbd7f1Sgerardnico                $actualInternalMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] ?? 0;
150*70bbd7f1Sgerardnico                $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] = $actualInternalMediaCount + 1;
15104fd306cSNickeau                try {
15204fd306cSNickeau                    $path = $markupUrl->getPath();
15304fd306cSNickeau                } catch (ExceptionNotFound $e) {
15404fd306cSNickeau                    LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG);
15504fd306cSNickeau                    return;
15604fd306cSNickeau                }
15704fd306cSNickeau                if (!FileSystems::exists($path)) {
158*70bbd7f1Sgerardnico                    $brokenMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] ?? 0;
159*70bbd7f1Sgerardnico                    $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] = $brokenMediaCount + 1;
16037748cd8SNickeau                }
16137748cd8SNickeau                break;
16204fd306cSNickeau            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
163*70bbd7f1Sgerardnico                $mediaCount = $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] ?? 0;
164*70bbd7f1Sgerardnico                $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] = $mediaCount + 1;
16537748cd8SNickeau                break;
16637748cd8SNickeau        }
16737748cd8SNickeau    }
16837748cd8SNickeau
16921913ab3SNickeau
1704cadd4f8SNickeau    function getType(): string
17121913ab3SNickeau    {
17221913ab3SNickeau        return 'formatting';
17321913ab3SNickeau    }
17421913ab3SNickeau
17521913ab3SNickeau    /**
17621913ab3SNickeau     * How Dokuwiki will add P element
17721913ab3SNickeau     *
17821913ab3SNickeau     *  * 'normal' - The plugin can be used inside paragraphs (inline)
17921913ab3SNickeau     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
18021913ab3SNickeau     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
18121913ab3SNickeau     *
18221913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::getPType()
18321913ab3SNickeau     */
1844cadd4f8SNickeau    function getPType(): string
18521913ab3SNickeau    {
18623723136Sgerardnico        /**
18723723136Sgerardnico         * An image is not a block (it can be inside paragraph)
18823723136Sgerardnico         */
18921913ab3SNickeau        return 'normal';
19021913ab3SNickeau    }
19121913ab3SNickeau
1924cadd4f8SNickeau    function getAllowedTypes(): array
19321913ab3SNickeau    {
19421913ab3SNickeau        return array('substition', 'formatting', 'disabled');
19521913ab3SNickeau    }
19621913ab3SNickeau
19723723136Sgerardnico    /**
19823723136Sgerardnico     * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()}
19923723136Sgerardnico     * (It was 320 at the time of writing this code)
20023723136Sgerardnico     * @return int
20123723136Sgerardnico     *
20223723136Sgerardnico     */
2034cadd4f8SNickeau    function getSort(): int
20421913ab3SNickeau    {
20523723136Sgerardnico        return 319;
20621913ab3SNickeau    }
20721913ab3SNickeau
20821913ab3SNickeau
20921913ab3SNickeau    function connectTo($mode)
21021913ab3SNickeau    {
211531e725cSNickeau        $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1);
21221913ab3SNickeau        if (!$enable) {
21321913ab3SNickeau
21421913ab3SNickeau            // Inside a card, we need to take over and enable it
21521913ab3SNickeau            $modes = [
21604fd306cSNickeau                PluginUtility::getModeFromTag(CardTag::CARD_TAG),
21721913ab3SNickeau            ];
21821913ab3SNickeau            $enable = in_array($mode, $modes);
21921913ab3SNickeau        }
22021913ab3SNickeau
22121913ab3SNickeau        if ($enable) {
22237748cd8SNickeau            if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) {
2239337a630SNickeau                $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
22421913ab3SNickeau            }
22521913ab3SNickeau        }
22637748cd8SNickeau    }
22721913ab3SNickeau
22821913ab3SNickeau
2291fa8c418SNickeau    function handle($match, $state, $pos, Doku_Handler $handler): array
23021913ab3SNickeau    {
23121913ab3SNickeau
23221913ab3SNickeau        // As this is a container, this cannot happens but yeah, now, you know
23304fd306cSNickeau        if ($state == DOKU_LEXER_SPECIAL) {
23437748cd8SNickeau
23504fd306cSNickeau            try {
23604fd306cSNickeau                $mediaMarkup = MediaMarkup::createFromMarkup($match);
23704fd306cSNickeau            } catch (ExceptionCompile $e) {
23804fd306cSNickeau                $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}";
23904fd306cSNickeau                // to get the trace on test run
24004fd306cSNickeau                LogUtility::error($message, self::TAG, $e);
24104fd306cSNickeau                return [];
24204fd306cSNickeau            }
24337748cd8SNickeau
24437748cd8SNickeau            /**
24537748cd8SNickeau             * Parent
24637748cd8SNickeau             */
24704fd306cSNickeau            $callStack = CallStack::createFromHandler($handler);
24837748cd8SNickeau            $parent = $callStack->moveToParent();
24921913ab3SNickeau            $parentTag = "";
25021913ab3SNickeau            if (!empty($parent)) {
25137748cd8SNickeau                $parentTag = $parent->getTagName();
2524cadd4f8SNickeau                if (in_array($parentTag,
2534cadd4f8SNickeau                    [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) {
25421913ab3SNickeau                    /**
25504fd306cSNickeau                     * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link}
25604fd306cSNickeau                     *   / {@link syntax_plugin_combo_brand::handle()} brand
2574cadd4f8SNickeau                     *   - The image is in a link, we don't want another link to the image
2584cadd4f8SNickeau                     *   - In a brand, there is also already a link to the home page, no link to the media
25921913ab3SNickeau                     */
26004fd306cSNickeau                    $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE);
26121913ab3SNickeau                }
26221913ab3SNickeau            }
26337748cd8SNickeau
26404fd306cSNickeau            $callStackArray = $mediaMarkup->toCallStackArray();
26521913ab3SNickeau            return array(
26621913ab3SNickeau                PluginUtility::STATE => $state,
26704fd306cSNickeau                PluginUtility::ATTRIBUTES => $callStackArray,
26804fd306cSNickeau                PluginUtility::CONTEXT => $parentTag,
26904fd306cSNickeau                PluginUtility::TAG => MediaMarkup::TAG
27021913ab3SNickeau            );
27121913ab3SNickeau        }
27221913ab3SNickeau        return array();
27321913ab3SNickeau
27421913ab3SNickeau    }
27521913ab3SNickeau
27621913ab3SNickeau    /**
27721913ab3SNickeau     * Render the output
27821913ab3SNickeau     * @param string $format
27921913ab3SNickeau     * @param Doku_Renderer $renderer
28021913ab3SNickeau     * @param array $data - what the function handle() return'ed
28121913ab3SNickeau     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
28221913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::render()
28321913ab3SNickeau     *
28421913ab3SNickeau     */
2854cadd4f8SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
28621913ab3SNickeau    {
28721913ab3SNickeau
28821913ab3SNickeau        switch ($format) {
28921913ab3SNickeau
29021913ab3SNickeau            case 'xhtml':
29121913ab3SNickeau                /**
29204fd306cSNickeau                 * @var Doku_Renderer_xhtml $renderer
29321913ab3SNickeau                 */
29404fd306cSNickeau                $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer);
29523723136Sgerardnico                return true;
29621913ab3SNickeau
29704fd306cSNickeau            case "metadata":
29821913ab3SNickeau
29921913ab3SNickeau                /**
30021913ab3SNickeau                 * @var Doku_Renderer_metadata $renderer
30121913ab3SNickeau                 */
30204fd306cSNickeau                MediaMarkup::metadata($data, $renderer);
30323723136Sgerardnico                return true;
30421913ab3SNickeau
305e8b2ff59SNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
306e8b2ff59SNickeau
307e8b2ff59SNickeau                /**
308e8b2ff59SNickeau                 * @var renderer_plugin_combo_analytics $renderer
309e8b2ff59SNickeau                 */
31004fd306cSNickeau                MediaMarkup::analytics($data, $renderer);
311e8b2ff59SNickeau                return true;
312e8b2ff59SNickeau
31321913ab3SNickeau        }
31421913ab3SNickeau        // unsupported $mode
31521913ab3SNickeau        return false;
31621913ab3SNickeau    }
31721913ab3SNickeau
31821913ab3SNickeau    /**
31937748cd8SNickeau     * Update the index for the move plugin
320c3437056SNickeau     * and {@link Metadata::FIRST_IMAGE_META_RELATION}
32121913ab3SNickeau     * @param array $attributes
32221913ab3SNickeau     * @param Doku_Renderer_metadata $renderer
32321913ab3SNickeau     */
32404fd306cSNickeau    static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer)
32521913ab3SNickeau    {
32604fd306cSNickeau        try {
32704fd306cSNickeau            $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes);
32804fd306cSNickeau        } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) {
32904fd306cSNickeau            LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}");
33004fd306cSNickeau            return;
33104fd306cSNickeau        } catch (ExceptionNotExists $e) {
33204fd306cSNickeau            return;
333531e725cSNickeau        }
33404fd306cSNickeau        try {
33504fd306cSNickeau            $label = $mediaMarkup->getLabel();
33604fd306cSNickeau        } catch (ExceptionNotFound $e) {
33704fd306cSNickeau            $label = "";
33882a60d03SNickeau        }
33904fd306cSNickeau        $internalExternalType = $mediaMarkup->getInternalExternalType();
34004fd306cSNickeau        try {
34104fd306cSNickeau            $src = $mediaMarkup->getSrc();
34204fd306cSNickeau        } catch (ExceptionNotFound $e) {
34304fd306cSNickeau            LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e);
34404fd306cSNickeau            return;
34504fd306cSNickeau        }
34604fd306cSNickeau        switch ($internalExternalType) {
34704fd306cSNickeau            case MediaMarkup::INTERNAL_MEDIA_CALL_NAME:
34804fd306cSNickeau                try {
34904fd306cSNickeau                    $path = $mediaMarkup->getMarkupRef()->getPath();
35004fd306cSNickeau                } catch (ExceptionNotFound $e) {
35104fd306cSNickeau                    LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG);
35204fd306cSNickeau                    return;
35304fd306cSNickeau                }
35404fd306cSNickeau                self::registerFirstImage($renderer, $path);
35504fd306cSNickeau                $renderer->internalmedia($src, $label);
35623723136Sgerardnico                break;
35704fd306cSNickeau            case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME:
35804fd306cSNickeau                $renderer->externalmedia($src, $label);
35923723136Sgerardnico                break;
36023723136Sgerardnico            default:
36104fd306cSNickeau                LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown");
36223723136Sgerardnico                break;
36323723136Sgerardnico        }
36423723136Sgerardnico
36521913ab3SNickeau    }
36621913ab3SNickeau
36721913ab3SNickeau
36821913ab3SNickeau}
36921913ab3SNickeau
370