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 ComboStrap\TagAttribute\BackgroundAttribute;
16use ComboStrap\TagAttribute\StyleAttribute;
17
18/**
19 * This one support background loading
20 * https://github.com/ApoorvSaxena/lozad.js
21 *
22 *
23 * TODO: implement no script pattern ? the https://github.com/aFarkas/lazysizes#the-noscript-pattern
24 *
25 */
26class LazyLoad
27{
28
29    const CONF_LAZY_LOADING_PLACEHOLDER_COLOR = "lazyLoadingPlaceholderColor";
30
31    /**
32     * Lozad was choosen because
33     * it was easier to add svg injection
34     * it supports background image
35     * it's most used (JsDelivr stats)
36     */
37    const ACTIVE = self::LOZAD_ID;
38
39    /**
40     * The id of the lazy loaders
41     */
42    const LAZY_SIDE_ID = "lazy-sizes";
43    const LOZAD_ID = "lozad";
44
45
46    const CANONICAL = "lazy";
47    const DEFAULT_COLOR = "#cbf1ea";
48
49    public const LAZY_LOAD_METHOD_HTML_VALUE = "html";
50    public const LAZY_LOAD_METHOD_LOZAD_VALUE = "lozad";
51
52    /**
53     * The method on how to lazy load resources (Ie media)
54     */
55    public const LAZY_LOAD_METHOD = "lazy";
56    /**
57     * The default when the image are above the fold
58     */
59    public const LAZY_LOAD_METHOD_NONE_VALUE = "none";
60    /**
61     * Used internal for now on test
62     */
63    const CONF_LAZY_LOAD_METHOD = "internal-lazy-load-method-combo";
64    public const CONF_RASTER_ENABLE = "rasterImageLazyLoadingEnable";
65    public const CONF_RASTER_ENABLE_DEFAULT = 1;
66    public const HTML_LOADING_ATTRIBUTE = "loading";
67
68    /**
69     * Used to select all lazy loaded
70     * resources and load them before print
71     */
72    public static function getLazyClass(): string
73    {
74        return StyleAttribute::addComboStrapSuffix(self::CANONICAL);
75    }
76
77    public static function addSnippet()
78    {
79        switch (self::ACTIVE) {
80            case self::LAZY_SIDE_ID:
81                LazyLoad::addLazySizesSnippet();
82                break;
83            case self::LOZAD_ID:
84                LazyLoad::addLozadSnippet();
85                break;
86            default:
87                throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")");
88        }
89
90    }
91
92    /**
93     * Add the lazy sizes snippet
94     */
95    private static function addLazySizesSnippet()
96    {
97
98        $snippetManager = PluginUtility::getSnippetManager();
99
100        $snippetManager->attachRemoteJavascriptLibrary(
101            self::LAZY_SIDE_ID,
102            "https://cdn.jsdelivr.net/npm/lazysizes@5.3.1/lazysizes.min.js",
103            "sha256-bmG+LzdKASJRACVXiUC69++Nu8rz7MX1U1z8gb0c/Tk="
104        );
105        /**
106         * The Spinner effect
107         * lazysizes adds the class lazy loading while the images are loading
108         * and the class lazyloaded as soon as the image is loaded.
109         */
110        $snippetManager->attachCssInternalStyleSheet(self::LAZY_SIDE_ID);
111
112    }
113
114    /**
115     * @param TagAttributes $attributes
116     */
117    public static function addPlaceholderBackground(&$attributes)
118    {
119        // https://github.com/ApoorvSaxena/lozad.js#large-image-improvment
120        $placeholderColor = LazyLoad::getPlaceholderColor();
121        if ($attributes->hasComponentAttribute(BackgroundAttribute::BACKGROUND_COLOR)) {
122            $placeholderColor = $attributes->getValueAndRemove(BackgroundAttribute::BACKGROUND_COLOR);
123        }
124        $attributes->addOutputAttributeValue("data-placeholder-background", "$placeholderColor");
125
126
127    }
128
129    /**
130     * Add lozad
131     * Support background image
132     * https://github.com/ApoorvSaxena/lozad.js
133     */
134    public static function addLozadSnippet()
135    {
136
137        $snippetManager = PluginUtility::getSnippetManager();
138
139        // https://www.jsdelivr.com/package/npm/lozad
140        $snippetManager
141            ->attachRemoteJavascriptLibrary(
142                self::LOZAD_ID,
143                "https://cdn.jsdelivr.net/npm/lozad@1.16.0/dist/lozad.min.js",
144                "sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc="
145            )
146            ->setDoesManipulateTheDomOnRun(false);
147
148        /**
149         * Add the fading effect
150         */
151        $snippetId = "lazy-load-fade";
152        $snippetManager->attachCssInternalStyleSheet($snippetId);
153
154
155        /**
156         * Snippet to download the image before print
157         *
158         * The others javascript snippet to download lazy load depend on the image type
159         * and features and was therefore added in the code for svg or raster
160         */
161        $snippetManager->attachJavascriptFromComponentId("lozad-print");
162
163
164    }
165
166    /**
167     * Class selector to identify the element to lazy load
168     */
169    public static function getClass()
170    {
171        switch (self::ACTIVE) {
172            case self::LAZY_SIDE_ID:
173                return "lazyload";
174            case self::LOZAD_ID:
175                return "lozad";
176            default:
177                throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")");
178        }
179    }
180
181    /**
182     * @return string - the lazy loading placeholder color
183     */
184    public static function getPlaceholderColor()
185    {
186        return SiteConfig::getConfValue(self::CONF_LAZY_LOADING_PLACEHOLDER_COLOR, self::DEFAULT_COLOR);
187    }
188
189    /**
190     * The placeholder is not mandatory
191     * but if present, it should have the same target ratio of the image
192     *
193     * This function is documenting this fact.
194     *
195     * @param null $imgTagWidth
196     * @param null $imgTagHeight
197     * @return string
198     *
199     *
200     * Src is always set, this is the default
201     * src attribute is served to browsers that do not take the srcset attribute into account.
202     * When lazy loading, we set the srcset to a transparent image to not download the image in the src
203     *
204     */
205    public static function getPlaceholder($imgTagWidth = null, $imgTagHeight = null): string
206    {
207        if ($imgTagWidth != null) {
208            $svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $imgTagWidth $imgTagHeight'></svg>";
209            /**
210             * We encode it to be able to
211             * use it in a `srcset` attribute that does not
212             * want any space in the image definition
213             */
214            $svgBase64 = base64_encode($svg);
215            $image = "data:image/svg+xml;base64,$svgBase64";
216        } else {
217            /**
218             * Base64 transparent gif
219             * 1x1 image, it will produce a square
220             */
221            $image = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
222        }
223        return $image;
224    }
225
226    /**
227     * @return void
228     * @deprecated use {@link SiteConfig::disableLazyLoad()}
229     */
230    public static function disable()
231    {
232        ExecutionContext::getActualOrCreateFromEnv()
233            ->getConfig()
234            ->disableLazyLoad();
235    }
236
237    /**
238     *
239     * By default, the image above the fold should not be lazy loaded
240     * Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint.
241     *
242     *
243     */
244    public static function getDefault()
245    {
246
247        try {
248            /**
249             * Above-the-fold images that are lazily loaded render later in the page lifecycle,
250             * which can delay the largest contentful paint.
251             */
252            $sourcePath = ExecutionContext::getActualOrCreateFromEnv()
253                ->getExecutingMarkupHandler()
254                ->getSourcePath();
255            if(SlotSystem::isMainHeaderSlot($sourcePath)){
256                return LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE;
257            }
258        } catch (ExceptionNotFound $e) {
259            // not a path execution
260        }
261
262        /**
263         * HTML and not lozad as default because in a Hbs template, in a {@link TemplateForWebPage},
264         * it would not work as the script would not be added
265         */
266        return ExecutionContext::getActualOrCreateFromEnv()
267            ->getConfig()
268            ->getValue(LazyLoad::CONF_LAZY_LOAD_METHOD,LazyLoad::LAZY_LOAD_METHOD_HTML_VALUE) ;
269    }
270
271}
272