1<?php 2 3 4use ComboStrap\CallStack; 5use ComboStrap\CardTag; 6use ComboStrap\Dimension; 7use ComboStrap\Display; 8use ComboStrap\ExceptionBadArgument; 9use ComboStrap\ExceptionBadSyntax; 10use ComboStrap\ExceptionCompile; 11use ComboStrap\ExceptionNotExists; 12use ComboStrap\ExceptionNotFound; 13use ComboStrap\ExceptionRuntime; 14use ComboStrap\FetcherSvg; 15use ComboStrap\FileSystems; 16use ComboStrap\FirstRasterImage; 17use ComboStrap\FirstSvgIllustration; 18use ComboStrap\FeaturedIcon; 19use ComboStrap\IFetcherAbs; 20use ComboStrap\LogUtility; 21use ComboStrap\MarkupRef; 22use ComboStrap\MediaLink; 23use ComboStrap\MediaMarkup; 24use ComboStrap\Meta\Api\Metadata; 25use ComboStrap\Mime; 26use ComboStrap\Path; 27use ComboStrap\PluginUtility; 28use ComboStrap\TagAttributes; 29use ComboStrap\ThirdPartyPlugins; 30use ComboStrap\WikiPath; 31 32 33require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 34 35 36/** 37 * Media 38 * 39 * Takes over the {@link \dokuwiki\Parsing\ParserMode\Media media mode} 40 * that is processed by {@link Doku_Handler_Parse_Media} 41 * 42 * 43 * 44 * It can be a internal / external media 45 * 46 * 47 * See: 48 * https://developers.google.com/search/docs/advanced/guidelines/google-images 49 */ 50class syntax_plugin_combo_media extends DokuWiki_Syntax_Plugin 51{ 52 53 54 const TAG = "media"; 55 56 /** 57 * Used in the move plugin 58 * !!! The two last word of the plugin class !!! 59 */ 60 const COMPONENT = 'combo_' . self::TAG; 61 62 63 /** 64 * Found at {@link \dokuwiki\Parsing\ParserMode\Media} 65 */ 66 const MEDIA_PATTERN = "\{\{(?:[^>\}]|(?:\}[^\}]))+\}\}"; 67 68 /** 69 * Enable or disable the image 70 */ 71 const CONF_IMAGE_ENABLE = "imageEnable"; 72 73 /** 74 * Svg Rendering error 75 */ 76 const SVG_RENDERING_ERROR_CLASS = "combo-svg-rendering-error"; 77 const CANONICAL = "media"; 78 79 80 public static function registerFirstImage(Doku_Renderer_metadata $renderer, Path $path) 81 { 82 /** 83 * {@link Doku_Renderer_metadata::$firstimage} is unfortunately protected 84 * and {@link Doku_Renderer_metadata::internalmedia()} does not allow svg as first image 85 */ 86 if (!($path instanceof WikiPath)) { 87 return; 88 } 89 if (!FileSystems::exists($path)) { 90 return; 91 } 92 /** 93 * Image Id check 94 */ 95 $wikiId = $path->getWikiId(); 96 if (media_isexternal($wikiId)) { 97 // The first image is not a local image 98 // Don't set 99 return; 100 } 101 try { 102 $mime = $path->getMime(); 103 } catch (ExceptionNotFound $e) { 104 LogUtility::internalError("The mime for the path ($path) was not found", self::CANONICAL, $e); 105 return; 106 } 107 if (!isset($renderer->meta[FirstRasterImage::PROPERTY_NAME])) { 108 if ($mime->isSupportedRasterImage()) { 109 $renderer->meta[FirstRasterImage::PROPERTY_NAME] = $wikiId; 110 return; 111 } 112 } 113 if (!isset($renderer->meta[FirstSvgIllustration::PROPERTY_NAME]) || !isset($renderer->meta[FeaturedIcon::FIRST_ICON_PARSED])) { 114 if ($mime->toString() === Mime::SVG) { 115 try { 116 $isIcon = FetcherSvg::createSvgFromPath(WikiPath::createMediaPathFromId($wikiId)) 117 ->isIconStructure(); 118 } catch (Exception $e) { 119 return; 120 } 121 if (!$isIcon) { 122 $renderer->meta[FirstSvgIllustration::PROPERTY_NAME] = $wikiId; 123 } else { 124 $renderer->meta[FeaturedIcon::FIRST_ICON_PARSED] = $wikiId; 125 } 126 } 127 } 128 129 } 130 131 132 /** 133 * @param $attributes 134 * @param renderer_plugin_combo_analytics $renderer 135 */ 136 public static function updateStatistics($attributes, renderer_plugin_combo_analytics $renderer) 137 { 138 $markupUrlString = $attributes[MarkupRef::REF_ATTRIBUTE]; 139 $actualMediaCount = $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] ?? 0; 140 $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT] = $actualMediaCount + 1; 141 try { 142 $markupUrl = MediaMarkup::createFromRef($markupUrlString); 143 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 144 LogUtility::error("media update statistics: cannot create the media markup", "media", $e); 145 return; 146 } 147 switch ($markupUrl->getInternalExternalType()) { 148 case MediaMarkup::INTERNAL_MEDIA_CALL_NAME: 149 $actualInternalMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] ?? 0; 150 $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT] = $actualInternalMediaCount + 1; 151 try { 152 $path = $markupUrl->getPath(); 153 } catch (ExceptionNotFound $e) { 154 LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG); 155 return; 156 } 157 if (!FileSystems::exists($path)) { 158 $brokenMediaCount = $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] ?? 0; 159 $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT] = $brokenMediaCount + 1; 160 } 161 break; 162 case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME: 163 $mediaCount = $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] ?? 0; 164 $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT] = $mediaCount + 1; 165 break; 166 } 167 } 168 169 170 function getType(): string 171 { 172 return 'formatting'; 173 } 174 175 /** 176 * How Dokuwiki will add P element 177 * 178 * * 'normal' - The plugin can be used inside paragraphs (inline) 179 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 180 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 181 * 182 * @see DokuWiki_Syntax_Plugin::getPType() 183 */ 184 function getPType(): string 185 { 186 /** 187 * An image is not a block (it can be inside paragraph) 188 */ 189 return 'normal'; 190 } 191 192 function getAllowedTypes(): array 193 { 194 return array('substition', 'formatting', 'disabled'); 195 } 196 197 /** 198 * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()} 199 * (It was 320 at the time of writing this code) 200 * @return int 201 * 202 */ 203 function getSort(): int 204 { 205 return 319; 206 } 207 208 209 function connectTo($mode) 210 { 211 $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1); 212 if (!$enable) { 213 214 // Inside a card, we need to take over and enable it 215 $modes = [ 216 PluginUtility::getModeFromTag(CardTag::CARD_TAG), 217 ]; 218 $enable = in_array($mode, $modes); 219 } 220 221 if ($enable) { 222 if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) { 223 $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 224 } 225 } 226 } 227 228 229 function handle($match, $state, $pos, Doku_Handler $handler): array 230 { 231 232 // As this is a container, this cannot happens but yeah, now, you know 233 if ($state == DOKU_LEXER_SPECIAL) { 234 235 try { 236 $mediaMarkup = MediaMarkup::createFromMarkup($match); 237 } catch (ExceptionCompile $e) { 238 $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}"; 239 // to get the trace on test run 240 LogUtility::error($message, self::TAG, $e); 241 return []; 242 } 243 244 /** 245 * Parent 246 */ 247 $callStack = CallStack::createFromHandler($handler); 248 $parent = $callStack->moveToParent(); 249 $parentTag = ""; 250 if (!empty($parent)) { 251 $parentTag = $parent->getTagName(); 252 if (in_array($parentTag, 253 [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) { 254 /** 255 * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link} 256 * / {@link syntax_plugin_combo_brand::handle()} brand 257 * - The image is in a link, we don't want another link to the image 258 * - In a brand, there is also already a link to the home page, no link to the media 259 */ 260 $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE); 261 } 262 } 263 264 $callStackArray = $mediaMarkup->toCallStackArray(); 265 return array( 266 PluginUtility::STATE => $state, 267 PluginUtility::ATTRIBUTES => $callStackArray, 268 PluginUtility::CONTEXT => $parentTag, 269 PluginUtility::TAG => MediaMarkup::TAG 270 ); 271 } 272 return array(); 273 274 } 275 276 /** 277 * Render the output 278 * @param string $format 279 * @param Doku_Renderer $renderer 280 * @param array $data - what the function handle() return'ed 281 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 282 * @see DokuWiki_Syntax_Plugin::render() 283 * 284 */ 285 function render($format, Doku_Renderer $renderer, $data): bool 286 { 287 288 switch ($format) { 289 290 case 'xhtml': 291 /** 292 * @var Doku_Renderer_xhtml $renderer 293 */ 294 $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer); 295 return true; 296 297 case "metadata": 298 299 /** 300 * @var Doku_Renderer_metadata $renderer 301 */ 302 MediaMarkup::metadata($data, $renderer); 303 return true; 304 305 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 306 307 /** 308 * @var renderer_plugin_combo_analytics $renderer 309 */ 310 MediaMarkup::analytics($data, $renderer); 311 return true; 312 313 } 314 // unsupported $mode 315 return false; 316 } 317 318 /** 319 * Update the index for the move plugin 320 * and {@link Metadata::FIRST_IMAGE_META_RELATION} 321 * @param array $attributes 322 * @param Doku_Renderer_metadata $renderer 323 */ 324 static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer) 325 { 326 try { 327 $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes); 328 } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) { 329 LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}"); 330 return; 331 } catch (ExceptionNotExists $e) { 332 return; 333 } 334 try { 335 $label = $mediaMarkup->getLabel(); 336 } catch (ExceptionNotFound $e) { 337 $label = ""; 338 } 339 $internalExternalType = $mediaMarkup->getInternalExternalType(); 340 try { 341 $src = $mediaMarkup->getSrc(); 342 } catch (ExceptionNotFound $e) { 343 LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e); 344 return; 345 } 346 switch ($internalExternalType) { 347 case MediaMarkup::INTERNAL_MEDIA_CALL_NAME: 348 try { 349 $path = $mediaMarkup->getMarkupRef()->getPath(); 350 } catch (ExceptionNotFound $e) { 351 LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG); 352 return; 353 } 354 self::registerFirstImage($renderer, $path); 355 $renderer->internalmedia($src, $label); 356 break; 357 case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME: 358 $renderer->externalmedia($src, $label); 359 break; 360 default: 361 LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown"); 362 break; 363 } 364 365 } 366 367 368} 369 370