xref: /plugin/combo/ComboStrap/Html.php (revision 9154f6e30a3942d6f075522e01fb3935a48bc58f)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
4c3437056SNickeaunamespace ComboStrap;
5c3437056SNickeau
6c3437056SNickeau
704fd306cSNickeauuse ComboStrap\Web\Url;
804fd306cSNickeauuse ComboStrap\Xml\XmlDocument;
904fd306cSNickeau
10c3437056SNickeauclass Html
11c3437056SNickeau{
12c3437056SNickeau
13c3437056SNickeau
14c3437056SNickeau    /**
15c3437056SNickeau     * @param string $name
1604fd306cSNickeau     * @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) {
2704fd306cSNickeau            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
35*9154f6e3Sgerardnico     *
36*9154f6e3Sgerardnico     * See also: https://github.com/Flet/github-slugger
374cadd4f8SNickeau     */
384cadd4f8SNickeau    public static function toHtmlId($string): string
394cadd4f8SNickeau    {
404cadd4f8SNickeau        /**
414cadd4f8SNickeau         * sectionId calls cleanID
424cadd4f8SNickeau         * cleanID delete all things before a ':'
434cadd4f8SNickeau         * we do then the replace before to not
444cadd4f8SNickeau         * lost a minus '-' separator
454cadd4f8SNickeau         */
464cadd4f8SNickeau        $string = str_replace(array(':', '.'), '', $string);
474cadd4f8SNickeau        return sectionID($string, $check);
484cadd4f8SNickeau    }
4904fd306cSNickeau
5004fd306cSNickeau    /**
5104fd306cSNickeau     * Encode special HTML characters to entity (ie escaping)
5204fd306cSNickeau     *
5304fd306cSNickeau     * This is used to transform text that may be interpreted as HTML
5404fd306cSNickeau     * into a text
5504fd306cSNickeau     *   * that will not be interpreted as HTML
5604fd306cSNickeau     *   * that may be added in html attribute
5704fd306cSNickeau     *
5804fd306cSNickeau     * For instance:
5904fd306cSNickeau     *  * text that should go in attribute with special HTML characters (such as title)
6004fd306cSNickeau     *  * text that we don't create (to prevent HTML injection)
6104fd306cSNickeau     *
6204fd306cSNickeau     * Example:
6304fd306cSNickeau     *
6404fd306cSNickeau     * <script>...</script>
6504fd306cSNickeau     * to
6604fd306cSNickeau     * "&lt;script&gt;...&lt;/hello&gt;"
6704fd306cSNickeau     *
6804fd306cSNickeau     *
6904fd306cSNickeau     * @param $text
7004fd306cSNickeau     * @return string
7104fd306cSNickeau     *
7204fd306cSNickeau     * Note that if the `meta[charset]` matches the text encoding   , it should not be encoded
7304fd306cSNickeau     *
7404fd306cSNickeau     * True ? Beware that this still allows users to insert unsafe scripting vectors, such as markdown links like [xss](javascript:alert%281%29).
7504fd306cSNickeau     */
7604fd306cSNickeau    public static function encode($text): string
7704fd306cSNickeau    {
7804fd306cSNickeau        /**
7904fd306cSNickeau         * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344
8004fd306cSNickeau         *
8104fd306cSNickeau         * Not {@link htmlentities } htmlentities($text, ENT_QUOTES);
8204fd306cSNickeau         * Otherwise we get `Error while loading HTMLError: Entity 'hellip' not defined`
8304fd306cSNickeau         * when loading HTML with {@link XmlDocument}
8404fd306cSNickeau         *
8504fd306cSNickeau         * See also {@link self::htmlDecode()}
8604fd306cSNickeau         *
8704fd306cSNickeau         * Without ENT_QUOTES
8804fd306cSNickeau         * <h4 class="heading-combo">
8904fd306cSNickeau         * is encoded as
9004fd306cSNickeau         * &gt;h4 class="heading-combo"&lt;
9104fd306cSNickeau         * and cannot be added in a attribute because of the quote
9204fd306cSNickeau         * This is used for {@link Tooltip}
9304fd306cSNickeau         */
9404fd306cSNickeau        return htmlspecialchars($text, ENT_XHTML | ENT_QUOTES | ENT_DISALLOWED);
9504fd306cSNickeau
9604fd306cSNickeau    }
9704fd306cSNickeau
9804fd306cSNickeau    public static function decode($int): string
9904fd306cSNickeau    {
10004fd306cSNickeau        return htmlspecialchars_decode($int, ENT_XHTML | ENT_QUOTES);
10104fd306cSNickeau    }
10204fd306cSNickeau
10304fd306cSNickeau    public static function getDiffBetweenValuesSeparatedByBlank(string $expected, string $actual, string $expectedName = "expected class", string $actualName = "actual class"): string
10404fd306cSNickeau    {
10504fd306cSNickeau        $leftClasses = preg_split("/\s/", $expected);
10604fd306cSNickeau        $rightClasses = preg_split("/\s/", $actual);
10704fd306cSNickeau        $error = "";
10804fd306cSNickeau        foreach ($leftClasses as $leftClass) {
10904fd306cSNickeau            if (!in_array($leftClass, $rightClasses)) {
11004fd306cSNickeau                $error .= "The $expectedName has the value (" . $leftClass . ") that is not present in the $actualName)\n";
11104fd306cSNickeau            } else {
11204fd306cSNickeau                // Delete the value
11304fd306cSNickeau                $key = array_search($leftClass, $rightClasses);
11404fd306cSNickeau                unset($rightClasses[$key]);
11504fd306cSNickeau            }
11604fd306cSNickeau        }
11704fd306cSNickeau        foreach ($rightClasses as $rightClass) {
11804fd306cSNickeau            $error .= "The $actualName has the value (" . $rightClass . ") that is not present in the $expectedName)\n";
11904fd306cSNickeau        }
12004fd306cSNickeau        return $error;
12104fd306cSNickeau    }
12204fd306cSNickeau
12304fd306cSNickeau    /**
12404fd306cSNickeau     * @throws ExceptionBadSyntax - bad url
12504fd306cSNickeau     * @throws ExceptionNotEquals - not equals
12604fd306cSNickeau     */
12704fd306cSNickeau    public static function getDiffBetweenSrcSet(string $expected, string $actual)
12804fd306cSNickeau    {
12904fd306cSNickeau        $expectedSrcSets = explode(",", $expected);
13004fd306cSNickeau        $actualSrcSets = explode(",", $actual);
13104fd306cSNickeau        $countExpected = count($expectedSrcSets);
13204fd306cSNickeau        $countActual = count($actualSrcSets);
13304fd306cSNickeau        if ($countExpected !== $countActual) {
13404fd306cSNickeau            throw new ExceptionNotEquals("The expected srcSet count ($countExpected) is not the same than the actual ($countActual).");
13504fd306cSNickeau        }
13604fd306cSNickeau        for ($i = 0; $i < $countExpected; $i++) {
13704fd306cSNickeau            $expectedSrcSet = trim($expectedSrcSets[$i]);
13870bbd7f1Sgerardnico            $expectedSrcSetExploded = explode(" ", $expectedSrcSet, 2);
13970bbd7f1Sgerardnico            $expectedSrc = $expectedSrcSetExploded[0];
14070bbd7f1Sgerardnico            if (count($expectedSrcSetExploded) == 2) {
14170bbd7f1Sgerardnico                $expectedWidth = $expectedSrcSetExploded[1];
14270bbd7f1Sgerardnico            } else {
14370bbd7f1Sgerardnico                $expectedWidth = null;
14470bbd7f1Sgerardnico            }
14504fd306cSNickeau            $actualSrcSet = trim($actualSrcSets[$i]);
14670bbd7f1Sgerardnico            $actualSrcSetExploded = explode(" ", $actualSrcSet, 2);
14770bbd7f1Sgerardnico            $actualSrc = $actualSrcSetExploded[0];
14870bbd7f1Sgerardnico            if (count($actualSrcSetExploded) == 2) {
14970bbd7f1Sgerardnico                $actualWidth = $actualSrcSetExploded[1];
15070bbd7f1Sgerardnico            } else {
15170bbd7f1Sgerardnico                $actualWidth = null;
15270bbd7f1Sgerardnico            }
15304fd306cSNickeau            if ($expectedWidth !== $actualWidth) {
15404fd306cSNickeau                throw new ExceptionNotEquals("The expected width ($expectedWidth) of the srcSet ($i) is not the same than the actual ($actualWidth).");
15504fd306cSNickeau            }
15604fd306cSNickeau            try {
15704fd306cSNickeau                Html::getDiffBetweenUrlStrings($expectedSrc, $actualSrc);
15804fd306cSNickeau            } catch (ExceptionBadSyntax $e) {
15904fd306cSNickeau                throw new ExceptionBadSyntax("Bad Syntax on Src Set ($i). Error: {$e->getMessage()}. Expected: $expectedSrc vs Actual: $actualSrc. ");
16004fd306cSNickeau            } catch (ExceptionNotEquals $e) {
16104fd306cSNickeau                throw ExceptionNotEquals::create("Not Equals on Src Set ($i). Error: {$e->getMessage()}.", $expectedSrc, $actualSrc);
16204fd306cSNickeau            }
16304fd306cSNickeau        }
16404fd306cSNickeau    }
16504fd306cSNickeau
16604fd306cSNickeau
16704fd306cSNickeau    /**
16804fd306cSNickeau     * @throws ExceptionBadSyntax
16904fd306cSNickeau     * @throws ExceptionNotEquals
17004fd306cSNickeau     */
17104fd306cSNickeau    public static function getDiffBetweenUrlStrings(string $expected, string $actual)
17204fd306cSNickeau    {
17304fd306cSNickeau        try {
17404fd306cSNickeau            $url = Url::createFromString($expected);
17504fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
17604fd306cSNickeau            throw new ExceptionBadSyntax("The expected URL string ($expected) is not valid. Error: {$e->getMessage()}");
17704fd306cSNickeau        }
17804fd306cSNickeau        try {
17904fd306cSNickeau            $urlActual = Url::createFromString($actual);
18004fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
18104fd306cSNickeau            throw new ExceptionBadSyntax("The $actual URL string ($actual) is not valid. Error: {$e->getMessage()}");
18204fd306cSNickeau        }
18304fd306cSNickeau        $url->equals($urlActual);
18404fd306cSNickeau
18504fd306cSNickeau    }
18604fd306cSNickeau
18704fd306cSNickeau    /**
18804fd306cSNickeau     * Merge class name
18904fd306cSNickeau     * @param string $newNames - the name that we want to add
19004fd306cSNickeau     * @param ?string $actualNames - the actual names
19104fd306cSNickeau     * @return string - the class name list
19204fd306cSNickeau     *
19304fd306cSNickeau     * for instance:
19404fd306cSNickeau     *   * newNames = foo blue
19504fd306cSNickeau     *   * actual Name = foo bar
19604fd306cSNickeau     * return
19704fd306cSNickeau     *   * foo bar blue
19804fd306cSNickeau     */
19904fd306cSNickeau    public static function mergeClassNames(string $newNames, ?string $actualNames): string
20004fd306cSNickeau    {
20104fd306cSNickeau        /**
20204fd306cSNickeau         * It may be in the form "value1 value2"
20304fd306cSNickeau         */
20404fd306cSNickeau        $newValues = StringUtility::explodeAndTrim($newNames, " ");
20504fd306cSNickeau        if (!empty($actualNames)) {
20604fd306cSNickeau            $actualValues = StringUtility::explodeAndTrim(trim($actualNames), " ");
20704fd306cSNickeau        } else {
20804fd306cSNickeau            $actualValues = [];
20904fd306cSNickeau        }
21004fd306cSNickeau        $newValues = array_merge($actualValues, $newValues);
21104fd306cSNickeau        $newValues = array_unique($newValues);
21204fd306cSNickeau        return implode(" ", $newValues);
21304fd306cSNickeau    }
21404fd306cSNickeau
21504fd306cSNickeau    /**
21604fd306cSNickeau     * @param array $styleProperties - an array of CSS properties with key, value
21704fd306cSNickeau     * @return string - the value for the style attribute (ie all rules where joined with the comma)
21804fd306cSNickeau     */
21904fd306cSNickeau    public static function array2InlineStyle(array $styleProperties)
22004fd306cSNickeau    {
22104fd306cSNickeau        $inlineCss = "";
22204fd306cSNickeau        foreach ($styleProperties as $key => $value) {
22304fd306cSNickeau            $inlineCss .= "$key:$value;";
22404fd306cSNickeau        }
22504fd306cSNickeau        // Suppress the last ;
22604fd306cSNickeau        if ($inlineCss[strlen($inlineCss) - 1] == ";") {
22704fd306cSNickeau            $inlineCss = substr($inlineCss, 0, -1);
22804fd306cSNickeau        }
22904fd306cSNickeau        return $inlineCss;
23004fd306cSNickeau    }
231c3437056SNickeau}
232