file Unicode.php

Namespace

Drupal\bootstrap\Utility
  1. <?php
  2. namespace Drupal\bootstrap\Utility;
  3. use Drupal\bootstrap\Bootstrap;
  4. use Drupal\Component\Utility\Unicode as CoreUnicode;
  5. use Drupal\Component\Utility\Xss;
  6. /**
  7. * Extends \Drupal\Component\Utility\Unicode.
  8. *
  9. * @ingroup utility
  10. */
  11. class Unicode extends CoreUnicode {
  12. /**
  13. * Casts a value to a string, recursively if an array.
  14. *
  15. * @param mixed $value
  16. * Any value.
  17. * @param string $delimiter
  18. * The delimiter to use when joining multiple items in an array.
  19. *
  20. * @return string
  21. * The cast string.
  22. */
  23. public static function castToString($value = NULL, $delimiter = '.') {
  24. if (is_object($value) && method_exists($value, '__toString')) {
  25. return (string) ($value->__toString() ?: '');
  26. }
  27. if (is_array($value)) {
  28. foreach ($value as $key => $item) {
  29. $value[$key] = static::castToString($item, $delimiter);
  30. }
  31. return implode($delimiter, array_filter($value));
  32. }
  33. // Handle scalar values.
  34. if (isset($value) && is_scalar($value) && !is_bool($value)) {
  35. return (string) $value;
  36. }
  37. return '';
  38. }
  39. /**
  40. * Extracts the hook name from a function name.
  41. *
  42. * @param string $string
  43. * The function name to extract the hook name from.
  44. * @param string $suffix
  45. * A suffix hook ending (like "alter") to also remove.
  46. * @param string $prefix
  47. * A prefix hook beginning (like "form") to also remove.
  48. *
  49. * @return string
  50. * The extracted hook name.
  51. */
  52. public static function extractHook($string, $suffix = NULL, $prefix = NULL) {
  53. $regex = '^(' . implode('|', array_keys(Bootstrap::getTheme()->getAncestry())) . ')';
  54. $regex .= $prefix ? '_' . $prefix : '';
  55. $regex .= $suffix ? '_|_' . $suffix . '$' : '';
  56. return preg_replace("/$regex/", '', $string);
  57. }
  58. /**
  59. * Converts a callback to a string representation.
  60. *
  61. * @param array|string $callback
  62. * The callback to convert.
  63. * @param bool $array
  64. * Flag determining whether or not to convert the callback to an array.
  65. *
  66. * @return string
  67. * The converted callback as a string or an array if $array is specified.
  68. *
  69. * @see \Drupal\bootstrap\Bootstrap::addCallback()
  70. */
  71. public static function convertCallback($callback, $array = FALSE) {
  72. if (is_array($callback)) {
  73. if (is_object($callback[0])) {
  74. $callback[0] = get_class($callback[0]);
  75. }
  76. $callback = implode('::', $callback);
  77. }
  78. if ($callback[0] === '\\') {
  79. $callback = static::substr($callback, 1);
  80. }
  81. if ($array && static::strpos($callback, '::') !== FALSE) {
  82. $callback = explode('::', $callback);
  83. }
  84. return $callback;
  85. }
  86. /**
  87. * Escapes a delimiter in a string.
  88. *
  89. * Note: this is primarily useful in situations where dot notation is used
  90. * where the values also contain dots, like in a semantic version string.
  91. *
  92. * @param string $string
  93. * The string to search in.
  94. * @param string $delimiter
  95. * The delimiter to escape.
  96. *
  97. * @return string
  98. * The escaped string.
  99. *
  100. * @see \Drupal\bootstrap\Utility\Unicode::splitDelimiter()
  101. */
  102. public static function escapeDelimiter($string, $delimiter = '.') {
  103. return str_replace($delimiter, "\\$delimiter", $string);
  104. }
  105. /**
  106. * Determines if a string of text is considered "simple".
  107. *
  108. * @param string $string
  109. * The string of text to check "simple" criteria on.
  110. * @param int|false $length
  111. * The length of characters used to determine whether or not $string is
  112. * considered "simple". Set explicitly to FALSE to disable this criteria.
  113. * @param array|false $allowed_tags
  114. * An array of allowed tag elements. Set explicitly to FALSE to disable this
  115. * criteria.
  116. * @param bool $html
  117. * A variable, passed by reference, that indicates whether or not the
  118. * string contains HTML.
  119. *
  120. * @return bool
  121. * Returns TRUE if the $string is considered "simple", FALSE otherwise.
  122. */
  123. public static function isSimple($string, $length = 250, $allowed_tags = NULL, &$html = FALSE) {
  124. // Typecast to a string (if an object).
  125. $string_clone = (string) $string;
  126. // Use the advanced drupal_static() pattern.
  127. static $drupal_static_fast;
  128. if (!isset($drupal_static_fast)) {
  129. $drupal_static_fast['strings'] = &drupal_static(__METHOD__);
  130. }
  131. $strings = &$drupal_static_fast['strings'];
  132. if (!isset($strings[$string_clone])) {
  133. $plain_string = strip_tags($string_clone);
  134. $simple = TRUE;
  135. if ($allowed_tags !== FALSE) {
  136. $filtered_string = Xss::filter($string_clone, $allowed_tags);
  137. $html = $filtered_string !== $plain_string;
  138. $simple = $simple && $string_clone === $filtered_string;
  139. }
  140. if ($length !== FALSE) {
  141. $simple = $simple && strlen($plain_string) <= intval($length);
  142. }
  143. $strings[$string_clone] = $simple;
  144. }
  145. return $strings[$string_clone];
  146. }
  147. /**
  148. * Splits a string by a specified delimiter, allowing them to be escaped.
  149. *
  150. * Note: this is primarily useful in situations where dot notation is used
  151. * where the values also contain dots, like in a semantic version string.
  152. *
  153. * @param string $string
  154. * The string to split into parts.
  155. * @param string $delimiter
  156. * The delimiter used to split the string.
  157. * @param bool $escapable
  158. * Flag indicating whether the $delimiter can be escaped using a backward
  159. * slash (\).
  160. *
  161. * @return array
  162. * An array of strings, split where the specified $delimiter was present.
  163. *
  164. * @see \Drupal\bootstrap\Utility\Unicode::escapeDelimiter()
  165. * @see https://stackoverflow.com/a/6243797
  166. */
  167. public static function splitDelimiter($string, $delimiter = '.', $escapable = TRUE) {
  168. if (!$escapable) {
  169. return explode($delimiter, $string);
  170. }
  171. // Split based on delimiter.
  172. $parts = preg_split('~\\\\' . preg_quote($delimiter, '~') . '(*SKIP)(*FAIL)|\.~s', $string);
  173. // Iterate over the parts and remove backslashes from delimiters.
  174. return array_map(function ($string) use ($delimiter) {
  175. return str_replace("\\$delimiter", $delimiter, $string);
  176. }, $parts);
  177. }
  178. /**
  179. * Finds the position of the first occurrence of a string in another string.
  180. *
  181. * @param string $haystack
  182. * The string to search in.
  183. * @param string $needle
  184. * The string to find in $haystack.
  185. * @param int $offset
  186. * If specified, start the search at this number of characters from the
  187. * beginning (default 0).
  188. *
  189. * @return int|false
  190. * The position where $needle occurs in $haystack, always relative to the
  191. * beginning (independent of $offset), or FALSE if not found. Note that
  192. * a return value of 0 is not the same as FALSE.
  193. *
  194. * @deprecated in bootstrap:8.x-3.22 and is removed from bootstrap:5.0.0.
  195. * Use mb_strpos() instead.
  196. * @see https://www.drupal.org/project/bootstrap/issues/3096963
  197. *
  198. * @see https://www.drupal.org/node/2850048
  199. */
  200. public static function strpos($haystack, $needle, $offset = 0) {
  201. @trigger_error('\Drupal\bootstrap\Utility\Unicode::strpos() is deprecated in bootstrap:8.x-3.22 and will be removed before bootstrap:5.0.0. Use mb_strpos() instead. See https://www.drupal.org/project/bootstrap/issues/3096963.', E_USER_DEPRECATED);
  202. if (static::getStatus() == static::STATUS_MULTIBYTE) {
  203. return mb_strpos($haystack, $needle, $offset);
  204. }
  205. else {
  206. // Remove Unicode continuation characters, to be compatible with
  207. // Unicode::strlen() and Unicode::substr().
  208. $haystack = preg_replace("/[\x80-\xBF]/", '', $haystack);
  209. $needle = preg_replace("/[\x80-\xBF]/", '', $needle);
  210. return strpos($haystack, $needle, $offset);
  211. }
  212. }
  213. /**
  214. * Converts a UTF-8 string to lowercase.
  215. *
  216. * @param string $text
  217. * The string to run the operation on.
  218. *
  219. * @return string
  220. * The string in lowercase.
  221. *
  222. * @deprecated in bootstrap:8.x-3.22 and is removed from bootstrap:5.0.0.
  223. * Use mb_strtolower() instead.
  224. * @see https://www.drupal.org/project/bootstrap/issues/3096963
  225. *
  226. * @see https://www.drupal.org/node/2850048
  227. */
  228. public static function strtolower($text) {
  229. @trigger_error('\Drupal\bootstrap\Utility\Unicode::strtolower() is deprecated in bootstrap:8.x-3.22 and will be removed before bootstrap:5.0.0. Use mb_strtolower() instead. See https://www.drupal.org/project/bootstrap/issues/3096963.', E_USER_DEPRECATED);
  230. if (static::getStatus() == static::STATUS_MULTIBYTE) {
  231. return mb_strtolower($text);
  232. }
  233. else {
  234. // Use C-locale for ASCII-only lowercase.
  235. $text = strtolower($text);
  236. // Case flip Latin-1 accented letters.
  237. $text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
  238. return $text;
  239. }
  240. }
  241. /**
  242. * Cuts off a piece of a string based on character indices and counts.
  243. *
  244. * Follows the same behavior as PHP's own substr() function. Note that for
  245. * cutting off a string at a known character/substring location, the usage of
  246. * PHP's normal strpos/substr is safe and much faster.
  247. *
  248. * @param string $text
  249. * The input string.
  250. * @param int $start
  251. * The position at which to start reading.
  252. * @param int $length
  253. * The number of characters to read.
  254. *
  255. * @return string
  256. * The shortened string.
  257. *
  258. * @deprecated in bootstrap:8.x-3.22 and is removed from bootstrap:5.0.0.
  259. * Use mb_substr() instead.
  260. * @see https://www.drupal.org/project/bootstrap/issues/3096963
  261. *
  262. * @see https://www.drupal.org/node/2850048
  263. */
  264. public static function substr($text, $start, $length = NULL) {
  265. @trigger_error('\Drupal\bootstrap\Utility\Unicode::substr() is deprecated in bootstrap:8.x-3.22 and will be removed before bootstrap:5.0.0. Use mb_substr() instead. See https://www.drupal.org/project/bootstrap/issues/3096963.', E_USER_DEPRECATED);
  266. if (static::getStatus() == static::STATUS_MULTIBYTE) {
  267. return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
  268. }
  269. else {
  270. $strlen = strlen($text);
  271. // Find the starting byte offset.
  272. $bytes = 0;
  273. if ($start > 0) {
  274. // Count all the characters except continuation bytes from the start
  275. // until we have found $start characters or the end of the string.
  276. $bytes = -1; $chars = -1;
  277. while ($bytes < $strlen - 1 && $chars < $start) {
  278. $bytes++;
  279. $c = ord($text[$bytes]);
  280. if ($c < 0x80 || $c >= 0xC0) {
  281. $chars++;
  282. }
  283. }
  284. }
  285. elseif ($start < 0) {
  286. // Count all the characters except continuation bytes from the end
  287. // until we have found abs($start) characters.
  288. $start = abs($start);
  289. $bytes = $strlen; $chars = 0;
  290. while ($bytes > 0 && $chars < $start) {
  291. $bytes--;
  292. $c = ord($text[$bytes]);
  293. if ($c < 0x80 || $c >= 0xC0) {
  294. $chars++;
  295. }
  296. }
  297. }
  298. $istart = $bytes;
  299. // Find the ending byte offset.
  300. if ($length === NULL) {
  301. $iend = $strlen;
  302. }
  303. elseif ($length > 0) {
  304. // Count all the characters except continuation bytes from the starting
  305. // index until we have found $length characters or reached the end of
  306. // the string, then backtrace one byte.
  307. $iend = $istart - 1;
  308. $chars = -1;
  309. $last_real = FALSE;
  310. while ($iend < $strlen - 1 && $chars < $length) {
  311. $iend++;
  312. $c = ord($text[$iend]);
  313. $last_real = FALSE;
  314. if ($c < 0x80 || $c >= 0xC0) {
  315. $chars++;
  316. $last_real = TRUE;
  317. }
  318. }
  319. // Backtrace one byte if the last character we found was a real
  320. // character and we don't need it.
  321. if ($last_real && $chars >= $length) {
  322. $iend--;
  323. }
  324. }
  325. elseif ($length < 0) {
  326. // Count all the characters except continuation bytes from the end
  327. // until we have found abs($start) characters, then backtrace one byte.
  328. $length = abs($length);
  329. $iend = $strlen; $chars = 0;
  330. while ($iend > 0 && $chars < $length) {
  331. $iend--;
  332. $c = ord($text[$iend]);
  333. if ($c < 0x80 || $c >= 0xC0) {
  334. $chars++;
  335. }
  336. }
  337. // Backtrace one byte if we are not at the beginning of the string.
  338. if ($iend > 0) {
  339. $iend--;
  340. }
  341. }
  342. else {
  343. // $length == 0, return an empty string.
  344. return '';
  345. }
  346. return substr($text, $istart, max(0, $iend - $istart + 1));
  347. }
  348. }
  349. }

Classes