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 $renderer->stats[renderer_plugin_combo_analytics::MEDIA_COUNT]++; 140 try { 141 $markupUrl = MediaMarkup::createFromRef($markupUrlString); 142 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 143 LogUtility::error("media update statistics: cannot create the media markup", "media", $e); 144 return; 145 } 146 switch ($markupUrl->getInternalExternalType()) { 147 case MediaMarkup::INTERNAL_MEDIA_CALL_NAME: 148 $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_MEDIA_COUNT]++; 149 try { 150 $path = $markupUrl->getPath(); 151 } catch (ExceptionNotFound $e) { 152 LogUtility::internalError("The path of an internal media should be known. We were unable to update the statistics.", self::TAG); 153 return; 154 } 155 if (!FileSystems::exists($path)) { 156 $renderer->stats[renderer_plugin_combo_analytics::INTERNAL_BROKEN_MEDIA_COUNT]++; 157 } 158 break; 159 case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME: 160 $renderer->stats[renderer_plugin_combo_analytics::EXTERNAL_MEDIA_COUNT]++; 161 break; 162 } 163 } 164 165 166 function getType(): string 167 { 168 return 'formatting'; 169 } 170 171 /** 172 * How Dokuwiki will add P element 173 * 174 * * 'normal' - The plugin can be used inside paragraphs (inline) 175 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 176 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 177 * 178 * @see DokuWiki_Syntax_Plugin::getPType() 179 */ 180 function getPType(): string 181 { 182 /** 183 * An image is not a block (it can be inside paragraph) 184 */ 185 return 'normal'; 186 } 187 188 function getAllowedTypes(): array 189 { 190 return array('substition', 'formatting', 'disabled'); 191 } 192 193 /** 194 * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()} 195 * (It was 320 at the time of writing this code) 196 * @return int 197 * 198 */ 199 function getSort(): int 200 { 201 return 319; 202 } 203 204 205 function connectTo($mode) 206 { 207 $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1); 208 if (!$enable) { 209 210 // Inside a card, we need to take over and enable it 211 $modes = [ 212 PluginUtility::getModeFromTag(CardTag::CARD_TAG), 213 ]; 214 $enable = in_array($mode, $modes); 215 } 216 217 if ($enable) { 218 if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) { 219 $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 220 } 221 } 222 } 223 224 225 function handle($match, $state, $pos, Doku_Handler $handler): array 226 { 227 228 // As this is a container, this cannot happens but yeah, now, you know 229 if ($state == DOKU_LEXER_SPECIAL) { 230 231 try { 232 $mediaMarkup = MediaMarkup::createFromMarkup($match); 233 } catch (ExceptionCompile $e) { 234 $message = "The media ($match) could not be parsed. Error: {$e->getMessage()}"; 235 // to get the trace on test run 236 LogUtility::error($message, self::TAG, $e); 237 return []; 238 } 239 240 /** 241 * Parent 242 */ 243 $callStack = CallStack::createFromHandler($handler); 244 $parent = $callStack->moveToParent(); 245 $parentTag = ""; 246 if (!empty($parent)) { 247 $parentTag = $parent->getTagName(); 248 if (in_array($parentTag, 249 [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) { 250 /** 251 * TODO: should be on the exit tag of the {@link syntax_plugin_combo_link::handle() link} 252 * / {@link syntax_plugin_combo_brand::handle()} brand 253 * - The image is in a link, we don't want another link to the image 254 * - In a brand, there is also already a link to the home page, no link to the media 255 */ 256 $mediaMarkup->setLinking(MediaMarkup::LINKING_NOLINK_VALUE); 257 } 258 } 259 260 $callStackArray = $mediaMarkup->toCallStackArray(); 261 return array( 262 PluginUtility::STATE => $state, 263 PluginUtility::ATTRIBUTES => $callStackArray, 264 PluginUtility::CONTEXT => $parentTag, 265 PluginUtility::TAG => MediaMarkup::TAG 266 ); 267 } 268 return array(); 269 270 } 271 272 /** 273 * Render the output 274 * @param string $format 275 * @param Doku_Renderer $renderer 276 * @param array $data - what the function handle() return'ed 277 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 278 * @see DokuWiki_Syntax_Plugin::render() 279 * 280 */ 281 function render($format, Doku_Renderer $renderer, $data): bool 282 { 283 284 switch ($format) { 285 286 case 'xhtml': 287 /** 288 * @var Doku_Renderer_xhtml $renderer 289 */ 290 $renderer->doc .= MediaMarkup::renderSpecial($data, $renderer); 291 return true; 292 293 case "metadata": 294 295 /** 296 * @var Doku_Renderer_metadata $renderer 297 */ 298 MediaMarkup::metadata($data, $renderer); 299 return true; 300 301 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 302 303 /** 304 * @var renderer_plugin_combo_analytics $renderer 305 */ 306 MediaMarkup::analytics($data, $renderer); 307 return true; 308 309 } 310 // unsupported $mode 311 return false; 312 } 313 314 /** 315 * Update the index for the move plugin 316 * and {@link Metadata::FIRST_IMAGE_META_RELATION} 317 * @param array $attributes 318 * @param Doku_Renderer_metadata $renderer 319 */ 320 static public function registerImageMeta(array $attributes, Doku_Renderer_metadata $renderer) 321 { 322 try { 323 $mediaMarkup = MediaMarkup::createFromCallStackArray($attributes); 324 } catch (ExceptionNotFound|ExceptionBadArgument|ExceptionBadSyntax $e) { 325 LogUtility::internalError("We can't register the media metadata. Error: {$e->getMessage()}"); 326 return; 327 } catch (ExceptionNotExists $e) { 328 return; 329 } 330 try { 331 $label = $mediaMarkup->getLabel(); 332 } catch (ExceptionNotFound $e) { 333 $label = ""; 334 } 335 $internalExternalType = $mediaMarkup->getInternalExternalType(); 336 try { 337 $src = $mediaMarkup->getSrc(); 338 } catch (ExceptionNotFound $e) { 339 LogUtility::internalError("No src found, we couldn't register the media in the index. Error: {$e->getMessage()}", self::CANONICAL, $e); 340 return; 341 } 342 switch ($internalExternalType) { 343 case MediaMarkup::INTERNAL_MEDIA_CALL_NAME: 344 try { 345 $path = $mediaMarkup->getMarkupRef()->getPath(); 346 } catch (ExceptionNotFound $e) { 347 LogUtility::internalError("We cannot get the path of the image. Error: {$e->getMessage()}. The image was not registered in the metadata", self::TAG); 348 return; 349 } 350 self::registerFirstImage($renderer, $path); 351 $renderer->internalmedia($src, $label); 352 break; 353 case MediaMarkup::EXTERNAL_MEDIA_CALL_NAME: 354 $renderer->externalmedia($src, $label); 355 break; 356 default: 357 LogUtility::msg("The dokuwiki media type ($internalExternalType) for metadata registration is unknown"); 358 break; 359 } 360 361 } 362 363 364} 365 366