1<?php 2 3 4namespace ComboStrap\TagAttribute; 5 6 7use ComboStrap\ColorRgb; 8use ComboStrap\ExceptionBadArgument; 9use ComboStrap\ExceptionCompile; 10use ComboStrap\ExceptionInternal; 11use ComboStrap\ExceptionNotFound; 12use ComboStrap\FetcherSvg; 13use ComboStrap\LazyLoad; 14use ComboStrap\LogUtility; 15use ComboStrap\MediaMarkup; 16use ComboStrap\Opacity; 17use ComboStrap\PluginUtility; 18use ComboStrap\StringUtility; 19use ComboStrap\TagAttributes; 20 21/** 22 * Process background attribute 23 */ 24class BackgroundAttribute 25{ 26 /** 27 * Background Logical attribute / Public 28 */ 29 const BACKGROUND_COLOR = 'background-color'; 30 const BACKGROUND_FILL = "fill"; 31 32 33 /** 34 * CSS attribute / not public 35 */ 36 const BACKGROUND_IMAGE = 'background-image'; 37 const BACKGROUND_SIZE = "background-size"; 38 const BACKGROUND_REPEAT = "background-repeat"; 39 const BACKGROUND_POSITION = "background-position"; 40 41 42 /** 43 * The page canonical of the documentation 44 */ 45 const CANONICAL = "background"; 46 /** 47 * A component attributes to store backgrounds 48 */ 49 const BACKGROUNDS = "backgrounds"; 50 51 /** 52 * Pattern css 53 * https://bansal.io/pattern-css 54 */ 55 const PATTERN_ATTRIBUTE = "pattern"; 56 const PATTERN_CSS_SNIPPET_ID = "pattern.css"; 57 const PATTERN_CSS_SIZE = ["sm", "md", "lg", "xl"]; 58 const PATTERN_NAMES = ['checks', 'grid', 'dots', 'cross-dots', 'diagonal-lines', 'horizontal-lines', 'vertical-lines', 'diagonal-stripes', 'horizontal-stripes', 'vertical-stripes', 'triangles', 'zigzag']; 59 const PATTERN_CSS_CLASS_PREFIX = "pattern"; 60 const PATTERN_COLOR_ATTRIBUTE = "pattern-color"; 61 62 63 public static function processBackgroundAttributes(TagAttributes &$tagAttributes) 64 { 65 66 /** 67 * Backgrounds set with the {@link \syntax_plugin_combo_background} component 68 */ 69 if ($tagAttributes->hasComponentAttribute(self::BACKGROUNDS)) { 70 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::CANONICAL); 71 $backgrounds = $tagAttributes->getValueAndRemove(self::BACKGROUNDS); 72 switch (sizeof($backgrounds)) { 73 case 1: 74 // Only one background was specified 75 $background = $backgrounds[0]; 76 if ( 77 /** 78 * We need to create a background node 79 * if we transform or 80 * use a CSS pattern (because it use the text color as one of painting color) 81 */ 82 !isset($background[TagAttributes::TRANSFORM]) && 83 !isset($background[self::PATTERN_ATTRIBUTE]) 84 ) { 85 /** 86 * For readability, 87 * we put the background on the parent node 88 * because there is only one background 89 */ 90 $backgroundImage = $background[self::BACKGROUND_IMAGE] ?? null; 91 if ($backgroundImage !== null) { 92 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_IMAGE, $backgroundImage); 93 } 94 $backgroundColor = $background[self::BACKGROUND_COLOR] ?? null; 95 if ($backgroundColor !== null) { 96 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_COLOR, $backgroundColor); 97 } 98 $opacityAttribute = $background[Opacity::OPACITY_ATTRIBUTE] ?? null; 99 if ($opacityAttribute !== null) { 100 $tagAttributes->addComponentAttributeValueIfNotEmpty(Opacity::OPACITY_ATTRIBUTE, $opacityAttribute); 101 } 102 $backgroundPosition = $background[self::BACKGROUND_POSITION] ?? null; 103 if ($backgroundPosition !== null) { 104 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_POSITION, $backgroundPosition); 105 } 106 $backgroundFill = $background[self::BACKGROUND_FILL] ?? null; 107 if ($backgroundFill !== null) { 108 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_FILL, $backgroundFill); 109 } 110 } else { 111 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 112 $backgroundTagAttribute->addClassName(self::CANONICAL); 113 $backgroundHTML = "<div class=\"backgrounds\">" . 114 $backgroundTagAttribute->toHtmlEnterTag("div") . 115 "</div>" . 116 "</div>"; 117 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 118 } 119 break; 120 default: 121 /** 122 * More than one background 123 * This backgrounds should have been set on the backgrounds 124 */ 125 $backgroundHTML = ""; 126 foreach ($backgrounds as $background) { 127 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 128 $backgroundTagAttribute->addClassName(self::CANONICAL); 129 $backgroundHTMLEnter = $backgroundTagAttribute->toHtmlEnterTag("div"); 130 $backgroundHTML .= $backgroundHTMLEnter . "</div>"; 131 } 132 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 133 break; 134 } 135 } 136 137 /** 138 * Background-image attribute 139 */ 140 $backgroundImageStyleValue = ""; 141 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_IMAGE)) { 142 $backgroundImageValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 143 if (is_string($backgroundImageValue)) { 144 /** 145 * Image background is set by the user 146 */ 147 $backgroundImageStyleValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 148 149 } else { 150 151 if (is_array($backgroundImageValue)) { 152 153 /** 154 * Background-fill for background image 155 */ 156 $backgroundFill = $tagAttributes->getValueAndRemove(self::BACKGROUND_FILL, "cover"); 157 switch ($backgroundFill) { 158 case "cover": 159 // it makes the background responsive 160 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_SIZE, $backgroundFill); 161 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "no-repeat"); 162 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_POSITION, "center center"); 163 164 /** 165 * The type of image is important for the processing of SVG 166 */ 167 $backgroundImageValue[TagAttributes::TYPE_KEY] = FetcherSvg::ILLUSTRATION_TYPE; 168 break; 169 case "tile": 170 // background size is then "auto" (ie repeat), the default 171 // background position is not needed (the tile start on the left top corner) 172 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "repeat"); 173 174 /** 175 * The type of image is important for the processing of SVG 176 * A tile needs to have a width and a height 177 */ 178 $backgroundImageValue[TagAttributes::TYPE_KEY] = FetcherSvg::TILE_TYPE; 179 break; 180 case "css": 181 // custom, set by the user in own css stylesheet, nothing to do 182 break; 183 default: 184 LogUtility::msg("The background `fill` attribute ($backgroundFill) is unknown. If you want to take over the filling via css, set the `fill` value to `css`.", self::CANONICAL); 185 break; 186 } 187 188 189 try { 190 $mediaMarkup = MediaMarkup::createFromCallStackArray($backgroundImageValue) 191 ->setLinking(MediaMarkup::LINKING_NOLINK_VALUE); 192 } catch (ExceptionCompile $e) { 193 LogUtility::error("We could not create a background image. Error: {$e->getMessage()}"); 194 return; 195 } 196 197 try { 198 $imageFetcher = $mediaMarkup->getFetcher(); 199 } catch (ExceptionBadArgument|ExceptionInternal|ExceptionNotFound $e) { 200 LogUtility::internalError("The fetcher for the background image ($mediaMarkup) returns an error", self::CANONICAL, $e); 201 return; 202 } 203 $mime = $imageFetcher->getMime(); 204 if (!$mime->isImage()) { 205 LogUtility::error("The background image ($mediaMarkup) is not an image but a $mime", self::CANONICAL); 206 return; 207 } 208 $backgroundImageStyleValue = "url(" . $imageFetcher->getFetchUrl()->toAbsoluteUrl()->toCssString() . ")"; 209 210 } else { 211 LogUtility::msg("Internal Error: The background image value ($backgroundImageValue) is not a string nor an array", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 212 } 213 214 } 215 } 216 if (!empty($backgroundImageStyleValue)) { 217 if ($tagAttributes->hasComponentAttribute(Opacity::OPACITY_ATTRIBUTE)) { 218 $opacity = $tagAttributes->getValueAndRemove(Opacity::OPACITY_ATTRIBUTE); 219 $finalOpacity = 1 - $opacity; 220 /** 221 * In the argument of linear-gradient, we don't use `0 100%` to apply the 222 * same image everywhere 223 * 224 * because the validator https://validator.w3.org/ would complain with 225 * ` 226 * CSS: background-image: too few values for the property linear-gradient. 227 * ` 228 */ 229 $backgroundImageStyleValue = "linear-gradient(to right, rgba(255,255,255, $finalOpacity) 0 50%, rgba(255,255,255, $finalOpacity) 50% 100%)," . $backgroundImageStyleValue; 230 } 231 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, $backgroundImageStyleValue); 232 } 233 234 235 /** 236 * Process the pattern css 237 * https://bansal.io/pattern-css 238 * This call should be before the processing of the background color 239 * because it will set one if it's not available 240 */ 241 self::processPatternAttribute($tagAttributes); 242 243 /** 244 * Background color 245 */ 246 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 247 248 $colorValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_COLOR); 249 250 $gradientPrefix = 'gradient-'; 251 if (strpos($colorValue, $gradientPrefix) === 0) { 252 /** 253 * A gradient is an image 254 * Check that there is no image 255 */ 256 if (!empty($backgroundImageStyleValue)) { 257 LogUtility::msg("An image and a linear gradient color are exclusive because a linear gradient color creates an image. You can't use the linear color (" . $colorValue . ") and the image (" . $backgroundImageStyleValue . ")", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 258 } else { 259 $mainColorValue = substr($colorValue, strlen($gradientPrefix)); 260 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, 'linear-gradient(to top,#fff 0,' . ColorRgb::createFromString($mainColorValue)->toCssValue() . ' 100%)'); 261 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, 'unset!important'); 262 } 263 } else { 264 265 $colorValue = ColorRgb::createFromString($colorValue)->toCssValue(); 266 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, $colorValue); 267 268 } 269 } 270 271 272 } 273 274 /** 275 * Return a background array with background properties 276 * from a media {@link MediaLink::toCallStackArray()} 277 * @param array $mediaCallStackArray 278 * @return array 279 */ 280 public static function fromMediaToBackgroundImageStackArray(array $mediaCallStackArray): array 281 { 282 /** 283 * This attributes should no be taken 284 */ 285 $mediaCallStackArray[MediaMarkup::LINKING_KEY] = LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE; 286 $mediaCallStackArray[Align::ALIGN_ATTRIBUTE] = null; 287 $mediaCallStackArray[TagAttributes::TITLE_KEY] = null; // not sure why 288 return $mediaCallStackArray; 289 290 } 291 292 /** 293 * @param TagAttributes $tagAttributes 294 * Process the `pattern` attribute 295 * that implements 296 * https://bansal.io/pattern-css 297 */ 298 private static function processPatternAttribute(TagAttributes &$tagAttributes) 299 { 300 /** 301 * Css Pattern 302 */ 303 if ($tagAttributes->hasComponentAttribute(self::PATTERN_ATTRIBUTE)) { 304 305 /** 306 * Attach the stylesheet 307 */ 308 PluginUtility::getSnippetManager()->attachRemoteCssStyleSheet( 309 self::PATTERN_CSS_SNIPPET_ID, 310 "https://cdn.jsdelivr.net/npm/pattern.css@1.0.0/dist/pattern.min.css", 311 "sha256-Vwich3JPJa27TO9g6q+TxJGE7DNEigBaHNPm+KkMR6o=") 312 ->setCritical(false); // not blocking for rendering 313 314 315 $patternValue = strtolower($tagAttributes->getValueAndRemove(self::PATTERN_ATTRIBUTE)); 316 317 $lastIndexOfMinus = StringUtility::lastIndexOf($patternValue, "-"); 318 $lastMinusPart = substr($patternValue, $lastIndexOfMinus); 319 /** 320 * Do we have the size as last part 321 */ 322 if (!in_array($lastMinusPart, self::PATTERN_CSS_SIZE)) { 323 /** 324 * We don't have a size 325 */ 326 $pattern = $patternValue; 327 $size = "md"; 328 } else { 329 $pattern = substr($patternValue, 0, $lastIndexOfMinus); 330 $size = $lastMinusPart; 331 } 332 /** 333 * Does this pattern is a known pattern 334 */ 335 if (!in_array($pattern, self::PATTERN_NAMES)) { 336 LogUtility::msg("The pattern (" . $pattern . ") is not a known CSS pattern and was ignored.", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 337 return; 338 } else { 339 $tagAttributes->addClassName(self::PATTERN_CSS_CLASS_PREFIX . "-" . $pattern . "-" . $size); 340 } 341 342 if (!$tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 343 LogUtility::msg("The background color was not set for the background with the (" . $pattern . "). It was set to the default color.", LogUtility::LVL_MSG_INFO, self::CANONICAL); 344 $tagAttributes->addComponentAttributeValue(self::BACKGROUND_COLOR, "steelblue"); 345 } 346 /** 347 * Color 348 */ 349 if ($tagAttributes->hasComponentAttribute(self::PATTERN_COLOR_ATTRIBUTE)) { 350 $patternColor = $tagAttributes->getValueAndRemove(self::PATTERN_COLOR_ATTRIBUTE); 351 } else { 352 LogUtility::msg("The pattern color was not set for the background with the (" . $pattern . "). It was set to the default color.", LogUtility::LVL_MSG_INFO, self::CANONICAL); 353 $patternColor = "#FDE482"; 354 } 355 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::COLOR, $patternColor); 356 357 } 358 } 359 360 361} 362