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