1*37748cd8SNickeau<?php 2*37748cd8SNickeau/** 3*37748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4*37748cd8SNickeau * 5*37748cd8SNickeau * This source code is licensed under the GPL license found in the 6*37748cd8SNickeau * COPYING file in the root directory of this source tree. 7*37748cd8SNickeau * 8*37748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9*37748cd8SNickeau * @author ComboStrap <support@combostrap.com> 10*37748cd8SNickeau * 11*37748cd8SNickeau */ 12*37748cd8SNickeau 13*37748cd8SNickeaunamespace ComboStrap; 14*37748cd8SNickeau 15*37748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin; 16*37748cd8SNickeauuse syntax_plugin_combo_media; 17*37748cd8SNickeau 18*37748cd8SNickeaurequire_once(__DIR__ . '/DokuPath.php'); 19*37748cd8SNickeau 20*37748cd8SNickeau/** 21*37748cd8SNickeau * Class InternalMedia 22*37748cd8SNickeau * Represent a media link 23*37748cd8SNickeau * 24*37748cd8SNickeau * 25*37748cd8SNickeau * @package ComboStrap 26*37748cd8SNickeau * 27*37748cd8SNickeau * Wrapper around {@link Doku_Handler_Parse_Media} 28*37748cd8SNickeau * 29*37748cd8SNickeau * Not that for dokuwiki the `type` key of the attributes is the `call` 30*37748cd8SNickeau * and therefore determine the function in an render 31*37748cd8SNickeau * (ie {@link \Doku_Renderer::internalmedialink()} or {@link \Doku_Renderer::externalmedialink()} 32*37748cd8SNickeau * 33*37748cd8SNickeau * It's a HTML tag and a URL (in the dokuwiki mode) build around its file system path 34*37748cd8SNickeau */ 35*37748cd8SNickeauabstract class MediaLink extends DokuPath 36*37748cd8SNickeau{ 37*37748cd8SNickeau 38*37748cd8SNickeau 39*37748cd8SNickeau /** 40*37748cd8SNickeau * The dokuwiki type and mode name 41*37748cd8SNickeau * (ie call) 42*37748cd8SNickeau * * ie {@link MediaLink::EXTERNAL_MEDIA_CALL_NAME} 43*37748cd8SNickeau * or {@link MediaLink::INTERNAL_MEDIA_CALL_NAME} 44*37748cd8SNickeau * 45*37748cd8SNickeau * The dokuwiki type (internalmedia/externalmedia) 46*37748cd8SNickeau * is saved in a `type` key that clash with the 47*37748cd8SNickeau * combostrap type. To avoid the clash, we renamed it 48*37748cd8SNickeau */ 49*37748cd8SNickeau const MEDIA_DOKUWIKI_TYPE = 'dokuwiki_type'; 50*37748cd8SNickeau const INTERNAL_MEDIA_CALL_NAME = "internalmedia"; 51*37748cd8SNickeau const EXTERNAL_MEDIA_CALL_NAME = "externalmedia"; 52*37748cd8SNickeau 53*37748cd8SNickeau const CANONICAL = "image"; 54*37748cd8SNickeau 55*37748cd8SNickeau /** 56*37748cd8SNickeau * This attributes does not apply 57*37748cd8SNickeau * to a URL 58*37748cd8SNickeau * They are only for the tag (img, svg, ...) 59*37748cd8SNickeau * or internal 60*37748cd8SNickeau */ 61*37748cd8SNickeau const NON_URL_ATTRIBUTES = [ 62*37748cd8SNickeau self::ALIGN_KEY, 63*37748cd8SNickeau self::LINKING_KEY, 64*37748cd8SNickeau TagAttributes::TITLE_KEY, 65*37748cd8SNickeau Hover::ON_HOVER_ATTRIBUTE, 66*37748cd8SNickeau Animation::ON_VIEW_ATTRIBUTE, 67*37748cd8SNickeau MediaLink::MEDIA_DOKUWIKI_TYPE, 68*37748cd8SNickeau MediaLink::DOKUWIKI_SRC 69*37748cd8SNickeau ]; 70*37748cd8SNickeau 71*37748cd8SNickeau /** 72*37748cd8SNickeau * This attribute applies 73*37748cd8SNickeau * to a image url (img, svg, ...) 74*37748cd8SNickeau */ 75*37748cd8SNickeau const URL_ATTRIBUTES = [ 76*37748cd8SNickeau Dimension::WIDTH_KEY, 77*37748cd8SNickeau Dimension::HEIGHT_KEY, 78*37748cd8SNickeau CacheMedia::CACHE_KEY, 79*37748cd8SNickeau ]; 80*37748cd8SNickeau 81*37748cd8SNickeau /** 82*37748cd8SNickeau * Default image linking value 83*37748cd8SNickeau */ 84*37748cd8SNickeau const CONF_DEFAULT_LINKING = "defaultImageLinking"; 85*37748cd8SNickeau const LINKING_LINKONLY_VALUE = "linkonly"; 86*37748cd8SNickeau const LINKING_DETAILS_VALUE = 'details'; 87*37748cd8SNickeau const LINKING_NOLINK_VALUE = 'nolink'; 88*37748cd8SNickeau 89*37748cd8SNickeau /** 90*37748cd8SNickeau * @deprecated 2021-06-12 91*37748cd8SNickeau */ 92*37748cd8SNickeau const LINK_PATTERN = "{{\s*([^|\s]*)\s*\|?.*}}"; 93*37748cd8SNickeau 94*37748cd8SNickeau const LINKING_DIRECT_VALUE = 'direct'; 95*37748cd8SNickeau 96*37748cd8SNickeau /** 97*37748cd8SNickeau * Only used by Dokuwiki 98*37748cd8SNickeau * Contains the path and eventually an anchor 99*37748cd8SNickeau * never query parameters 100*37748cd8SNickeau */ 101*37748cd8SNickeau const DOKUWIKI_SRC = "src"; 102*37748cd8SNickeau /** 103*37748cd8SNickeau * Link value: 104*37748cd8SNickeau * * 'nolink' 105*37748cd8SNickeau * * 'direct': directly to the image 106*37748cd8SNickeau * * 'linkonly': show only a url 107*37748cd8SNickeau * * 'details': go to the details media viewer 108*37748cd8SNickeau * 109*37748cd8SNickeau * @var 110*37748cd8SNickeau */ 111*37748cd8SNickeau const LINKING_KEY = 'linking'; 112*37748cd8SNickeau const ALIGN_KEY = 'align'; 113*37748cd8SNickeau 114*37748cd8SNickeau 115*37748cd8SNickeau private $lazyLoad = null; 116*37748cd8SNickeau 117*37748cd8SNickeau 118*37748cd8SNickeau /** 119*37748cd8SNickeau * @var TagAttributes 120*37748cd8SNickeau */ 121*37748cd8SNickeau protected $tagAttributes; 122*37748cd8SNickeau 123*37748cd8SNickeau 124*37748cd8SNickeau /** 125*37748cd8SNickeau * Image constructor. 126*37748cd8SNickeau * @param $ref 127*37748cd8SNickeau * @param TagAttributes $tagAttributes 128*37748cd8SNickeau * @param string $rev - mtime 129*37748cd8SNickeau * 130*37748cd8SNickeau * Protected and not private 131*37748cd8SNickeau * to allow cascading init 132*37748cd8SNickeau * If private, the parent attributes are null 133*37748cd8SNickeau * 134*37748cd8SNickeau */ 135*37748cd8SNickeau protected function __construct($absolutePath, $tagAttributes = null, $rev = null) 136*37748cd8SNickeau { 137*37748cd8SNickeau 138*37748cd8SNickeau parent::__construct($absolutePath, DokuPath::MEDIA_TYPE, $rev); 139*37748cd8SNickeau 140*37748cd8SNickeau if ($tagAttributes == null) { 141*37748cd8SNickeau $this->tagAttributes = TagAttributes::createEmpty(); 142*37748cd8SNickeau } else { 143*37748cd8SNickeau $this->tagAttributes = $tagAttributes; 144*37748cd8SNickeau } 145*37748cd8SNickeau 146*37748cd8SNickeau } 147*37748cd8SNickeau 148*37748cd8SNickeau 149*37748cd8SNickeau /** 150*37748cd8SNickeau * Create an image from dokuwiki internal call media attributes 151*37748cd8SNickeau * @param array $callAttributes 152*37748cd8SNickeau * @return MediaLink 153*37748cd8SNickeau */ 154*37748cd8SNickeau public static function createFromIndexAttributes(array $callAttributes) 155*37748cd8SNickeau { 156*37748cd8SNickeau $id = $callAttributes[0]; // path 157*37748cd8SNickeau $title = $callAttributes[1]; 158*37748cd8SNickeau $align = $callAttributes[2]; 159*37748cd8SNickeau $width = $callAttributes[3]; 160*37748cd8SNickeau $height = $callAttributes[4]; 161*37748cd8SNickeau $cache = $callAttributes[5]; 162*37748cd8SNickeau $linking = $callAttributes[6]; 163*37748cd8SNickeau 164*37748cd8SNickeau $tagAttributes = TagAttributes::createEmpty(); 165*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(TagAttributes::TITLE_KEY, $title); 166*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(self::ALIGN_KEY, $align); 167*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, $width); 168*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(Dimension::HEIGHT_KEY, $height); 169*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(CacheMedia::CACHE_KEY, $cache); 170*37748cd8SNickeau $tagAttributes->addComponentAttributeValue(self::LINKING_KEY, $linking); 171*37748cd8SNickeau 172*37748cd8SNickeau return self::createMediaLinkFromNonQualifiedPath($id, $tagAttributes); 173*37748cd8SNickeau 174*37748cd8SNickeau } 175*37748cd8SNickeau 176*37748cd8SNickeau /** 177*37748cd8SNickeau * A function to explicitly create an internal media from 178*37748cd8SNickeau * a call stack array (ie key string and value) that we get in the {@link SyntaxPlugin::render()} 179*37748cd8SNickeau * from the {@link MediaLink::toCallStackArray()} 180*37748cd8SNickeau * 181*37748cd8SNickeau * @param $attributes - the attributes created by the function {@link MediaLink::getParseAttributes()} 182*37748cd8SNickeau * @param $rev - the mtime 183*37748cd8SNickeau * @return MediaLink|RasterImageLink|SvgImageLink 184*37748cd8SNickeau */ 185*37748cd8SNickeau public static function createFromCallStackArray($attributes, $rev = null) 186*37748cd8SNickeau { 187*37748cd8SNickeau 188*37748cd8SNickeau if (!is_array($attributes)) { 189*37748cd8SNickeau // Debug for the key_exist below because of the following message: 190*37748cd8SNickeau // `PHP Warning: key_exists() expects parameter 2 to be array, array given` 191*37748cd8SNickeau LogUtility::msg("The `attributes` parameter is not an array. Value ($attributes)", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 192*37748cd8SNickeau } 193*37748cd8SNickeau 194*37748cd8SNickeau /** 195*37748cd8SNickeau * Media id are not cleaned 196*37748cd8SNickeau * They are always absolute ? 197*37748cd8SNickeau */ 198*37748cd8SNickeau if (!isset($attributes[DokuPath::PATH_ATTRIBUTE])) { 199*37748cd8SNickeau $path = "notfound"; 200*37748cd8SNickeau LogUtility::msg("A path attribute is mandatory when creating a media link and was not found in the attributes " . print_r($attributes, true), LogUtility::LVL_MSG_ERROR, self::CANONICAL); 201*37748cd8SNickeau } else { 202*37748cd8SNickeau $path = $attributes[DokuPath::PATH_ATTRIBUTE]; 203*37748cd8SNickeau unset($attributes[DokuPath::PATH_ATTRIBUTE]); 204*37748cd8SNickeau } 205*37748cd8SNickeau 206*37748cd8SNickeau 207*37748cd8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 208*37748cd8SNickeau 209*37748cd8SNickeau 210*37748cd8SNickeau return self::createMediaLinkFromNonQualifiedPath($path, $rev, $tagAttributes); 211*37748cd8SNickeau 212*37748cd8SNickeau } 213*37748cd8SNickeau 214*37748cd8SNickeau /** 215*37748cd8SNickeau * @param $match - the match of the renderer (just a shortcut) 216*37748cd8SNickeau * @return MediaLink 217*37748cd8SNickeau */ 218*37748cd8SNickeau public static function createFromRenderMatch($match) 219*37748cd8SNickeau { 220*37748cd8SNickeau 221*37748cd8SNickeau /** 222*37748cd8SNickeau * The parsing function {@link Doku_Handler_Parse_Media} has some flow / problem 223*37748cd8SNickeau * * It keeps the anchor only if there is no query string 224*37748cd8SNickeau * * It takes the first digit as the width (ie media.pdf?page=31 would have a width of 31) 225*37748cd8SNickeau * * `src` is not only the media path but may have a anchor 226*37748cd8SNickeau * We parse it then 227*37748cd8SNickeau */ 228*37748cd8SNickeau 229*37748cd8SNickeau 230*37748cd8SNickeau /** 231*37748cd8SNickeau * * Delete the opening and closing character 232*37748cd8SNickeau * * create the url and description 233*37748cd8SNickeau */ 234*37748cd8SNickeau $match = preg_replace(array('/^\{\{/', '/\}\}$/u'), '', $match); 235*37748cd8SNickeau $parts = explode('|', $match, 2); 236*37748cd8SNickeau $description = null; 237*37748cd8SNickeau $url = $parts[0]; 238*37748cd8SNickeau if (isset($parts[1])) { 239*37748cd8SNickeau $description = $parts[1]; 240*37748cd8SNickeau } 241*37748cd8SNickeau 242*37748cd8SNickeau /** 243*37748cd8SNickeau * Media Alignment 244*37748cd8SNickeau */ 245*37748cd8SNickeau $rightAlign = (bool)preg_match('/^ /', $url); 246*37748cd8SNickeau $leftAlign = (bool)preg_match('/ $/', $url); 247*37748cd8SNickeau $url = trim($url); 248*37748cd8SNickeau 249*37748cd8SNickeau // Logic = what's that ;)... 250*37748cd8SNickeau if ($leftAlign & $rightAlign) { 251*37748cd8SNickeau $align = 'center'; 252*37748cd8SNickeau } else if ($rightAlign) { 253*37748cd8SNickeau $align = 'right'; 254*37748cd8SNickeau } else if ($leftAlign) { 255*37748cd8SNickeau $align = 'left'; 256*37748cd8SNickeau } else { 257*37748cd8SNickeau $align = null; 258*37748cd8SNickeau } 259*37748cd8SNickeau 260*37748cd8SNickeau /** 261*37748cd8SNickeau * The combo attributes array 262*37748cd8SNickeau */ 263*37748cd8SNickeau $parsedAttributes = DokuwikiUrl::createFromUrl($url)->toArray(); 264*37748cd8SNickeau $path = $parsedAttributes[DokuPath::PATH_ATTRIBUTE]; 265*37748cd8SNickeau if (!isset($parsedAttributes[MediaLink::LINKING_KEY])) { 266*37748cd8SNickeau $parsedAttributes[MediaLink::LINKING_KEY] = PluginUtility::getConfValue(self::CONF_DEFAULT_LINKING, self::LINKING_DIRECT_VALUE); 267*37748cd8SNickeau } 268*37748cd8SNickeau 269*37748cd8SNickeau /** 270*37748cd8SNickeau * Media Type 271*37748cd8SNickeau */ 272*37748cd8SNickeau if (media_isexternal($path) || link_isinterwiki($path)) { 273*37748cd8SNickeau $mediaType = MediaLink::EXTERNAL_MEDIA_CALL_NAME; 274*37748cd8SNickeau } else { 275*37748cd8SNickeau $mediaType = MediaLink::INTERNAL_MEDIA_CALL_NAME; 276*37748cd8SNickeau } 277*37748cd8SNickeau 278*37748cd8SNickeau 279*37748cd8SNickeau /** 280*37748cd8SNickeau * src in dokuwiki is the path and the anchor if any 281*37748cd8SNickeau */ 282*37748cd8SNickeau $src = $path; 283*37748cd8SNickeau if (isset($parsedAttributes[DokuwikiUrl::ANCHOR_ATTRIBUTES]) != null) { 284*37748cd8SNickeau $src = $src . "#" . $parsedAttributes[DokuwikiUrl::ANCHOR_ATTRIBUTES]; 285*37748cd8SNickeau } 286*37748cd8SNickeau 287*37748cd8SNickeau /** 288*37748cd8SNickeau * To avoid clash with the combostrap component type 289*37748cd8SNickeau * ie this is also a ComboStrap attribute where we set the type of a SVG (icon, illustration, background) 290*37748cd8SNickeau * we store the media type (ie external/internal) in another key 291*37748cd8SNickeau * 292*37748cd8SNickeau * There is no need to repeat the attributes as the arrays are merged 293*37748cd8SNickeau * into on but this is also an informal code to show which attributes 294*37748cd8SNickeau * are only Dokuwiki Native 295*37748cd8SNickeau * 296*37748cd8SNickeau */ 297*37748cd8SNickeau $dokuwikiAttributes = array( 298*37748cd8SNickeau self::MEDIA_DOKUWIKI_TYPE => $mediaType, 299*37748cd8SNickeau self::DOKUWIKI_SRC => $src, 300*37748cd8SNickeau Dimension::WIDTH_KEY => $parsedAttributes[Dimension::WIDTH_KEY], 301*37748cd8SNickeau Dimension::HEIGHT_KEY => $parsedAttributes[Dimension::HEIGHT_KEY], 302*37748cd8SNickeau CacheMedia::CACHE_KEY => $parsedAttributes[CacheMedia::CACHE_KEY], 303*37748cd8SNickeau 'title' => $description, 304*37748cd8SNickeau MediaLink::ALIGN_KEY => $align, 305*37748cd8SNickeau MediaLink::LINKING_KEY => $parsedAttributes[MediaLink::LINKING_KEY], 306*37748cd8SNickeau ); 307*37748cd8SNickeau 308*37748cd8SNickeau /** 309*37748cd8SNickeau * Merge standard dokuwiki attributes and 310*37748cd8SNickeau * parsed attributes 311*37748cd8SNickeau */ 312*37748cd8SNickeau $mergedAttributes = PluginUtility::mergeAttributes($dokuwikiAttributes, $parsedAttributes); 313*37748cd8SNickeau 314*37748cd8SNickeau /** 315*37748cd8SNickeau * If this is an internal media, 316*37748cd8SNickeau * we are using our implementation 317*37748cd8SNickeau * and we have a change on attribute specification 318*37748cd8SNickeau */ 319*37748cd8SNickeau if ($mediaType == MediaLink::INTERNAL_MEDIA_CALL_NAME) { 320*37748cd8SNickeau 321*37748cd8SNickeau /** 322*37748cd8SNickeau * The align attribute on an image parse 323*37748cd8SNickeau * is a float right 324*37748cd8SNickeau * ComboStrap does a difference between a block right and a float right 325*37748cd8SNickeau */ 326*37748cd8SNickeau if ($mergedAttributes[self::ALIGN_KEY] === "right") { 327*37748cd8SNickeau unset($mergedAttributes[self::ALIGN_KEY]); 328*37748cd8SNickeau $mergedAttributes[FloatAttribute::FLOAT_KEY] = "right"; 329*37748cd8SNickeau } 330*37748cd8SNickeau 331*37748cd8SNickeau 332*37748cd8SNickeau } 333*37748cd8SNickeau 334*37748cd8SNickeau return self::createFromCallStackArray($mergedAttributes); 335*37748cd8SNickeau 336*37748cd8SNickeau } 337*37748cd8SNickeau 338*37748cd8SNickeau 339*37748cd8SNickeau public 340*37748cd8SNickeau function setLazyLoad($false) 341*37748cd8SNickeau { 342*37748cd8SNickeau $this->lazyLoad = $false; 343*37748cd8SNickeau } 344*37748cd8SNickeau 345*37748cd8SNickeau public 346*37748cd8SNickeau function getLazyLoad() 347*37748cd8SNickeau { 348*37748cd8SNickeau return $this->lazyLoad; 349*37748cd8SNickeau } 350*37748cd8SNickeau 351*37748cd8SNickeau 352*37748cd8SNickeau /** 353*37748cd8SNickeau * Create a media link from a unknown type path (ie relative or absolute) 354*37748cd8SNickeau * 355*37748cd8SNickeau * This function transforms the path to absolute against the actual namespace of the requested page ID if the 356*37748cd8SNickeau * path is relative. 357*37748cd8SNickeau * 358*37748cd8SNickeau * @param $nonQualifiedPath 359*37748cd8SNickeau * @param TagAttributes $tagAttributes 360*37748cd8SNickeau * @param string $rev 361*37748cd8SNickeau * @return MediaLink 362*37748cd8SNickeau */ 363*37748cd8SNickeau public 364*37748cd8SNickeau static function createMediaLinkFromNonQualifiedPath($nonQualifiedPath, $rev = null, $tagAttributes = null) 365*37748cd8SNickeau { 366*37748cd8SNickeau if (is_object($rev)) { 367*37748cd8SNickeau LogUtility::msg("rev should not be an object", LogUtility::LVL_MSG_ERROR, "support"); 368*37748cd8SNickeau } 369*37748cd8SNickeau if ($tagAttributes == null) { 370*37748cd8SNickeau $tagAttributes = TagAttributes::createEmpty(); 371*37748cd8SNickeau } else { 372*37748cd8SNickeau if (!($tagAttributes instanceof TagAttributes)) { 373*37748cd8SNickeau LogUtility::msg("TagAttributes is not an instance of Tag Attributes", LogUtility::LVL_MSG_ERROR, "support"); 374*37748cd8SNickeau } 375*37748cd8SNickeau } 376*37748cd8SNickeau 377*37748cd8SNickeau /** 378*37748cd8SNickeau * Resolution 379*37748cd8SNickeau */ 380*37748cd8SNickeau $qualifiedPath = $nonQualifiedPath; 381*37748cd8SNickeau if(!media_isexternal($qualifiedPath)) { 382*37748cd8SNickeau global $ID; 383*37748cd8SNickeau $qualifiedId = $nonQualifiedPath; 384*37748cd8SNickeau resolve_mediaid(getNS($ID), $qualifiedId, $exists); 385*37748cd8SNickeau $qualifiedPath = DokuPath::PATH_SEPARATOR . $qualifiedId; 386*37748cd8SNickeau } 387*37748cd8SNickeau 388*37748cd8SNickeau /** 389*37748cd8SNickeau * Processing 390*37748cd8SNickeau */ 391*37748cd8SNickeau $dokuPath = DokuPath::createMediaPathFromAbsolutePath($qualifiedPath, $rev); 392*37748cd8SNickeau if ($dokuPath->getExtension() == "svg") { 393*37748cd8SNickeau /** 394*37748cd8SNickeau * The mime type is set when uploading, not when 395*37748cd8SNickeau * viewing. 396*37748cd8SNickeau * Because they are internal image, the svg was already uploaded 397*37748cd8SNickeau * Therefore, no authorization scheme here 398*37748cd8SNickeau */ 399*37748cd8SNickeau $mime = "image/svg+xml"; 400*37748cd8SNickeau } else { 401*37748cd8SNickeau $mime = $dokuPath->getKnownMime(); 402*37748cd8SNickeau } 403*37748cd8SNickeau 404*37748cd8SNickeau if (substr($mime, 0, 5) == 'image') { 405*37748cd8SNickeau if (substr($mime, 6) == "svg+xml") { 406*37748cd8SNickeau // The require is here because Svg Image Link is child of Internal Media Link (extends) 407*37748cd8SNickeau require_once(__DIR__ . '/SvgImageLink.php'); 408*37748cd8SNickeau $internalMedia = new SvgImageLink($qualifiedPath, $tagAttributes, $rev); 409*37748cd8SNickeau } else { 410*37748cd8SNickeau // The require is here because Raster Image Link is child of Internal Media Link (extends) 411*37748cd8SNickeau require_once(__DIR__ . '/RasterImageLink.php'); 412*37748cd8SNickeau $internalMedia = new RasterImageLink($qualifiedPath, $tagAttributes); 413*37748cd8SNickeau } 414*37748cd8SNickeau } else { 415*37748cd8SNickeau if ($mime == false) { 416*37748cd8SNickeau LogUtility::msg("The mime type of the media ($nonQualifiedPath) is <a href=\"https://www.dokuwiki.org/mime\">unknown (not in the configuration file)</a>", LogUtility::LVL_MSG_ERROR, "support"); 417*37748cd8SNickeau $internalMedia = new RasterImageLink($qualifiedPath, $tagAttributes); 418*37748cd8SNickeau } else { 419*37748cd8SNickeau LogUtility::msg("The type ($mime) of media ($nonQualifiedPath) is not an image", LogUtility::LVL_MSG_DEBUG, "image"); 420*37748cd8SNickeau $internalMedia = new ThirdMediaLink($qualifiedPath, $tagAttributes); 421*37748cd8SNickeau } 422*37748cd8SNickeau } 423*37748cd8SNickeau 424*37748cd8SNickeau 425*37748cd8SNickeau return $internalMedia; 426*37748cd8SNickeau } 427*37748cd8SNickeau 428*37748cd8SNickeau 429*37748cd8SNickeau /** 430*37748cd8SNickeau * A function to set explicitly which array format 431*37748cd8SNickeau * is used in the returned data of a {@link SyntaxPlugin::handle()} 432*37748cd8SNickeau * (which ultimately is stored in the {@link CallStack) 433*37748cd8SNickeau * 434*37748cd8SNickeau * This is to make the difference with the {@link MediaLink::createFromIndexAttributes()} 435*37748cd8SNickeau * that is indexed by number (ie without property name) 436*37748cd8SNickeau * 437*37748cd8SNickeau * 438*37748cd8SNickeau * Return the same array than with the {@link self::parse()} method 439*37748cd8SNickeau * that is used in the {@link CallStack} 440*37748cd8SNickeau * 441*37748cd8SNickeau * @return array of key string and value 442*37748cd8SNickeau */ 443*37748cd8SNickeau public 444*37748cd8SNickeau function toCallStackArray() 445*37748cd8SNickeau { 446*37748cd8SNickeau /** 447*37748cd8SNickeau * Trying to stay inline with the dokuwiki key 448*37748cd8SNickeau * We use the 'src' attributes as id 449*37748cd8SNickeau * 450*37748cd8SNickeau * src is a path (not an id) 451*37748cd8SNickeau */ 452*37748cd8SNickeau $array = array( 453*37748cd8SNickeau DokuPath::PATH_ATTRIBUTE => $this->getPath() 454*37748cd8SNickeau ); 455*37748cd8SNickeau 456*37748cd8SNickeau 457*37748cd8SNickeau // Add the extra attribute 458*37748cd8SNickeau return array_merge($this->tagAttributes->toCallStackArray(), $array); 459*37748cd8SNickeau 460*37748cd8SNickeau 461*37748cd8SNickeau } 462*37748cd8SNickeau 463*37748cd8SNickeau 464*37748cd8SNickeau /** 465*37748cd8SNickeau * @return string the wiki syntax 466*37748cd8SNickeau */ 467*37748cd8SNickeau public 468*37748cd8SNickeau function getMarkupSyntax() 469*37748cd8SNickeau { 470*37748cd8SNickeau $descriptionPart = ""; 471*37748cd8SNickeau if ($this->tagAttributes->hasComponentAttribute(TagAttributes::TITLE_KEY)) { 472*37748cd8SNickeau $descriptionPart = "|" . $this->tagAttributes->getValue(TagAttributes::TITLE_KEY); 473*37748cd8SNickeau } 474*37748cd8SNickeau return '{{:' . $this->getId() . $descriptionPart . '}}'; 475*37748cd8SNickeau } 476*37748cd8SNickeau 477*37748cd8SNickeau 478*37748cd8SNickeau public 479*37748cd8SNickeau static function isInternalMediaSyntax($text) 480*37748cd8SNickeau { 481*37748cd8SNickeau return preg_match(' / ' . syntax_plugin_combo_media::MEDIA_PATTERN . ' / msSi', $text); 482*37748cd8SNickeau } 483*37748cd8SNickeau 484*37748cd8SNickeau 485*37748cd8SNickeau public 486*37748cd8SNickeau function getRequestedHeight() 487*37748cd8SNickeau { 488*37748cd8SNickeau return $this->tagAttributes->getValue(Dimension::HEIGHT_KEY); 489*37748cd8SNickeau } 490*37748cd8SNickeau 491*37748cd8SNickeau 492*37748cd8SNickeau /** 493*37748cd8SNickeau * The requested width 494*37748cd8SNickeau */ 495*37748cd8SNickeau public 496*37748cd8SNickeau function getRequestedWidth() 497*37748cd8SNickeau { 498*37748cd8SNickeau return $this->tagAttributes->getValue(Dimension::WIDTH_KEY); 499*37748cd8SNickeau } 500*37748cd8SNickeau 501*37748cd8SNickeau 502*37748cd8SNickeau public 503*37748cd8SNickeau function getCache() 504*37748cd8SNickeau { 505*37748cd8SNickeau return $this->tagAttributes->getValue(CacheMedia::CACHE_KEY); 506*37748cd8SNickeau } 507*37748cd8SNickeau 508*37748cd8SNickeau protected 509*37748cd8SNickeau function getTitle() 510*37748cd8SNickeau { 511*37748cd8SNickeau return $this->tagAttributes->getValue(TagAttributes::TITLE_KEY); 512*37748cd8SNickeau } 513*37748cd8SNickeau 514*37748cd8SNickeau 515*37748cd8SNickeau public 516*37748cd8SNickeau function __toString() 517*37748cd8SNickeau { 518*37748cd8SNickeau return $this->getId(); 519*37748cd8SNickeau } 520*37748cd8SNickeau 521*37748cd8SNickeau private 522*37748cd8SNickeau function getAlign() 523*37748cd8SNickeau { 524*37748cd8SNickeau return $this->getTagAttributes()->getComponentAttributeValue(self::ALIGN_KEY, null); 525*37748cd8SNickeau } 526*37748cd8SNickeau 527*37748cd8SNickeau private 528*37748cd8SNickeau function getLinking() 529*37748cd8SNickeau { 530*37748cd8SNickeau return $this->getTagAttributes()->getComponentAttributeValue(self::LINKING_KEY, null); 531*37748cd8SNickeau } 532*37748cd8SNickeau 533*37748cd8SNickeau 534*37748cd8SNickeau public 535*37748cd8SNickeau function &getTagAttributes() 536*37748cd8SNickeau { 537*37748cd8SNickeau return $this->tagAttributes; 538*37748cd8SNickeau } 539*37748cd8SNickeau 540*37748cd8SNickeau /** 541*37748cd8SNickeau * @return string - the HTML of the image inside a link if asked 542*37748cd8SNickeau */ 543*37748cd8SNickeau public 544*37748cd8SNickeau function renderMediaTagWithLink() 545*37748cd8SNickeau { 546*37748cd8SNickeau 547*37748cd8SNickeau /** 548*37748cd8SNickeau * Link to the media 549*37748cd8SNickeau * 550*37748cd8SNickeau */ 551*37748cd8SNickeau $imageLink = TagAttributes::createEmpty(); 552*37748cd8SNickeau // https://www.dokuwiki.org/config:target 553*37748cd8SNickeau global $conf; 554*37748cd8SNickeau $target = $conf['target']['media']; 555*37748cd8SNickeau $imageLink->addHtmlAttributeValueIfNotEmpty("target", $target); 556*37748cd8SNickeau if (!empty($target)) { 557*37748cd8SNickeau $imageLink->addHtmlAttributeValue("rel", 'noopener'); 558*37748cd8SNickeau } 559*37748cd8SNickeau 560*37748cd8SNickeau /** 561*37748cd8SNickeau * Do we add a link to the image ? 562*37748cd8SNickeau */ 563*37748cd8SNickeau $linking = $this->tagAttributes->getValue(self::LINKING_KEY); 564*37748cd8SNickeau switch ($linking) { 565*37748cd8SNickeau case self::LINKING_LINKONLY_VALUE: // show only a url 566*37748cd8SNickeau $src = ml( 567*37748cd8SNickeau $this->getId(), 568*37748cd8SNickeau array( 569*37748cd8SNickeau 'id' => $this->getId(), 570*37748cd8SNickeau 'cache' => $this->getCache(), 571*37748cd8SNickeau 'rev' => $this->getRevision() 572*37748cd8SNickeau ) 573*37748cd8SNickeau ); 574*37748cd8SNickeau $imageLink->addHtmlAttributeValue("href", $src); 575*37748cd8SNickeau $title = $this->getTitle(); 576*37748cd8SNickeau if (empty($title)) { 577*37748cd8SNickeau $title = $this->getBaseName(); 578*37748cd8SNickeau } 579*37748cd8SNickeau return $imageLink->toHtmlEnterTag("a") . $title . "</a>"; 580*37748cd8SNickeau case self::LINKING_NOLINK_VALUE: 581*37748cd8SNickeau return $this->renderMediaTag(); 582*37748cd8SNickeau default: 583*37748cd8SNickeau case self::LINKING_DIRECT_VALUE: 584*37748cd8SNickeau //directly to the image 585*37748cd8SNickeau $src = ml( 586*37748cd8SNickeau $this->getId(), 587*37748cd8SNickeau array( 588*37748cd8SNickeau 'id' => $this->getId(), 589*37748cd8SNickeau 'cache' => $this->getCache(), 590*37748cd8SNickeau 'rev' => $this->getRevision() 591*37748cd8SNickeau ), 592*37748cd8SNickeau true 593*37748cd8SNickeau ); 594*37748cd8SNickeau $imageLink->addHtmlAttributeValue("href", $src); 595*37748cd8SNickeau return $imageLink->toHtmlEnterTag("a") . 596*37748cd8SNickeau $this->renderMediaTag() . 597*37748cd8SNickeau "</a>"; 598*37748cd8SNickeau 599*37748cd8SNickeau case self::LINKING_DETAILS_VALUE: 600*37748cd8SNickeau //go to the details media viewer 601*37748cd8SNickeau $src = ml( 602*37748cd8SNickeau $this->getId(), 603*37748cd8SNickeau array( 604*37748cd8SNickeau 'id' => $this->getId(), 605*37748cd8SNickeau 'cache' => $this->getCache(), 606*37748cd8SNickeau 'rev' => $this->getRevision() 607*37748cd8SNickeau ), 608*37748cd8SNickeau false 609*37748cd8SNickeau ); 610*37748cd8SNickeau $imageLink->addHtmlAttributeValue("href", $src); 611*37748cd8SNickeau return $imageLink->toHtmlEnterTag("a") . 612*37748cd8SNickeau $this->renderMediaTag() . 613*37748cd8SNickeau "</a>"; 614*37748cd8SNickeau 615*37748cd8SNickeau } 616*37748cd8SNickeau 617*37748cd8SNickeau 618*37748cd8SNickeau } 619*37748cd8SNickeau 620*37748cd8SNickeau /** 621*37748cd8SNickeau * @param $imgTagHeight 622*37748cd8SNickeau * @param $imgTagWidth 623*37748cd8SNickeau * @return float|mixed 624*37748cd8SNickeau */ 625*37748cd8SNickeau public 626*37748cd8SNickeau function checkWidthAndHeightRatioAndReturnTheGoodValue($imgTagWidth, $imgTagHeight) 627*37748cd8SNickeau { 628*37748cd8SNickeau /** 629*37748cd8SNickeau * Check of height and width dimension 630*37748cd8SNickeau * as specified here 631*37748cd8SNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 632*37748cd8SNickeau */ 633*37748cd8SNickeau $targetRatio = $this->getTargetRatio(); 634*37748cd8SNickeau if (!( 635*37748cd8SNickeau $imgTagHeight * $targetRatio >= $imgTagWidth - 0.5 636*37748cd8SNickeau && 637*37748cd8SNickeau $imgTagHeight * $targetRatio <= $imgTagWidth + 0.5 638*37748cd8SNickeau )) { 639*37748cd8SNickeau // check the second statement 640*37748cd8SNickeau if (!( 641*37748cd8SNickeau $imgTagWidth / $targetRatio >= $imgTagHeight - 0.5 642*37748cd8SNickeau && 643*37748cd8SNickeau $imgTagWidth / $targetRatio <= $imgTagHeight + 0.5 644*37748cd8SNickeau )) { 645*37748cd8SNickeau $requestedHeight = $this->getRequestedHeight(); 646*37748cd8SNickeau $requestedWidth = $this->getRequestedWidth(); 647*37748cd8SNickeau if ( 648*37748cd8SNickeau !empty($requestedHeight) 649*37748cd8SNickeau && !empty($requestedWidth) 650*37748cd8SNickeau ) { 651*37748cd8SNickeau /** 652*37748cd8SNickeau * The user has asked for a width and height 653*37748cd8SNickeau */ 654*37748cd8SNickeau $imgTagWidth = round($imgTagHeight * $targetRatio); 655*37748cd8SNickeau LogUtility::msg("The width ($requestedWidth) and height ($requestedHeight) specified on the image ($this) does not follow the natural ratio as <a href=\"https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height\">required by HTML</a>. The width was then set to ($imgTagWidth).", LogUtility::LVL_MSG_INFO, self::CANONICAL); 656*37748cd8SNickeau } else { 657*37748cd8SNickeau /** 658*37748cd8SNickeau * Programmatic error from the developer 659*37748cd8SNickeau */ 660*37748cd8SNickeau $imgTagRatio = $imgTagWidth / $imgTagHeight; 661*37748cd8SNickeau LogUtility::msg("Internal Error: The width ($imgTagWidth) and height ($imgTagHeight) calculated for the image ($this) does not pass the ratio test. They have a ratio of ($imgTagRatio) while the natural dimension ratio is ($targetRatio)"); 662*37748cd8SNickeau } 663*37748cd8SNickeau } 664*37748cd8SNickeau } 665*37748cd8SNickeau return $imgTagWidth; 666*37748cd8SNickeau } 667*37748cd8SNickeau 668*37748cd8SNickeau /** 669*37748cd8SNickeau * Target ratio as explained here 670*37748cd8SNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 671*37748cd8SNickeau * @return float|int|false 672*37748cd8SNickeau * false if the image is not supported 673*37748cd8SNickeau * 674*37748cd8SNickeau * It's needed for an img tag to set the img `width` and `height` that pass the 675*37748cd8SNickeau * {@link MediaLink::checkWidthAndHeightRatioAndReturnTheGoodValue() check} 676*37748cd8SNickeau * to avoid layout shift 677*37748cd8SNickeau * 678*37748cd8SNickeau */ 679*37748cd8SNickeau protected function getTargetRatio() 680*37748cd8SNickeau { 681*37748cd8SNickeau if ($this->getMediaHeight() == null || $this->getMediaWidth() == null) { 682*37748cd8SNickeau return false; 683*37748cd8SNickeau } else { 684*37748cd8SNickeau return $this->getMediaWidth() / $this->getMediaHeight(); 685*37748cd8SNickeau } 686*37748cd8SNickeau } 687*37748cd8SNickeau 688*37748cd8SNickeau /** 689*37748cd8SNickeau * Return the height that the image should take on the screen 690*37748cd8SNickeau * for the specified size 691*37748cd8SNickeau * 692*37748cd8SNickeau * @param null $localRequestedWidth - the width to derive the height from (in case the image is created for responsive lazy loading) 693*37748cd8SNickeau * if not specified, the requested width and if not specified the intrinsic width 694*37748cd8SNickeau * @return int the height value attribute in a img 695*37748cd8SNickeau */ 696*37748cd8SNickeau public 697*37748cd8SNickeau function getImgTagHeightValue($localRequestedWidth = null) 698*37748cd8SNickeau { 699*37748cd8SNickeau 700*37748cd8SNickeau /** 701*37748cd8SNickeau * Cropping is not yet supported. 702*37748cd8SNickeau */ 703*37748cd8SNickeau $requestedHeight = $this->getRequestedHeight(); 704*37748cd8SNickeau $requestedWidth = $this->getRequestedWidth(); 705*37748cd8SNickeau if ( 706*37748cd8SNickeau $requestedHeight != null 707*37748cd8SNickeau && $requestedHeight != 0 708*37748cd8SNickeau && $requestedWidth != null 709*37748cd8SNickeau && $requestedWidth != 0 710*37748cd8SNickeau ) { 711*37748cd8SNickeau global $ID; 712*37748cd8SNickeau if ($ID != "wiki:syntax") { 713*37748cd8SNickeau /** 714*37748cd8SNickeau * Cropping 715*37748cd8SNickeau */ 716*37748cd8SNickeau LogUtility::msg("The width and height has been set on the image ($this) but we don't support yet cropping. Set only the width or the height (0x250)", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 717*37748cd8SNickeau } 718*37748cd8SNickeau } 719*37748cd8SNickeau 720*37748cd8SNickeau /** 721*37748cd8SNickeau * If resize by height, the img tag height is the requested height 722*37748cd8SNickeau */ 723*37748cd8SNickeau if ($localRequestedWidth == null) { 724*37748cd8SNickeau if ($requestedHeight != null) { 725*37748cd8SNickeau return $requestedHeight; 726*37748cd8SNickeau } else { 727*37748cd8SNickeau $localRequestedWidth = $this->getRequestedWidth(); 728*37748cd8SNickeau if (empty($localRequestedWidth)) { 729*37748cd8SNickeau $localRequestedWidth = $this->getMediaWidth(); 730*37748cd8SNickeau } 731*37748cd8SNickeau } 732*37748cd8SNickeau } 733*37748cd8SNickeau 734*37748cd8SNickeau /** 735*37748cd8SNickeau * Computation 736*37748cd8SNickeau */ 737*37748cd8SNickeau $computedHeight = $this->getRequestedHeight(); 738*37748cd8SNickeau $targetRatio = $this->getTargetRatio(); 739*37748cd8SNickeau if ($targetRatio !== false) { 740*37748cd8SNickeau 741*37748cd8SNickeau /** 742*37748cd8SNickeau * Scale the height by target ratio 743*37748cd8SNickeau */ 744*37748cd8SNickeau $computedHeight = $localRequestedWidth / $this->getTargetRatio(); 745*37748cd8SNickeau 746*37748cd8SNickeau /** 747*37748cd8SNickeau * Check 748*37748cd8SNickeau */ 749*37748cd8SNickeau if ($requestedHeight != null) { 750*37748cd8SNickeau if ($requestedHeight < $computedHeight) { 751*37748cd8SNickeau LogUtility::msg("The computed height cannot be greater than the requested height"); 752*37748cd8SNickeau } 753*37748cd8SNickeau } 754*37748cd8SNickeau 755*37748cd8SNickeau } 756*37748cd8SNickeau 757*37748cd8SNickeau 758*37748cd8SNickeau /** 759*37748cd8SNickeau * Rounding to integer 760*37748cd8SNickeau * The fetch.php file takes int as value for width and height 761*37748cd8SNickeau * making a rounding if we pass a double (such as 37.5) 762*37748cd8SNickeau * This is important because the security token is based on width and height 763*37748cd8SNickeau * and therefore the fetch will failed 764*37748cd8SNickeau * 765*37748cd8SNickeau * And not directly {@link intval} because it will make from 3.6, 3 and not 4 766*37748cd8SNickeau */ 767*37748cd8SNickeau return intval(round($computedHeight)); 768*37748cd8SNickeau 769*37748cd8SNickeau } 770*37748cd8SNickeau 771*37748cd8SNickeau /** 772*37748cd8SNickeau * @return int - the width value attribute in a img (in CSS pixel that the image should takes) 773*37748cd8SNickeau */ 774*37748cd8SNickeau public 775*37748cd8SNickeau function getImgTagWidthValue() 776*37748cd8SNickeau { 777*37748cd8SNickeau $linkWidth = $this->getRequestedWidth(); 778*37748cd8SNickeau if (empty($linkWidth)) { 779*37748cd8SNickeau if (empty($this->getRequestedHeight())) { 780*37748cd8SNickeau 781*37748cd8SNickeau $linkWidth = $this->getMediaWidth(); 782*37748cd8SNickeau 783*37748cd8SNickeau } else { 784*37748cd8SNickeau 785*37748cd8SNickeau // Height is not empty 786*37748cd8SNickeau // We derive the width from it 787*37748cd8SNickeau if ($this->getMediaHeight() != 0 788*37748cd8SNickeau && !empty($this->getMediaHeight()) 789*37748cd8SNickeau && !empty($this->getMediaWidth()) 790*37748cd8SNickeau ) { 791*37748cd8SNickeau $linkWidth = $this->getMediaWidth() * ($this->getRequestedHeight() / $this->getMediaHeight()); 792*37748cd8SNickeau } 793*37748cd8SNickeau 794*37748cd8SNickeau } 795*37748cd8SNickeau } 796*37748cd8SNickeau /** 797*37748cd8SNickeau * Rounding to integer 798*37748cd8SNickeau * The fetch.php file takes int as value for width and height 799*37748cd8SNickeau * making a rounding if we pass a double (such as 37.5) 800*37748cd8SNickeau * This is important because the security token is based on width and height 801*37748cd8SNickeau * and therefore the fetch will failed 802*37748cd8SNickeau * 803*37748cd8SNickeau * And this is also ask by the specification 804*37748cd8SNickeau * a non-null positive integer 805*37748cd8SNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 806*37748cd8SNickeau * 807*37748cd8SNickeau * And not {@link intval} because it will make from 3.6, 3 and not 4 808*37748cd8SNickeau */ 809*37748cd8SNickeau return intval(round($linkWidth)); 810*37748cd8SNickeau } 811*37748cd8SNickeau 812*37748cd8SNickeau /** 813*37748cd8SNickeau * @return string - the HTML of the image 814*37748cd8SNickeau */ 815*37748cd8SNickeau public abstract function renderMediaTag(); 816*37748cd8SNickeau 817*37748cd8SNickeau /** 818*37748cd8SNickeau * The Url 819*37748cd8SNickeau * @return mixed 820*37748cd8SNickeau */ 821*37748cd8SNickeau public abstract function getAbsoluteUrl(); 822*37748cd8SNickeau 823*37748cd8SNickeau /** 824*37748cd8SNickeau * For a raster image, the internal width 825*37748cd8SNickeau * for a svg, the defined viewBox 826*37748cd8SNickeau * 827*37748cd8SNickeau * This is needed to calculate the {@link MediaLink::getTargetRatio() target ratio} 828*37748cd8SNickeau * and pass them to the img tag to avoid layout shift 829*37748cd8SNickeau * 830*37748cd8SNickeau * @return mixed 831*37748cd8SNickeau */ 832*37748cd8SNickeau public abstract function getMediaWidth(); 833*37748cd8SNickeau 834*37748cd8SNickeau /** 835*37748cd8SNickeau * For a raster image, the internal height 836*37748cd8SNickeau * for a svg, the defined `viewBox` value 837*37748cd8SNickeau * 838*37748cd8SNickeau * This is needed to calculate the {@link MediaLink::getTargetRatio() target ratio} 839*37748cd8SNickeau * and pass them to the img tag to avoid layout shift 840*37748cd8SNickeau * 841*37748cd8SNickeau * @return mixed 842*37748cd8SNickeau */ 843*37748cd8SNickeau public abstract function getMediaHeight(); 844*37748cd8SNickeau 845*37748cd8SNickeau 846*37748cd8SNickeau} 847