1<?php 2 3 4use ComboStrap\AnalyticsDocument; 5use ComboStrap\CallStack; 6use ComboStrap\DokuFs; 7use ComboStrap\ExceptionComboRuntime; 8use ComboStrap\InternetPath; 9use ComboStrap\LogUtility; 10use ComboStrap\MediaLink; 11use ComboStrap\Metadata; 12use ComboStrap\PageImages; 13use ComboStrap\PagePath; 14use ComboStrap\PluginUtility; 15use ComboStrap\SvgDocument; 16use ComboStrap\TagAttributes; 17use ComboStrap\ThirdPartyPlugins; 18 19 20require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 21 22 23/** 24 * Media 25 * 26 * Takes over the {@link \dokuwiki\Parsing\ParserMode\Media media mode} 27 * that is processed by {@link Doku_Handler_Parse_Media} 28 * 29 * 30 * 31 * It can be a internal / external media 32 * 33 * 34 * See: 35 * https://developers.google.com/search/docs/advanced/guidelines/google-images 36 */ 37class syntax_plugin_combo_media extends DokuWiki_Syntax_Plugin 38{ 39 40 41 const TAG = "media"; 42 43 /** 44 * Used in the move plugin 45 * !!! The two last word of the plugin class !!! 46 */ 47 const COMPONENT = 'combo_' . self::TAG; 48 49 50 /** 51 * Found at {@link \dokuwiki\Parsing\ParserMode\Media} 52 */ 53 const MEDIA_PATTERN = "\{\{(?:[^>\}]|(?:\}[^\}]))+\}\}"; 54 55 /** 56 * Enable or disable the image 57 */ 58 const CONF_IMAGE_ENABLE = "imageEnable"; 59 60 /** 61 * Svg Rendering error 62 */ 63 const SVG_RENDERING_ERROR_CLASS = "combo-svg-rendering-error"; 64 65 /** 66 * An attribute to set the class of the link if any 67 */ 68 const LINK_CLASS_ATTRIBUTE = "link-class"; 69 70 public static function registerFirstMedia(Doku_Renderer_metadata $renderer, $src) 71 { 72 /** 73 * {@link Doku_Renderer_metadata::$firstimage} is unfortunately protected 74 * and {@link Doku_Renderer_metadata::internalmedia()} does not allow svg as first image 75 */ 76 if (!isset($renderer->meta[PageImages::FIRST_IMAGE_META_RELATION])) { 77 $renderer->meta[PageImages::FIRST_IMAGE_META_RELATION] = $src; 78 } 79 80 } 81 82 83 /** 84 * @param $attributes 85 * @param renderer_plugin_combo_analytics $renderer 86 */ 87 public static function updateStatistics($attributes, renderer_plugin_combo_analytics $renderer) 88 { 89 $media = MediaLink::createFromCallStackArray($attributes); 90 $renderer->stats[AnalyticsDocument::MEDIA_COUNT]++; 91 $scheme = $media->getMedia()->getPath()->getScheme(); 92 switch ($scheme) { 93 case DokuFs::SCHEME: 94 $renderer->stats[AnalyticsDocument::INTERNAL_MEDIA_COUNT]++; 95 if (!$media->getMedia()->exists()) { 96 $renderer->stats[AnalyticsDocument::INTERNAL_BROKEN_MEDIA_COUNT]++; 97 } 98 break; 99 case InternetPath::scheme: 100 $renderer->stats[AnalyticsDocument::EXTERNAL_MEDIA_COUNT]++; 101 break; 102 } 103 } 104 105 106 function getType(): string 107 { 108 return 'formatting'; 109 } 110 111 /** 112 * How Dokuwiki will add P element 113 * 114 * * 'normal' - The plugin can be used inside paragraphs (inline) 115 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 116 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 117 * 118 * @see DokuWiki_Syntax_Plugin::getPType() 119 */ 120 function getPType(): string 121 { 122 /** 123 * An image is not a block (it can be inside paragraph) 124 */ 125 return 'normal'; 126 } 127 128 function getAllowedTypes(): array 129 { 130 return array('substition', 'formatting', 'disabled'); 131 } 132 133 /** 134 * It should be less than {@link \dokuwiki\Parsing\ParserMode\Media::getSort()} 135 * (It was 320 at the time of writing this code) 136 * @return int 137 * 138 */ 139 function getSort(): int 140 { 141 return 319; 142 } 143 144 145 function connectTo($mode) 146 { 147 $enable = $this->getConf(self::CONF_IMAGE_ENABLE, 1); 148 if (!$enable) { 149 150 // Inside a card, we need to take over and enable it 151 $modes = [ 152 PluginUtility::getModeFromTag(syntax_plugin_combo_card::TAG), 153 ]; 154 $enable = in_array($mode, $modes); 155 } 156 157 if ($enable) { 158 if ($mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)) { 159 $this->Lexer->addSpecialPattern(self::MEDIA_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 160 } 161 } 162 } 163 164 165 function handle($match, $state, $pos, Doku_Handler $handler): array 166 { 167 168 switch ($state) { 169 170 171 // As this is a container, this cannot happens but yeah, now, you know 172 case DOKU_LEXER_SPECIAL : 173 174 /** 175 * Note: The type of image for a svg (icon/illustration) is dedicated 176 * by its structure or is expressly set on type 177 */ 178 $media = MediaLink::createFromRenderMatch($match); 179 $attributes = $media->toCallStackArray(); 180 181 $callStack = CallStack::createFromHandler($handler); 182 183 /** 184 * Parent 185 */ 186 $parent = $callStack->moveToParent(); 187 $parentTag = ""; 188 if (!empty($parent)) { 189 $parentTag = $parent->getTagName(); 190 if (in_array($parentTag, 191 [syntax_plugin_combo_link::TAG, syntax_plugin_combo_brand::TAG])) { 192 /** 193 * TODO: should be on the exit tag of the link / brand 194 * - The image is in a link, we don't want another link to the image 195 * - In a brand, there is also already a link to the home page, no link to the media 196 */ 197 $attributes[MediaLink::LINKING_KEY] = MediaLink::LINKING_NOLINK_VALUE; 198 } 199 } 200 201 return array( 202 PluginUtility::STATE => $state, 203 PluginUtility::ATTRIBUTES => $attributes, 204 PluginUtility::CONTEXT => $parentTag 205 ); 206 207 208 } 209 return array(); 210 211 } 212 213 /** 214 * Render the output 215 * @param string $format 216 * @param Doku_Renderer $renderer 217 * @param array $data - what the function handle() return'ed 218 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 219 * @see DokuWiki_Syntax_Plugin::render() 220 * 221 * 222 */ 223 function render($format, Doku_Renderer $renderer, $data): bool 224 { 225 226 switch ($format) { 227 228 case 'xhtml': 229 230 /** @var Doku_Renderer_xhtml $renderer */ 231 $attributes = $data[PluginUtility::ATTRIBUTES]; 232 $mediaLink = MediaLink::createFromCallStackArray($attributes, $renderer->date_at); 233 $media = $mediaLink->getMedia(); 234 if ($media->getPath()->getScheme() == DokuFs::SCHEME) { 235 if ($media->getPath()->getMime()->isImage() || $media->getPath()->getExtension() === "svg") { 236 try { 237 $renderer->doc .= $mediaLink->renderMediaTagWithLink(); 238 } catch (RuntimeException $e) { 239 if (PluginUtility::isDevOrTest()) { 240 throw new ExceptionComboRuntime("Media Rendering Error. {$e->getMessage()}", MediaLink::CANONICAL, 0, $e); 241 } else { 242 $errorClass = self::SVG_RENDERING_ERROR_CLASS; 243 $message = "Media ({$media->getPath()}). Error while rendering: {$e->getMessage()}"; 244 $renderer->doc .= "<span class=\"text-alert $errorClass\">" . hsc(trim($message)) . "</span>"; 245 LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, MediaLink::CANONICAL); 246 } 247 } 248 return true; 249 } 250 } 251 252 /** 253 * This is not an local internal media image (a video or an url image) 254 * Dokuwiki takes over 255 */ 256 $type = $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE]; 257 $src = $attributes['src']; 258 $title = $attributes['title']; 259 $align = $attributes['align']; 260 $width = $attributes['width']; 261 $height = $attributes['height']; 262 $cache = $attributes['cache']; 263 if ($cache == null) { 264 // Dokuwiki needs a value 265 // If their is no value it will output it without any value 266 // in the query string. 267 $cache = "cache"; 268 } 269 $linking = $attributes['linking']; 270 switch ($type) { 271 case MediaLink::INTERNAL_MEDIA_CALL_NAME: 272 $renderer->doc .= $renderer->internalmedia($src, $title, $align, $width, $height, $cache, $linking, true); 273 break; 274 case MediaLink::EXTERNAL_MEDIA_CALL_NAME: 275 $renderer->doc .= $renderer->externalmedia($src, $title, $align, $width, $height, $cache, $linking, true); 276 break; 277 default: 278 LogUtility::msg("The dokuwiki media type ($type) is unknown"); 279 break; 280 } 281 282 return true; 283 284 case 285 "metadata": 286 287 /** 288 * Keep track of the metadata 289 * @var Doku_Renderer_metadata $renderer 290 */ 291 $attributes = $data[PluginUtility::ATTRIBUTES]; 292 self::registerImageMeta($attributes, $renderer); 293 return true; 294 295 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 296 297 /** 298 * Special pattern call 299 * @var renderer_plugin_combo_analytics $renderer 300 */ 301 $attributes = $data[PluginUtility::ATTRIBUTES]; 302 self::updateStatistics($attributes, $renderer); 303 return true; 304 305 } 306 // unsupported $mode 307 return false; 308 } 309 310 /** 311 * Update the index for the move plugin 312 * and {@link Metadata::FIRST_IMAGE_META_RELATION} 313 * 314 * @param array $attributes 315 * @param Doku_Renderer_metadata $renderer 316 */ 317 static public function registerImageMeta($attributes, $renderer) 318 { 319 $type = $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE]; 320 $src = $attributes['src']; 321 if ($src == null) { 322 $src = $attributes[PagePath::PROPERTY_NAME]; 323 } 324 $title = $attributes['title']; 325 $align = $attributes['align']; 326 $width = $attributes['width']; 327 $height = $attributes['height']; 328 $cache = $attributes['cache']; // Cache: https://www.dokuwiki.org/images#caching 329 $linking = $attributes['linking']; 330 331 switch ($type) { 332 case MediaLink::INTERNAL_MEDIA_CALL_NAME: 333 if (substr($src, -4) === ".svg") { 334 self::registerFirstMedia($renderer, $src); 335 } 336 $renderer->internalmedia($src, $title, $align, $width, $height, $cache, $linking); 337 break; 338 case MediaLink::EXTERNAL_MEDIA_CALL_NAME: 339 $renderer->externalmedia($src, $title, $align, $width, $height, $cache, $linking); 340 break; 341 default: 342 LogUtility::msg("The dokuwiki media type ($type) for metadata registration is unknown"); 343 break; 344 } 345 346 } 347 348 349} 350 351