1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Core\Utility;
17
18/**
19 * Class with helper functions for string handling
20 */
21class StringUtility
22{
23    /**
24     * Returns TRUE if $haystack begins with $needle.
25     * The input string is not trimmed before and search is done case sensitive.
26     *
27     * @param string $haystack Full string to check
28     * @param string $needle Reference string which must be found as the "first part" of the full string
29     * @throws \InvalidArgumentException
30     * @return bool TRUE if $needle was found to be equal to the first part of $haystack
31     */
32    public static function beginsWith($haystack, $needle)
33    {
34        // Sanitize $haystack and $needle
35        if (is_array($haystack) || is_object($haystack) || $haystack === null || (string)$haystack != $haystack) {
36            throw new \InvalidArgumentException(
37                '$haystack can not be interpreted as string',
38                1347135546
39            );
40        }
41        if (is_array($needle) || is_object($needle) || (string)$needle != $needle || strlen($needle) < 1) {
42            throw new \InvalidArgumentException(
43                '$needle can not be interpreted as string or has zero length',
44                1347135547
45            );
46        }
47        $haystack = (string)$haystack;
48        $needle = (string)$needle;
49        return $needle !== '' && strpos($haystack, $needle) === 0;
50    }
51
52    /**
53     * Returns TRUE if $haystack ends with $needle.
54     * The input string is not trimmed before and search is done case sensitive.
55     *
56     * @param string $haystack Full string to check
57     * @param string $needle Reference string which must be found as the "last part" of the full string
58     * @throws \InvalidArgumentException
59     * @return bool TRUE if $needle was found to be equal to the last part of $haystack
60     */
61    public static function endsWith($haystack, $needle)
62    {
63        // Sanitize $haystack and $needle
64        if (is_array($haystack) || is_object($haystack) || $haystack === null || (string)$haystack != $haystack) {
65            throw new \InvalidArgumentException(
66                '$haystack can not be interpreted as string',
67                1347135544
68            );
69        }
70        if (is_array($needle) || is_object($needle) || (string)$needle != $needle || strlen($needle) < 1) {
71            throw new \InvalidArgumentException(
72                '$needle can not be interpreted as string or has no length',
73                1347135545
74            );
75        }
76        $haystackLength = strlen($haystack);
77        $needleLength = strlen($needle);
78        if (!$haystackLength || $needleLength > $haystackLength) {
79            return false;
80        }
81        $position = strrpos((string)$haystack, (string)$needle);
82        return $position !== false && $position === $haystackLength - $needleLength;
83    }
84
85    /**
86     * This function generates a unique id by using the more entropy parameter.
87     * Furthermore the dots are removed so the id can be used inside HTML attributes e.g. id.
88     *
89     * @param string $prefix
90     * @return string
91     */
92    public static function getUniqueId($prefix = '')
93    {
94        $uniqueId = uniqid($prefix, true);
95        return str_replace('.', '', $uniqueId);
96    }
97
98    /**
99     * Escape a CSS selector to be used for DOM queries
100     *
101     * This method takes care to escape any CSS selector meta character.
102     * The result may be used to query the DOM like $('#' + escapedSelector)
103     *
104     * @param string $selector
105     * @return string
106     */
107    public static function escapeCssSelector(string $selector): string
108    {
109        return preg_replace('/([#:.\\[\\],=@])/', '\\\\$1', $selector);
110    }
111
112    /**
113     * Removes the Byte Order Mark (BOM) from the input string. This method supports UTF-8 encoded strings only!
114     *
115     * @param string $input
116     * @return string
117     */
118    public static function removeByteOrderMark(string $input): string
119    {
120        if (strpos($input, "\xef\xbb\xbf") === 0) {
121            $input = substr($input, 3);
122        }
123
124        return $input;
125    }
126
127    /**
128     * Matching two strings against each other, supporting a "*" wildcard (match many) or a "?" wildcard (match one= or (if wrapped in "/") PCRE regular expressions
129     *
130     * @param string $haystack The string in which to find $needle.
131     * @param string $needle The string to find in $haystack
132     * @return bool Returns TRUE if $needle matches or is found in (according to wildcards) $haystack. E.g. if $haystack is "Netscape 6.5" and $needle is "Net*" or "Net*ape" then it returns TRUE.
133     */
134    public static function searchStringWildcard($haystack, $needle): bool
135    {
136        $result = false;
137        if ($haystack === $needle) {
138            $result = true;
139        } elseif ($needle) {
140            if (preg_match('/^\\/.+\\/$/', $needle)) {
141                // Regular expression, only "//" is allowed as delimiter
142                $regex = $needle;
143            } else {
144                $needle = str_replace(['*', '?'], ['%%%MANY%%%', '%%%ONE%%%'], $needle);
145                $regex = '/^' . preg_quote($needle, '/') . '$/';
146                // Replace the marker with .* to match anything (wildcard)
147                $regex = str_replace(['%%%MANY%%%', '%%%ONE%%%'], ['.*', '.'], $regex);
148            }
149            $result = (bool)preg_match($regex, $haystack);
150        }
151        return $result;
152    }
153
154    /**
155     * Works the same as str_pad() except that it correctly handles strings with multibyte characters
156     * and takes an additional optional argument $encoding.
157     *
158     * @param string $string
159     * @param int $length
160     * @param string $pad_string
161     * @param int $pad_type
162     * @param string $encoding
163     * @return string
164     */
165    public static function multibyteStringPad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, string $encoding = 'UTF-8'): string
166    {
167        $len = mb_strlen($string, $encoding);
168        $pad_string_len = mb_strlen($pad_string, $encoding);
169        if ($len >= $length || $pad_string_len === 0) {
170            return $string;
171        }
172
173        switch ($pad_type) {
174            case STR_PAD_RIGHT:
175                $string .= str_repeat($pad_string, (int)(($length - $len)/$pad_string_len));
176                $string .= mb_substr($pad_string, 0, ($length - $len) % $pad_string_len);
177                return $string;
178
179            case STR_PAD_LEFT:
180                $leftPad = str_repeat($pad_string, (int)(($length - $len)/$pad_string_len));
181                $leftPad .= mb_substr($pad_string, 0, ($length - $len) % $pad_string_len);
182                return $leftPad . $string;
183
184            case STR_PAD_BOTH:
185                $leftPadCount = (int)(($length - $len)/2);
186                $len += $leftPadCount;
187                $padded = ((int)($leftPadCount / $pad_string_len)) * $pad_string_len;
188                $leftPad = str_repeat($pad_string, (int)($leftPadCount / $pad_string_len));
189                $leftPad .= mb_substr($pad_string, 0, $leftPadCount - $padded);
190                $string = $leftPad . $string . str_repeat($pad_string, ($length - $len)/$pad_string_len);
191                $string .= mb_substr($pad_string, 0, ($length - $len) % $pad_string_len);
192                return $string;
193        }
194        return $string;
195    }
196}
197