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 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_IMAGE, $background[self::BACKGROUND_IMAGE]); 91 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_COLOR, $background[self::BACKGROUND_COLOR]); 92 $tagAttributes->addComponentAttributeValueIfNotEmpty(Opacity::OPACITY_ATTRIBUTE, $background[Opacity::OPACITY_ATTRIBUTE]); 93 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_POSITION, $background[self::BACKGROUND_POSITION]); 94 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_FILL, $background[self::BACKGROUND_FILL]); 95 } else { 96 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 97 $backgroundTagAttribute->addClassName(self::CANONICAL); 98 $backgroundHTML = "<div class=\"backgrounds\">" . 99 $backgroundTagAttribute->toHtmlEnterTag("div") . 100 "</div>" . 101 "</div>"; 102 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 103 } 104 break; 105 default: 106 /** 107 * More than one background 108 * This backgrounds should have been set on the backgrounds 109 */ 110 $backgroundHTML = ""; 111 foreach ($backgrounds as $background) { 112 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 113 $backgroundTagAttribute->addClassName(self::CANONICAL); 114 $backgroundHTMLEnter = $backgroundTagAttribute->toHtmlEnterTag("div"); 115 $backgroundHTML .= $backgroundHTMLEnter . "</div>"; 116 } 117 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 118 break; 119 } 120 } 121 122 /** 123 * Background-image attribute 124 */ 125 $backgroundImageStyleValue = ""; 126 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_IMAGE)) { 127 $backgroundImageValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 128 if (is_string($backgroundImageValue)) { 129 /** 130 * Image background is set by the user 131 */ 132 $backgroundImageStyleValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 133 134 } else { 135 136 if (is_array($backgroundImageValue)) { 137 138 /** 139 * Background-fill for background image 140 */ 141 $backgroundFill = $tagAttributes->getValueAndRemove(self::BACKGROUND_FILL, "cover"); 142 switch ($backgroundFill) { 143 case "cover": 144 // it makes the background responsive 145 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_SIZE, $backgroundFill); 146 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "no-repeat"); 147 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_POSITION, "center center"); 148 149 /** 150 * The type of image is important for the processing of SVG 151 */ 152 $backgroundImageValue[TagAttributes::TYPE_KEY] = FetcherSvg::ILLUSTRATION_TYPE; 153 break; 154 case "tile": 155 // background size is then "auto" (ie repeat), the default 156 // background position is not needed (the tile start on the left top corner) 157 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "repeat"); 158 159 /** 160 * The type of image is important for the processing of SVG 161 * A tile needs to have a width and a height 162 */ 163 $backgroundImageValue[TagAttributes::TYPE_KEY] = FetcherSvg::TILE_TYPE; 164 break; 165 case "css": 166 // custom, set by the user in own css stylesheet, nothing to do 167 break; 168 default: 169 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); 170 break; 171 } 172 173 174 try { 175 $mediaMarkup = MediaMarkup::createFromCallStackArray($backgroundImageValue) 176 ->setLinking(MediaMarkup::LINKING_NOLINK_VALUE); 177 } catch (ExceptionCompile $e) { 178 LogUtility::error("We could not create a background image. Error: {$e->getMessage()}"); 179 return; 180 } 181 182 try { 183 $imageFetcher = $mediaMarkup->getFetcher(); 184 } catch (ExceptionBadArgument|ExceptionInternal|ExceptionNotFound $e) { 185 LogUtility::internalError("The fetcher for the background image ($mediaMarkup) returns an error", self::CANONICAL, $e); 186 return; 187 } 188 $mime = $imageFetcher->getMime(); 189 if (!$mime->isImage()) { 190 LogUtility::error("The background image ($mediaMarkup) is not an image but a $mime", self::CANONICAL); 191 return; 192 } 193 $backgroundImageStyleValue = "url(" . $imageFetcher->getFetchUrl()->toAbsoluteUrl()->toCssString() . ")"; 194 195 } else { 196 LogUtility::msg("Internal Error: The background image value ($backgroundImageValue) is not a string nor an array", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 197 } 198 199 } 200 } 201 if (!empty($backgroundImageStyleValue)) { 202 if ($tagAttributes->hasComponentAttribute(Opacity::OPACITY_ATTRIBUTE)) { 203 $opacity = $tagAttributes->getValueAndRemove(Opacity::OPACITY_ATTRIBUTE); 204 $finalOpacity = 1 - $opacity; 205 /** 206 * In the argument of linear-gradient, we don't use `0 100%` to apply the 207 * same image everywhere 208 * 209 * because the validator https://validator.w3.org/ would complain with 210 * ` 211 * CSS: background-image: too few values for the property linear-gradient. 212 * ` 213 */ 214 $backgroundImageStyleValue = "linear-gradient(to right, rgba(255,255,255, $finalOpacity) 0 50%, rgba(255,255,255, $finalOpacity) 50% 100%)," . $backgroundImageStyleValue; 215 } 216 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, $backgroundImageStyleValue); 217 } 218 219 220 /** 221 * Process the pattern css 222 * https://bansal.io/pattern-css 223 * This call should be before the processing of the background color 224 * because it will set one if it's not available 225 */ 226 self::processPatternAttribute($tagAttributes); 227 228 /** 229 * Background color 230 */ 231 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 232 233 $colorValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_COLOR); 234 235 $gradientPrefix = 'gradient-'; 236 if (strpos($colorValue, $gradientPrefix) === 0) { 237 /** 238 * A gradient is an image 239 * Check that there is no image 240 */ 241 if (!empty($backgroundImageStyleValue)) { 242 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); 243 } else { 244 $mainColorValue = substr($colorValue, strlen($gradientPrefix)); 245 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, 'linear-gradient(to top,#fff 0,' . ColorRgb::createFromString($mainColorValue)->toCssValue() . ' 100%)'); 246 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, 'unset!important'); 247 } 248 } else { 249 250 $colorValue = ColorRgb::createFromString($colorValue)->toCssValue(); 251 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, $colorValue); 252 253 } 254 } 255 256 257 } 258 259 /** 260 * Return a background array with background properties 261 * from a media {@link MediaLink::toCallStackArray()} 262 * @param array $mediaCallStackArray 263 * @return array 264 */ 265 public static function fromMediaToBackgroundImageStackArray(array $mediaCallStackArray): array 266 { 267 /** 268 * This attributes should no be taken 269 */ 270 $mediaCallStackArray[MediaMarkup::LINKING_KEY] = LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE; 271 $mediaCallStackArray[Align::ALIGN_ATTRIBUTE] = null; 272 $mediaCallStackArray[TagAttributes::TITLE_KEY] = null; // not sure why 273 return $mediaCallStackArray; 274 275 } 276 277 /** 278 * @param TagAttributes $tagAttributes 279 * Process the `pattern` attribute 280 * that implements 281 * https://bansal.io/pattern-css 282 */ 283 private static function processPatternAttribute(TagAttributes &$tagAttributes) 284 { 285 /** 286 * Css Pattern 287 */ 288 if ($tagAttributes->hasComponentAttribute(self::PATTERN_ATTRIBUTE)) { 289 290 /** 291 * Attach the stylesheet 292 */ 293 PluginUtility::getSnippetManager()->attachRemoteCssStyleSheet( 294 self::PATTERN_CSS_SNIPPET_ID, 295 "https://cdn.jsdelivr.net/npm/pattern.css@1.0.0/dist/pattern.min.css", 296 "sha256-Vwich3JPJa27TO9g6q+TxJGE7DNEigBaHNPm+KkMR6o=") 297 ->setCritical(false); // not blocking for rendering 298 299 300 $patternValue = strtolower($tagAttributes->getValueAndRemove(self::PATTERN_ATTRIBUTE)); 301 302 $lastIndexOfMinus = StringUtility::lastIndexOf($patternValue, "-"); 303 $lastMinusPart = substr($patternValue, $lastIndexOfMinus); 304 /** 305 * Do we have the size as last part 306 */ 307 if (!in_array($lastMinusPart, self::PATTERN_CSS_SIZE)) { 308 /** 309 * We don't have a size 310 */ 311 $pattern = $patternValue; 312 $size = "md"; 313 } else { 314 $pattern = substr($patternValue, 0, $lastIndexOfMinus); 315 $size = $lastMinusPart; 316 } 317 /** 318 * Does this pattern is a known pattern 319 */ 320 if (!in_array($pattern, self::PATTERN_NAMES)) { 321 LogUtility::msg("The pattern (" . $pattern . ") is not a known CSS pattern and was ignored.", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 322 return; 323 } else { 324 $tagAttributes->addClassName(self::PATTERN_CSS_CLASS_PREFIX . "-" . $pattern . "-" . $size); 325 } 326 327 if (!$tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 328 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); 329 $tagAttributes->addComponentAttributeValue(self::BACKGROUND_COLOR, "steelblue"); 330 } 331 /** 332 * Color 333 */ 334 if ($tagAttributes->hasComponentAttribute(self::PATTERN_COLOR_ATTRIBUTE)) { 335 $patternColor = $tagAttributes->getValueAndRemove(self::PATTERN_COLOR_ATTRIBUTE); 336 } else { 337 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); 338 $patternColor = "#FDE482"; 339 } 340 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::COLOR, $patternColor); 341 342 } 343 } 344 345 346} 347