1<?php 2 3namespace ComboStrap; 4 5use ComboStrap\Meta\Field\AncestorImage; 6use ComboStrap\Meta\Field\FeaturedImage; 7use ComboStrap\TagAttribute\StyleAttribute; 8use syntax_plugin_combo_iterator; 9 10 11class PageImageTag 12{ 13 14 public const CANONICAL = PageImageTag::TAG; 15 public const VIGNETTE_TYPE = "vignette"; 16 public const DEFAULT_ATTRIBUTE = "default"; 17 public const MARKUP = "page-image"; 18 public const LOGO_TYPE = "logo"; 19 public const DEFAULT_ORDER = [ 20 PageImageTag::FEATURED, 21 PageImageTag::FIRST_TYPE, 22 PageImageTag::ANCESTOR_TYPE, 23 PageImageTag::ICON_TYPE, 24 PageImageTag::VIGNETTE_TYPE, 25 PageImageTag::LOGO_TYPE 26 ]; 27 public const ANCESTOR_TYPE = "ancestor"; 28 public const TAG = "pageimage"; 29 public const FEATURED = "featured"; 30 public const ICON_TYPE = "icon"; 31 public const NONE_TYPE = "none"; 32 public const FIRST_TYPE = "first"; 33 34 public const TYPES = [ 35 PageImageTag::FEATURED, 36 PageImageTag::FIRST_TYPE, 37 PageImageTag::VIGNETTE_TYPE, 38 PageImageTag::ANCESTOR_TYPE, 39 PageImageTag::LOGO_TYPE, 40 PageImageTag::ICON_TYPE 41 ]; 42 const DEFAULT_ZOOM = -4; 43 44 45 /** 46 * Because the pageimage can also be used 47 * in a template 48 * 49 * The calculation are done in the {@link syntax_plugin_combo_pageimage::render render function} 50 * 51 */ 52 public static function handle($tagAttributes, $handler): array 53 { 54 55 /** 56 * Context 57 */ 58 $callStack = CallStack::createFromHandler($handler); 59 $context = PageImageTag::TAG; 60 $parent = $callStack->moveToParent(); 61 if ($parent !== false) { 62 $context = $parent->getTagName(); 63 } 64 65 return array( 66 PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray(), 67 PluginUtility::CONTEXT => $context 68 ); 69 } 70 71 public static function render(TagAttributes $tagAttributes, array $data) 72 { 73 74 75 /** 76 * Image selection 77 */ 78 $path = syntax_plugin_combo_iterator::getContextPathForComponentThatMayBeInFragment($tagAttributes); 79 $contextPage = MarkupPath::createPageFromPathObject($path); 80 81 /** 82 * Zoom applied only to icon 83 * but we get and **delete** it 84 * because it's not a standard html attribute 85 */ 86 $zoom = $tagAttributes->getValueAndRemoveIfPresent(Dimension::ZOOM_ATTRIBUTE, self::DEFAULT_ZOOM); 87 88 /** 89 * Image Order of precedence 90 */ 91 $order = self::getOrderOfPreference($tagAttributes); 92 $imageFetcher = null; 93 foreach ($order as $pageImageProcessing) { 94 switch ($pageImageProcessing) { 95 case PageImageTag::FEATURED: 96 try { 97 $imagePath = FeaturedImage::createFromResourcePage($contextPage)->getValue(); 98 } catch (ExceptionNotFound $e) { 99 // ok 100 continue 2; 101 } 102 try { 103 $imageFetcher = IFetcherLocalImage::createImageFetchFromPath($imagePath); 104 } catch (ExceptionNotExists|ExceptionBadArgument|ExceptionBadSyntax $e) { 105 LogUtility::warning("Error while creating the fetcher for the feature image ($imagePath) and the page ($contextPage). Error: {$e->getMessage()}", self::CANONICAL, $e); 106 } 107 break; 108 case PageImageTag::ANCESTOR_TYPE: 109 case "parent": // old 110 try { 111 $ancestor = AncestorImage::createFromResourcePage($contextPage)->getValue(); 112 } catch (ExceptionNotFound $e) { 113 continue 2; 114 } 115 try { 116 $imageFetcher = IFetcherLocalImage::createImageFetchFromPath($ancestor); 117 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotExists $e) { 118 LogUtility::warning("Error while creating the ancestor image handler for the image ($ancestor) and the page ($contextPage). Error: {$e->getMessage()}", self::CANONICAL, $e); 119 } 120 break; 121 case PageImageTag::ICON_TYPE: 122 try { 123 $icon = FeaturedIcon::createForPage($contextPage)->getValueOrDefault(); 124 } catch (ExceptionNotFound $e) { 125 continue 2; 126 } 127 128 $width = $tagAttributes->getValueAndRemoveIfPresent(Dimension::WIDTH_KEY); 129 $height = $tagAttributes->getValueAndRemoveIfPresent(Dimension::HEIGHT_KEY); 130 $ratio = $tagAttributes->getValueAndRemoveIfPresent(Dimension::RATIO_ATTRIBUTE); 131 if ($width === null && $height !== null && $ratio === null) { 132 $width = $height; 133 } 134 if ($width !== null && $height !== null && $ratio === null) { 135 $height = $width; 136 } 137 $imageFetcher = FetcherSvg::createSvgFromPath($icon) 138 ->setRequestedZoom($zoom); 139 140 if ($ratio !== null) { 141 try { 142 $imageFetcher->setRequestedAspectRatio($ratio); 143 } catch (ExceptionBadSyntax $e) { 144 LogUtility::error("The ratio value ($ratio) is not a valid ratio for the icon image ($icon)"); 145 } 146 } 147 if ($width !== null) { 148 try { 149 $width = ConditionalLength::createFromString($width)->toPixelNumber(); 150 $imageFetcher->setRequestedWidth($width); 151 } catch (ExceptionBadArgument $e) { 152 LogUtility::warning("The width ($width) is not a valid length and was discarded", self::CANONICAL, $e); 153 } 154 } 155 if ($height !== null) { 156 try { 157 $height = ConditionalLength::createFromString($height)->toPixelNumber(); 158 $imageFetcher->setRequestedHeight($height); 159 } catch (ExceptionBadArgument $e) { 160 LogUtility::warning("The height ($height) is not a valid length and was discarded", self::CANONICAL, $e); 161 } 162 } 163 164 break; 165 case PageImageTag::FIRST_TYPE: 166 167 try { 168 $firstImagePath = FirstImage::createForPage($contextPage)->getValue(); 169 } catch (ExceptionNotFound $e) { 170 continue 2; 171 } 172 173 try { 174 $imageFetcher = IFetcherLocalImage::createImageFetchFromPath($firstImagePath); 175 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotExists $e) { 176 LogUtility::warning("Error while creating the first image handler for the image ($firstImagePath) and the page ($contextPage). Error: {$e->getMessage()}", self::CANONICAL, $e); 177 } 178 break; 179 case PageImageTag::VIGNETTE_TYPE: 180 try { 181 $imageFetcher = FetcherVignette::createForPage($contextPage); 182 } catch (ExceptionNotFound|ExceptionBadArgument $e) { 183 LogUtility::warning("Error while creating the vignette for the page ($contextPage). Error: {$e->getMessage()}", self::CANONICAL, $e); 184 } 185 break; 186 case PageImageTag::LOGO_TYPE: 187 try { 188 $imageFetcher = FetcherSvg::createSvgFromPath(Site::getLogoAsSvgImage()); 189 } catch (ExceptionNotFound $e) { 190 LogUtility::info("No page image could be find for the page ($path)", PageImageTag::CANONICAL); 191 } 192 break; 193 case PageImageTag::NONE_TYPE: 194 return false; 195 default: 196 LogUtility::error("The image ($pageImageProcessing) is an unknown page image type", PageImageTag::CANONICAL); 197 continue 2; 198 } 199 if ($imageFetcher !== null) { 200 $tagAttributes->addClassName(StyleAttribute::addComboStrapSuffix($pageImageProcessing)); 201 break; 202 } 203 } 204 205 if ($imageFetcher === null) { 206 return false; 207 } 208 209 /** 210 * Final building 211 */ 212 try { 213 $imageFetcher->buildFromTagAttributes($tagAttributes); 214 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionCompile $e) { 215 LogUtility::error("The image could not be build. Error: {$e->getMessage()}", PageImageTag::CANONICAL, $e); 216 } 217 218 /** 219 * Svg 220 */ 221 if ($imageFetcher instanceof FetcherSvg) { 222 223 /** 224 * This is an illustration image 225 * Used by svg to color by default with the primary color for instance 226 * 227 * (Note that because we use icon as page-image type, 228 * the buildFromTagAttributes would have set it to icon) 229 */ 230 $imageFetcher->setRequestedType(FetcherSvg::ILLUSTRATION_TYPE); 231 232 /** 233 * When the width requested is small, no zoom out 234 */ 235 try { 236 $requestedWidth = $imageFetcher->getRequestedWidth(); 237 try { 238 $pixelWidth = ConditionalLength::createFromString($requestedWidth)->toPixelNumber(); 239 if ($pixelWidth < 30) { 240 /** 241 * Icon rendering 242 */ 243 $imageFetcher 244 ->setRequestedZoom(1) 245 ->setRequestedType(FetcherSvg::ICON_TYPE); 246 247 } 248 } catch (ExceptionCompile $e) { 249 LogUtility::msg("The width value ($requestedWidth) could not be translated in pixel value. Error: {$e->getMessage()}"); 250 } 251 } catch (ExceptionNotFound $e) { 252 // no width 253 } 254 255 } 256 257 258 /** 259 * Img/Svg Tag 260 * 261 * Used as an illustration in a card 262 * If the image is too small, we allow that it will stretch 263 * to take the whole space 264 */ 265 if ($data[PluginUtility::CONTEXT] === CardTag::CARD_TAG) { 266 $tagAttributes->addStyleDeclarationIfNotSet("max-width", "100%"); 267 $tagAttributes->addStyleDeclarationIfNotSet("max-height", "unset"); 268 } 269 270 271 $tagAttributes->setType(self::MARKUP); 272 273 try { 274 return MediaMarkup::createFromFetcher($imageFetcher) 275 ->buildFromTagAttributes($tagAttributes) 276 ->toHtml(); 277 } catch (ExceptionCompile $e) { 278 $message = "Error while rendering the page image: {$e->getMessage()}"; 279 LogUtility::error($message, self::CANONICAL, $e); 280 return $message; 281 } 282 283 } 284 285 public static function getDefaultAttributes(): array 286 { 287 return [ 288 MediaMarkup::LINKING_KEY => MediaMarkup::LINKING_NOLINK_VALUE, 289 TagAttributes::TYPE_KEY => self::FEATURED 290 ]; 291 } 292 293 private static function getOrderOfPreference(TagAttributes $tagAttributes): array 294 { 295 // the type is first 296 $type = $tagAttributes->getType(); 297 $orderOfPreference[] = $type; 298 // then the default one 299 $default = $tagAttributes->getValueAndRemoveIfPresent(PageImageTag::DEFAULT_ATTRIBUTE); 300 if ($default === null) { 301 $defaultOrderOfPreference = PageImageTag::DEFAULT_ORDER; 302 } else { 303 $defaultOrderOfPreference = explode("|", $default); 304 } 305 foreach ($defaultOrderOfPreference as $defaultImageOrder) { 306 if ($defaultImageOrder === $type) { 307 continue; 308 } 309 $orderOfPreference[] = $defaultImageOrder; 310 } 311 return $orderOfPreference; 312 } 313 314} 315