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