xref: /plugin/combo/ComboStrap/Html.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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            [$expectedSrc, $expectedWidth] = explode(" ", $expectedSrcSet, 2);
137            $actualSrcSet = trim($actualSrcSets[$i]);
138            [$actualSrc, $actualWidth] = explode(" ", $actualSrcSet, 2);
139            if ($expectedWidth !== $actualWidth) {
140                throw new ExceptionNotEquals("The expected width ($expectedWidth) of the srcSet ($i) is not the same than the actual ($actualWidth).");
141            }
142            try {
143                Html::getDiffBetweenUrlStrings($expectedSrc, $actualSrc);
144            } catch (ExceptionBadSyntax $e) {
145                throw new ExceptionBadSyntax("Bad Syntax on Src Set ($i). Error: {$e->getMessage()}. Expected: $expectedSrc vs Actual: $actualSrc. ");
146            } catch (ExceptionNotEquals $e) {
147                throw ExceptionNotEquals::create("Not Equals on Src Set ($i). Error: {$e->getMessage()}.", $expectedSrc, $actualSrc);
148            }
149        }
150    }
151
152
153
154    /**
155     * @throws ExceptionBadSyntax
156     * @throws ExceptionNotEquals
157     */
158    public static function getDiffBetweenUrlStrings(string $expected, string $actual)
159    {
160        try {
161            $url = Url::createFromString($expected);
162        } catch (ExceptionBadSyntax $e) {
163            throw new ExceptionBadSyntax("The expected URL string ($expected) is not valid. Error: {$e->getMessage()}");
164        }
165        try {
166            $urlActual = Url::createFromString($actual);
167        } catch (ExceptionBadSyntax $e) {
168            throw new ExceptionBadSyntax("The $actual URL string ($actual) is not valid. Error: {$e->getMessage()}");
169        }
170        $url->equals($urlActual);
171
172    }
173
174    /**
175     * Merge class name
176     * @param string $newNames - the name that we want to add
177     * @param ?string $actualNames - the actual names
178     * @return string - the class name list
179     *
180     * for instance:
181     *   * newNames = foo blue
182     *   * actual Name = foo bar
183     * return
184     *   * foo bar blue
185     */
186    public static function mergeClassNames(string $newNames, ?string $actualNames): string
187    {
188        /**
189         * It may be in the form "value1 value2"
190         */
191        $newValues = StringUtility::explodeAndTrim($newNames, " ");
192        if (!empty($actualNames)) {
193            $actualValues = StringUtility::explodeAndTrim(trim($actualNames), " ");
194        } else {
195            $actualValues = [];
196        }
197        $newValues = array_merge($actualValues, $newValues);
198        $newValues = array_unique($newValues);
199        return implode(" ", $newValues);
200    }
201
202    /**
203     * @param array $styleProperties - an array of CSS properties with key, value
204     * @return string - the value for the style attribute (ie all rules where joined with the comma)
205     */
206    public static function array2InlineStyle(array $styleProperties)
207    {
208        $inlineCss = "";
209        foreach ($styleProperties as $key => $value) {
210            $inlineCss .= "$key:$value;";
211        }
212        // Suppress the last ;
213        if ($inlineCss[strlen($inlineCss) - 1] == ";") {
214            $inlineCss = substr($inlineCss, 0, -1);
215        }
216        return $inlineCss;
217    }
218}
219