xref: /plugin/combo/ComboStrap/Html.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
1<?php
2
3
4namespace ComboStrap;
5
6
7use ComboStrap\Web\Url;
8use ComboStrap\Xml\XmlDocument;
9
10class Html
11{
12
13
14    /**
15     * @param string $name
16     * @throws ExceptionRuntime
17     * Garbage In / Garbage Out design
18     */
19    public static function validNameGuard(string $name)
20    {
21        /**
22         * If the name is not in lowercase,
23         * the shorthand css selector does not work
24         */
25        $validName = strtolower($name);
26        if ($validName != $name) {
27            throw new ExceptionRuntime("The name ($name) is not a valid name");
28        }
29    }
30
31    /**
32     * Transform a text into a valid HTML id
33     * @param $string
34     * @return string
35     */
36    public static function toHtmlId($string): string
37    {
38        /**
39         * sectionId calls cleanID
40         * cleanID delete all things before a ':'
41         * we do then the replace before to not
42         * lost a minus '-' separator
43         */
44        $string = str_replace(array(':', '.'), '', $string);
45        return sectionID($string, $check);
46    }
47
48    /**
49     * Encode special HTML characters to entity (ie escaping)
50     *
51     * This is used to transform text that may be interpreted as HTML
52     * into a text
53     *   * that will not be interpreted as HTML
54     *   * that may be added in html attribute
55     *
56     * For instance:
57     *  * text that should go in attribute with special HTML characters (such as title)
58     *  * text that we don't create (to prevent HTML injection)
59     *
60     * Example:
61     *
62     * <script>...</script>
63     * to
64     * "&lt;script&gt;...&lt;/hello&gt;"
65     *
66     *
67     * @param $text
68     * @return string
69     *
70     * Note that if the `meta[charset]` matches the text encoding   , it should not be encoded
71     *
72     * True ? Beware that this still allows users to insert unsafe scripting vectors, such as markdown links like [xss](javascript:alert%281%29).
73     */
74    public static function encode($text): string
75    {
76        /**
77         * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344
78         *
79         * Not {@link htmlentities } htmlentities($text, ENT_QUOTES);
80         * Otherwise we get `Error while loading HTMLError: Entity 'hellip' not defined`
81         * when loading HTML with {@link XmlDocument}
82         *
83         * See also {@link self::htmlDecode()}
84         *
85         * Without ENT_QUOTES
86         * <h4 class="heading-combo">
87         * is encoded as
88         * &gt;h4 class="heading-combo"&lt;
89         * and cannot be added in a attribute because of the quote
90         * This is used for {@link Tooltip}
91         */
92        return htmlspecialchars($text, ENT_XHTML | ENT_QUOTES | ENT_DISALLOWED);
93
94    }
95
96    public static function decode($int): string
97    {
98        return htmlspecialchars_decode($int, ENT_XHTML | ENT_QUOTES);
99    }
100
101    public static function getDiffBetweenValuesSeparatedByBlank(string $expected, string $actual, string $expectedName = "expected class", string $actualName = "actual class"): string
102    {
103        $leftClasses = preg_split("/\s/", $expected);
104        $rightClasses = preg_split("/\s/", $actual);
105        $error = "";
106        foreach ($leftClasses as $leftClass) {
107            if (!in_array($leftClass, $rightClasses)) {
108                $error .= "The $expectedName has the value (" . $leftClass . ") that is not present in the $actualName)\n";
109            } else {
110                // Delete the value
111                $key = array_search($leftClass, $rightClasses);
112                unset($rightClasses[$key]);
113            }
114        }
115        foreach ($rightClasses as $rightClass) {
116            $error .= "The $actualName has the value (" . $rightClass . ") that is not present in the $expectedName)\n";
117        }
118        return $error;
119    }
120
121    /**
122     * @throws ExceptionBadSyntax - bad url
123     * @throws ExceptionNotEquals - not equals
124     */
125    public static function getDiffBetweenSrcSet(string $expected, string $actual)
126    {
127        $expectedSrcSets = explode(",", $expected);
128        $actualSrcSets = explode(",", $actual);
129        $countExpected = count($expectedSrcSets);
130        $countActual = count($actualSrcSets);
131        if ($countExpected !== $countActual) {
132            throw new ExceptionNotEquals("The expected srcSet count ($countExpected) is not the same than the actual ($countActual).");
133        }
134        for ($i = 0; $i < $countExpected; $i++) {
135            $expectedSrcSet = trim($expectedSrcSets[$i]);
136            $expectedSrcSetExploded = explode(" ", $expectedSrcSet, 2);
137            $expectedSrc = $expectedSrcSetExploded[0];
138            if (count($expectedSrcSetExploded) == 2) {
139                $expectedWidth = $expectedSrcSetExploded[1];
140            } else {
141                $expectedWidth = null;
142            }
143            $actualSrcSet = trim($actualSrcSets[$i]);
144            $actualSrcSetExploded = explode(" ", $actualSrcSet, 2);
145            $actualSrc = $actualSrcSetExploded[0];
146            if (count($actualSrcSetExploded) == 2) {
147                $actualWidth = $actualSrcSetExploded[1];
148            } else {
149                $actualWidth = null;
150            }
151            if ($expectedWidth !== $actualWidth) {
152                throw new ExceptionNotEquals("The expected width ($expectedWidth) of the srcSet ($i) is not the same than the actual ($actualWidth).");
153            }
154            try {
155                Html::getDiffBetweenUrlStrings($expectedSrc, $actualSrc);
156            } catch (ExceptionBadSyntax $e) {
157                throw new ExceptionBadSyntax("Bad Syntax on Src Set ($i). Error: {$e->getMessage()}. Expected: $expectedSrc vs Actual: $actualSrc. ");
158            } catch (ExceptionNotEquals $e) {
159                throw ExceptionNotEquals::create("Not Equals on Src Set ($i). Error: {$e->getMessage()}.", $expectedSrc, $actualSrc);
160            }
161        }
162    }
163
164
165    /**
166     * @throws ExceptionBadSyntax
167     * @throws ExceptionNotEquals
168     */
169    public static function getDiffBetweenUrlStrings(string $expected, string $actual)
170    {
171        try {
172            $url = Url::createFromString($expected);
173        } catch (ExceptionBadSyntax $e) {
174            throw new ExceptionBadSyntax("The expected URL string ($expected) is not valid. Error: {$e->getMessage()}");
175        }
176        try {
177            $urlActual = Url::createFromString($actual);
178        } catch (ExceptionBadSyntax $e) {
179            throw new ExceptionBadSyntax("The $actual URL string ($actual) is not valid. Error: {$e->getMessage()}");
180        }
181        $url->equals($urlActual);
182
183    }
184
185    /**
186     * Merge class name
187     * @param string $newNames - the name that we want to add
188     * @param ?string $actualNames - the actual names
189     * @return string - the class name list
190     *
191     * for instance:
192     *   * newNames = foo blue
193     *   * actual Name = foo bar
194     * return
195     *   * foo bar blue
196     */
197    public static function mergeClassNames(string $newNames, ?string $actualNames): string
198    {
199        /**
200         * It may be in the form "value1 value2"
201         */
202        $newValues = StringUtility::explodeAndTrim($newNames, " ");
203        if (!empty($actualNames)) {
204            $actualValues = StringUtility::explodeAndTrim(trim($actualNames), " ");
205        } else {
206            $actualValues = [];
207        }
208        $newValues = array_merge($actualValues, $newValues);
209        $newValues = array_unique($newValues);
210        return implode(" ", $newValues);
211    }
212
213    /**
214     * @param array $styleProperties - an array of CSS properties with key, value
215     * @return string - the value for the style attribute (ie all rules where joined with the comma)
216     */
217    public static function array2InlineStyle(array $styleProperties)
218    {
219        $inlineCss = "";
220        foreach ($styleProperties as $key => $value) {
221            $inlineCss .= "$key:$value;";
222        }
223        // Suppress the last ;
224        if ($inlineCss[strlen($inlineCss) - 1] == ";") {
225            $inlineCss = substr($inlineCss, 0, -1);
226        }
227        return $inlineCss;
228    }
229}
230