xref: /plugin/combo/ComboStrap/TagAttribute/BackgroundAttribute.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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