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
15
16use JsonSerializable;
17
18class Snippet implements JsonSerializable
19{
20    /**
21     * The head in css format
22     * We need to add the style node
23     */
24    const TYPE_CSS = "css";
25
26
27    /**
28     * The head in javascript
29     * We need to wrap it in a script node
30     */
31    const TYPE_JS = "js";
32    /**
33     * A tag head in array format
34     * No need
35     */
36    const TAG_TYPE = "tag";
37    const JSON_SNIPPET_ID_PROPERTY = "id";
38    const JSON_TYPE_PROPERTY = "type";
39    const JSON_CRITICAL_PROPERTY = "critical";
40    const JSON_CONTENT_PROPERTY = "content";
41    const JSON_HEAD_PROPERTY = "head";
42
43    private $snippetId;
44    private $type;
45
46    /**
47     * @var bool
48     */
49    private $critical;
50
51    /**
52     * @var string the text script / style (may be null if it's an external resources)
53     */
54    private $content;
55    /**
56     * @var array
57     */
58    private $headsTags;
59
60    /**
61     * Snippet constructor.
62     */
63    public function __construct($snippetId, $snippetType)
64    {
65        $this->snippetId = $snippetId;
66        $this->type = $snippetType;
67    }
68
69    public static function createJavascriptSnippet($snippetId): Snippet
70    {
71        return new Snippet($snippetId, self::TYPE_JS);
72    }
73
74    public static function createCssSnippet($snippetId): Snippet
75    {
76        return new Snippet($snippetId, self::TYPE_CSS);
77    }
78
79    /**
80     * @param $snippetId
81     * @return Snippet
82     * @deprecated You should create a snippet with a known type, this constructor was created for refactoring
83     */
84    public static function createUnknownSnippet($snippetId): Snippet
85    {
86        return new Snippet($snippetId, "unknwon");
87    }
88
89
90    /**
91     * @param $bool - if the snippet is critical, it would not be deferred or preloaded
92     * @return Snippet for chaining
93     * All css that are for animation or background for instance
94     * should not be set as critical as they are not needed to paint
95     * exactly the page
96     */
97    public function setCritical($bool): Snippet
98    {
99        $this->critical = $bool;
100        return $this;
101    }
102
103    /**
104     * @param $content - Set an inline content for a script or stylesheet
105     * @return Snippet for chaining
106     */
107    public function setContent($content): Snippet
108    {
109        $this->content = $content;
110        return $this;
111    }
112
113    /**
114     * @return string
115     */
116    public function getContent()
117    {
118        if ($this->content == null) {
119            switch ($this->type) {
120                case self::TYPE_CSS:
121                    $this->content = $this->getCssRulesFromFile($this->snippetId);
122                    break;
123                case self::TYPE_JS:
124                    $this->content = $this->getJavascriptContentFromFile($this->snippetId);
125                    break;
126                default:
127                    LogUtility::msg("The snippet ($this) has no content", LogUtility::LVL_MSG_ERROR, "support");
128            }
129        }
130        return $this->content;
131    }
132
133    /**
134     * @param $tagName
135     * @return false|string - the css content of the css file
136     */
137    private function getCssRulesFromFile($tagName)
138    {
139
140        $path = Resources::getSnippetResourceDirectory() . "/style/" . strtolower($tagName) . ".css";
141        if (file_exists($path)) {
142            return file_get_contents($path);
143        } else {
144            LogUtility::msg("The css file ($path) was not found", LogUtility::LVL_MSG_WARNING, $tagName);
145            return "";
146        }
147
148    }
149
150    /**
151     * @param $tagName - the tag name
152     * @return false|string - the specific javascript content for the tag
153     */
154    private function getJavascriptContentFromFile($tagName)
155    {
156
157        $path = Resources::getSnippetResourceDirectory() . "/js/" . strtolower($tagName) . ".js";
158        if (file_exists($path)) {
159            return file_get_contents($path);
160        } else {
161            LogUtility::msg("The javascript file ($path) was not found", LogUtility::LVL_MSG_WARNING, $tagName);
162            return "";
163        }
164
165    }
166
167    public function __toString()
168    {
169        return $this->snippetId . "-" . $this->type;
170    }
171
172    /**
173     * Set all tags at once.
174     * @param array $tags
175     * @return Snippet
176     */
177    public function setTags(array $tags): Snippet
178    {
179        $this->headsTags = $tags;
180        return $this;
181    }
182
183    public function getTags(): array
184    {
185        return $this->headsTags;
186    }
187
188    public function getCritical(): bool
189    {
190
191        if ($this->critical === null) {
192            if ($this->type == self::TYPE_CSS) {
193                // All CSS should be loaded first
194                // The CSS animation / background can set this to false
195                return true;
196            }
197            return false;
198        }
199        return $this->critical;
200    }
201
202    public function getClass(): string
203    {
204        /**
205         * The class for the snippet is just to be able to identify them
206         *
207         * The `snippet` prefix was added to be sure that the class
208         * name will not conflict with a css class
209         * Example: if you set the class to `combo-list`
210         * and that you use it in a inline `style` tag with
211         * the same class name, the inline `style` tag is not applied
212         *
213         */
214        return "snippet-" . $this->snippetId . "-" . SnippetManager::COMBO_CLASS_SUFFIX;
215
216    }
217
218    /**
219     * @return string the HTML of the tag (works for now only with CSS content)
220     */
221    public function getHtmlStyleTag(): string
222    {
223        $content = $this->getContent();
224        $class = $this->getClass();
225        return <<<EOF
226<style class="$class">
227$content
228</style>
229EOF;
230
231    }
232
233    public function getId()
234    {
235        return $this->snippetId;
236    }
237
238
239    public function jsonSerialize(): array
240    {
241        $dataToSerialize = [
242            self::JSON_SNIPPET_ID_PROPERTY => $this->snippetId,
243            self::JSON_TYPE_PROPERTY => $this->type
244        ];
245        if ($this->critical !== null) {
246            $dataToSerialize[self::JSON_CRITICAL_PROPERTY] = $this->critical;
247        }
248        if ($this->content !== null) {
249            $dataToSerialize[self::JSON_CONTENT_PROPERTY] = $this->content;
250        }
251        if ($this->headsTags !== null) {
252            $dataToSerialize[self::JSON_HEAD_PROPERTY] = $this->headsTags;
253        }
254        return $dataToSerialize;
255
256    }
257
258    /**
259     * @throws ExceptionCombo
260     */
261    public static function createFromJson($array): Snippet
262    {
263        $snippetId = $array[self::JSON_SNIPPET_ID_PROPERTY];
264        if ($snippetId === null) {
265            throw new ExceptionCombo("The snippet id property was not found in the json array");
266        }
267        $type = $array[self::JSON_TYPE_PROPERTY];
268        if ($type === null) {
269            throw new ExceptionCombo("The snippet type property was not found in the json array");
270        }
271        $snippet = new Snippet($snippetId, $type);
272        $critical = $array[self::JSON_CRITICAL_PROPERTY];
273        if ($critical !== null) {
274            $snippet->setCritical($critical);
275        }
276
277        $content = $array[self::JSON_CONTENT_PROPERTY];
278        if ($content !== null) {
279            $snippet->setContent($content);
280        }
281
282        $heads = $array[self::JSON_HEAD_PROPERTY];
283        if ($heads !== null) {
284            $snippet->setTags($heads);
285        }
286        return $snippet;
287
288    }
289}
290