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