PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/classes/text.php

http://github.com/moodle/moodle
PHP | 778 lines | 456 code | 74 blank | 248 comment | 79 complexity | 40ceef40e8f8c32ac45780e25d0b7400 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Defines string apis
  18. *
  19. * @package core
  20. * @copyright (C) 2001-3001 Eloy Lafuente (stronk7) {@link http://contiento.com}
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. /**
  25. * defines string api's for manipulating strings
  26. *
  27. * This class is used to manipulate strings under Moodle 1.6 an later. As
  28. * utf-8 text become mandatory a pool of safe functions under this encoding
  29. * become necessary. The name of the methods is exactly the
  30. * same than their PHP originals.
  31. *
  32. * A big part of this class acts as a wrapper over the Typo3 charset library,
  33. * really a cool group of utilities to handle texts and encoding conversion.
  34. *
  35. * Take a look to its own copyright and license details.
  36. *
  37. * IMPORTANT Note: Typo3 libraries always expect lowercase charsets to use 100%
  38. * its capabilities so, don't forget to make the conversion
  39. * from every wrapper function!
  40. *
  41. * @package core
  42. * @category string
  43. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  44. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45. */
  46. class core_text {
  47. /**
  48. * @var string[] Array of strings representing Unicode non-characters
  49. */
  50. protected static $noncharacters;
  51. /**
  52. * Return t3lib helper class, which is used for conversion between charsets
  53. *
  54. * @param bool $reset
  55. * @return t3lib_cs
  56. */
  57. protected static function typo3($reset = false) {
  58. static $typo3cs = null;
  59. if ($reset) {
  60. $typo3cs = null;
  61. return null;
  62. }
  63. if (isset($typo3cs)) {
  64. return $typo3cs;
  65. }
  66. global $CFG;
  67. // Required files
  68. require_once($CFG->libdir.'/typo3/class.t3lib_cs.php');
  69. require_once($CFG->libdir.'/typo3/class.t3lib_div.php');
  70. require_once($CFG->libdir.'/typo3/interface.t3lib_singleton.php');
  71. require_once($CFG->libdir.'/typo3/class.t3lib_l10n_locales.php');
  72. // do not use mbstring or recode because it may return invalid results in some corner cases
  73. $GLOBALS['TYPO3_CONF_VARS']['SYS']['t3lib_cs_convMethod'] = 'iconv';
  74. $GLOBALS['TYPO3_CONF_VARS']['SYS']['t3lib_cs_utils'] = 'iconv';
  75. // Tell Typo3 we are curl enabled always (mandatory since 2.0)
  76. $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] = '1';
  77. // And this directory must exist to allow Typo to cache conversion
  78. // tables when using internal functions
  79. make_temp_directory('typo3temp/cs');
  80. // Make sure typo is using our dir permissions
  81. $GLOBALS['TYPO3_CONF_VARS']['BE']['folderCreateMask'] = decoct($CFG->directorypermissions);
  82. // Default mask for Typo
  83. $GLOBALS['TYPO3_CONF_VARS']['BE']['fileCreateMask'] = decoct($CFG->filepermissions);
  84. // This full path constants must be defined too, transforming backslashes
  85. // to forward slashed because Typo3 requires it.
  86. if (!defined('PATH_t3lib')) {
  87. define('PATH_t3lib', str_replace('\\','/',$CFG->libdir.'/typo3/'));
  88. define('PATH_typo3', str_replace('\\','/',$CFG->libdir.'/typo3/'));
  89. define('PATH_site', str_replace('\\','/',$CFG->tempdir.'/'));
  90. define('TYPO3_OS', stristr(PHP_OS,'win')&&!stristr(PHP_OS,'darwin')?'WIN':'');
  91. }
  92. $typo3cs = new t3lib_cs();
  93. return $typo3cs;
  94. }
  95. /**
  96. * Reset internal textlib caches.
  97. * @static
  98. */
  99. public static function reset_caches() {
  100. self::typo3(true);
  101. }
  102. /**
  103. * Standardise charset name
  104. *
  105. * Please note it does not mean the returned charset is actually supported.
  106. *
  107. * @static
  108. * @param string $charset raw charset name
  109. * @return string normalised lowercase charset name
  110. */
  111. public static function parse_charset($charset) {
  112. $charset = strtolower($charset);
  113. // shortcuts so that we do not have to load typo3 on every page
  114. if ($charset === 'utf8' or $charset === 'utf-8') {
  115. return 'utf-8';
  116. }
  117. if (preg_match('/^(cp|win|windows)-?(12[0-9]{2})$/', $charset, $matches)) {
  118. return 'windows-'.$matches[2];
  119. }
  120. if (preg_match('/^iso-8859-[0-9]+$/', $charset, $matches)) {
  121. return $charset;
  122. }
  123. if ($charset === 'euc-jp') {
  124. return 'euc-jp';
  125. }
  126. if ($charset === 'iso-2022-jp') {
  127. return 'iso-2022-jp';
  128. }
  129. if ($charset === 'shift-jis' or $charset === 'shift_jis') {
  130. return 'shift_jis';
  131. }
  132. if ($charset === 'gb2312') {
  133. return 'gb2312';
  134. }
  135. if ($charset === 'gb18030') {
  136. return 'gb18030';
  137. }
  138. // fallback to typo3
  139. return self::typo3()->parse_charset($charset);
  140. }
  141. /**
  142. * Converts the text between different encodings. It uses iconv extension with //TRANSLIT parameter,
  143. * falls back to typo3. If both source and target are utf-8 it tries to fix invalid characters only.
  144. *
  145. * @param string $text
  146. * @param string $fromCS source encoding
  147. * @param string $toCS result encoding
  148. * @return string|bool converted string or false on error
  149. */
  150. public static function convert($text, $fromCS, $toCS='utf-8') {
  151. $fromCS = self::parse_charset($fromCS);
  152. $toCS = self::parse_charset($toCS);
  153. $text = (string)$text; // we can work only with strings
  154. if ($text === '') {
  155. return '';
  156. }
  157. if ($fromCS === 'utf-8') {
  158. $text = fix_utf8($text);
  159. if ($toCS === 'utf-8') {
  160. return $text;
  161. }
  162. }
  163. if ($toCS === 'ascii') {
  164. // Try to normalize the conversion a bit.
  165. $text = self::specialtoascii($text, $fromCS);
  166. }
  167. // Prevent any error notices, do not use //IGNORE so that we get
  168. // consistent result from Typo3 if iconv fails.
  169. $result = @iconv($fromCS, $toCS.'//TRANSLIT', $text);
  170. if ($result === false or $result === '') {
  171. // note: iconv is prone to return empty string when invalid char encountered, or false if encoding unsupported
  172. $oldlevel = error_reporting(E_PARSE);
  173. $result = self::typo3()->conv((string)$text, $fromCS, $toCS);
  174. error_reporting($oldlevel);
  175. }
  176. return $result;
  177. }
  178. /**
  179. * Multibyte safe substr() function, uses mbstring or iconv for UTF-8, falls back to typo3.
  180. *
  181. * @param string $text string to truncate
  182. * @param int $start negative value means from end
  183. * @param int $len maximum length of characters beginning from start
  184. * @param string $charset encoding of the text
  185. * @return string portion of string specified by the $start and $len
  186. */
  187. public static function substr($text, $start, $len=null, $charset='utf-8') {
  188. $charset = self::parse_charset($charset);
  189. if ($charset === 'utf-8') {
  190. if (function_exists('mb_substr')) {
  191. // this is much faster than iconv - see MDL-31142
  192. if ($len === null) {
  193. $oldcharset = mb_internal_encoding();
  194. mb_internal_encoding('UTF-8');
  195. $result = mb_substr($text, $start);
  196. mb_internal_encoding($oldcharset);
  197. return $result;
  198. } else {
  199. return mb_substr($text, $start, $len, 'UTF-8');
  200. }
  201. } else {
  202. if ($len === null) {
  203. $len = iconv_strlen($text, 'UTF-8');
  204. }
  205. return iconv_substr($text, $start, $len, 'UTF-8');
  206. }
  207. }
  208. $oldlevel = error_reporting(E_PARSE);
  209. if ($len === null) {
  210. $result = self::typo3()->substr($charset, (string)$text, $start);
  211. } else {
  212. $result = self::typo3()->substr($charset, (string)$text, $start, $len);
  213. }
  214. error_reporting($oldlevel);
  215. return $result;
  216. }
  217. /**
  218. * Truncates a string to no more than a certain number of bytes in a multi-byte safe manner.
  219. * UTF-8 only!
  220. *
  221. * Many of the other charsets we test for (like ISO-2022-JP and EUC-JP) are not supported
  222. * by typo3, and will give invalid results, so we are supporting UTF-8 only.
  223. *
  224. * @param string $string String to truncate
  225. * @param int $bytes Maximum length of bytes in the result
  226. * @return string Portion of string specified by $bytes
  227. * @since Moodle 3.1
  228. */
  229. public static function str_max_bytes($string, $bytes) {
  230. if (function_exists('mb_strcut')) {
  231. return mb_strcut($string, 0, $bytes, 'UTF-8');
  232. }
  233. $oldlevel = error_reporting(E_PARSE);
  234. $result = self::typo3()->strtrunc('utf-8', $string, $bytes);
  235. error_reporting($oldlevel);
  236. return $result;
  237. }
  238. /**
  239. * Finds the last occurrence of a character in a string within another.
  240. * UTF-8 ONLY safe mb_strrchr().
  241. *
  242. * @param string $haystack The string from which to get the last occurrence of needle.
  243. * @param string $needle The string to find in haystack.
  244. * @param boolean $part If true, returns the portion before needle, else return the portion after (including needle).
  245. * @return string|false False when not found.
  246. * @since Moodle 2.4.6, 2.5.2, 2.6
  247. */
  248. public static function strrchr($haystack, $needle, $part = false) {
  249. if (function_exists('mb_strrchr')) {
  250. return mb_strrchr($haystack, $needle, $part, 'UTF-8');
  251. }
  252. $pos = self::strrpos($haystack, $needle);
  253. if ($pos === false) {
  254. return false;
  255. }
  256. $length = null;
  257. if ($part) {
  258. $length = $pos;
  259. $pos = 0;
  260. }
  261. return self::substr($haystack, $pos, $length, 'utf-8');
  262. }
  263. /**
  264. * Multibyte safe strlen() function, uses mbstring or iconv for UTF-8, falls back to typo3.
  265. *
  266. * @param string $text input string
  267. * @param string $charset encoding of the text
  268. * @return int number of characters
  269. */
  270. public static function strlen($text, $charset='utf-8') {
  271. $charset = self::parse_charset($charset);
  272. if ($charset === 'utf-8') {
  273. if (function_exists('mb_strlen')) {
  274. return mb_strlen($text, 'UTF-8');
  275. } else {
  276. return iconv_strlen($text, 'UTF-8');
  277. }
  278. }
  279. $oldlevel = error_reporting(E_PARSE);
  280. $result = self::typo3()->strlen($charset, (string)$text);
  281. error_reporting($oldlevel);
  282. return $result;
  283. }
  284. /**
  285. * Multibyte safe strtolower() function, uses mbstring, falls back to typo3.
  286. *
  287. * @param string $text input string
  288. * @param string $charset encoding of the text (may not work for all encodings)
  289. * @return string lower case text
  290. */
  291. public static function strtolower($text, $charset='utf-8') {
  292. $charset = self::parse_charset($charset);
  293. if ($charset === 'utf-8' and function_exists('mb_strtolower')) {
  294. return mb_strtolower($text, 'UTF-8');
  295. }
  296. $oldlevel = error_reporting(E_PARSE);
  297. $result = self::typo3()->conv_case($charset, (string)$text, 'toLower');
  298. error_reporting($oldlevel);
  299. return $result;
  300. }
  301. /**
  302. * Multibyte safe strtoupper() function, uses mbstring, falls back to typo3.
  303. *
  304. * @param string $text input string
  305. * @param string $charset encoding of the text (may not work for all encodings)
  306. * @return string upper case text
  307. */
  308. public static function strtoupper($text, $charset='utf-8') {
  309. $charset = self::parse_charset($charset);
  310. if ($charset === 'utf-8' and function_exists('mb_strtoupper')) {
  311. return mb_strtoupper($text, 'UTF-8');
  312. }
  313. $oldlevel = error_reporting(E_PARSE);
  314. $result = self::typo3()->conv_case($charset, (string)$text, 'toUpper');
  315. error_reporting($oldlevel);
  316. return $result;
  317. }
  318. /**
  319. * Find the position of the first occurrence of a substring in a string.
  320. * UTF-8 ONLY safe strpos(), uses mbstring, falls back to iconv.
  321. *
  322. * @param string $haystack the string to search in
  323. * @param string $needle one or more charachters to search for
  324. * @param int $offset offset from begining of string
  325. * @return int the numeric position of the first occurrence of needle in haystack.
  326. */
  327. public static function strpos($haystack, $needle, $offset=0) {
  328. if (function_exists('mb_strpos')) {
  329. return mb_strpos($haystack, $needle, $offset, 'UTF-8');
  330. } else {
  331. return iconv_strpos($haystack, $needle, $offset, 'UTF-8');
  332. }
  333. }
  334. /**
  335. * Find the position of the last occurrence of a substring in a string
  336. * UTF-8 ONLY safe strrpos(), uses mbstring, falls back to iconv.
  337. *
  338. * @param string $haystack the string to search in
  339. * @param string $needle one or more charachters to search for
  340. * @return int the numeric position of the last occurrence of needle in haystack
  341. */
  342. public static function strrpos($haystack, $needle) {
  343. if (function_exists('mb_strrpos')) {
  344. return mb_strrpos($haystack, $needle, null, 'UTF-8');
  345. } else {
  346. return iconv_strrpos($haystack, $needle, 'UTF-8');
  347. }
  348. }
  349. /**
  350. * Reverse UTF-8 multibytes character sets (used for RTL languages)
  351. * (We only do this because there is no mb_strrev or iconv_strrev)
  352. *
  353. * @param string $str the multibyte string to reverse
  354. * @return string the reversed multi byte string
  355. */
  356. public static function strrev($str) {
  357. preg_match_all('/./us', $str, $ar);
  358. return join('', array_reverse($ar[0]));
  359. }
  360. /**
  361. * Try to convert upper unicode characters to plain ascii,
  362. * the returned string may contain unconverted unicode characters.
  363. *
  364. * @param string $text input string
  365. * @param string $charset encoding of the text
  366. * @return string converted ascii string
  367. */
  368. public static function specialtoascii($text, $charset='utf-8') {
  369. $charset = self::parse_charset($charset);
  370. $oldlevel = error_reporting(E_PARSE);
  371. $result = self::typo3()->specCharsToASCII($charset, (string)$text);
  372. error_reporting($oldlevel);
  373. return $result;
  374. }
  375. /**
  376. * Generate a correct base64 encoded header to be used in MIME mail messages.
  377. * This function seems to be 100% compliant with RFC1342. Credits go to:
  378. * paravoid (http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283).
  379. *
  380. * @param string $text input string
  381. * @param string $charset encoding of the text
  382. * @return string base64 encoded header
  383. */
  384. public static function encode_mimeheader($text, $charset='utf-8') {
  385. if (empty($text)) {
  386. return (string)$text;
  387. }
  388. // Normalize charset
  389. $charset = self::parse_charset($charset);
  390. // If the text is pure ASCII, we don't need to encode it
  391. if (self::convert($text, $charset, 'ascii') == $text) {
  392. return $text;
  393. }
  394. // Although RFC says that line feed should be \r\n, it seems that
  395. // some mailers double convert \r, so we are going to use \n alone
  396. $linefeed="\n";
  397. // Define start and end of every chunk
  398. $start = "=?$charset?B?";
  399. $end = "?=";
  400. // Accumulate results
  401. $encoded = '';
  402. // Max line length is 75 (including start and end)
  403. $length = 75 - strlen($start) - strlen($end);
  404. // Multi-byte ratio
  405. $multilength = self::strlen($text, $charset);
  406. // Detect if strlen and friends supported
  407. if ($multilength === false) {
  408. if ($charset == 'GB18030' or $charset == 'gb18030') {
  409. while (strlen($text)) {
  410. // try to encode first 22 chars - we expect most chars are two bytes long
  411. if (preg_match('/^(([\x00-\x7f])|([\x81-\xfe][\x40-\x7e])|([\x81-\xfe][\x80-\xfe])|([\x81-\xfe][\x30-\x39]..)){1,22}/m', $text, $matches)) {
  412. $chunk = $matches[0];
  413. $encchunk = base64_encode($chunk);
  414. if (strlen($encchunk) > $length) {
  415. // find first 11 chars - each char in 4 bytes - worst case scenario
  416. preg_match('/^(([\x00-\x7f])|([\x81-\xfe][\x40-\x7e])|([\x81-\xfe][\x80-\xfe])|([\x81-\xfe][\x30-\x39]..)){1,11}/m', $text, $matches);
  417. $chunk = $matches[0];
  418. $encchunk = base64_encode($chunk);
  419. }
  420. $text = substr($text, strlen($chunk));
  421. $encoded .= ' '.$start.$encchunk.$end.$linefeed;
  422. } else {
  423. break;
  424. }
  425. }
  426. $encoded = trim($encoded);
  427. return $encoded;
  428. } else {
  429. return false;
  430. }
  431. }
  432. $ratio = $multilength / strlen($text);
  433. // Base64 ratio
  434. $magic = $avglength = floor(3 * $length * $ratio / 4);
  435. // basic infinite loop protection
  436. $maxiterations = strlen($text)*2;
  437. $iteration = 0;
  438. // Iterate over the string in magic chunks
  439. for ($i=0; $i <= $multilength; $i+=$magic) {
  440. if ($iteration++ > $maxiterations) {
  441. return false; // probably infinite loop
  442. }
  443. $magic = $avglength;
  444. $offset = 0;
  445. // Ensure the chunk fits in length, reducing magic if necessary
  446. do {
  447. $magic -= $offset;
  448. $chunk = self::substr($text, $i, $magic, $charset);
  449. $chunk = base64_encode($chunk);
  450. $offset++;
  451. } while (strlen($chunk) > $length);
  452. // This chunk doesn't break any multi-byte char. Use it.
  453. if ($chunk)
  454. $encoded .= ' '.$start.$chunk.$end.$linefeed;
  455. }
  456. // Strip the first space and the last linefeed
  457. $encoded = substr($encoded, 1, -strlen($linefeed));
  458. return $encoded;
  459. }
  460. /**
  461. * Returns HTML entity transliteration table.
  462. * @return array with (html entity => utf-8) elements
  463. */
  464. protected static function get_entities_table() {
  465. static $trans_tbl = null;
  466. // Generate/create $trans_tbl
  467. if (!isset($trans_tbl)) {
  468. if (version_compare(phpversion(), '5.3.4') < 0) {
  469. $trans_tbl = array();
  470. foreach (get_html_translation_table(HTML_ENTITIES) as $val=>$key) {
  471. $trans_tbl[$key] = self::convert($val, 'ISO-8859-1', 'utf-8');
  472. }
  473. } else if (version_compare(phpversion(), '5.4.0') < 0) {
  474. $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'UTF-8');
  475. $trans_tbl = array_flip($trans_tbl);
  476. } else {
  477. $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML401, 'UTF-8');
  478. $trans_tbl = array_flip($trans_tbl);
  479. }
  480. }
  481. return $trans_tbl;
  482. }
  483. /**
  484. * Converts all the numeric entities &#nnnn; or &#xnnn; to UTF-8
  485. * Original from laurynas dot butkus at gmail at:
  486. * http://php.net/manual/en/function.html-entity-decode.php#75153
  487. * with some custom mods to provide more functionality
  488. *
  489. * @param string $str input string
  490. * @param boolean $htmlent convert also html entities (defaults to true)
  491. * @return string encoded UTF-8 string
  492. */
  493. public static function entities_to_utf8($str, $htmlent=true) {
  494. static $callback1 = null ;
  495. static $callback2 = null ;
  496. if (!$callback1 or !$callback2) {
  497. $callback1 = function($matches) {
  498. return core_text::code2utf8(hexdec($matches[1]));
  499. };
  500. $callback2 = function($matches) {
  501. return core_text::code2utf8($matches[1]);
  502. };
  503. }
  504. $result = (string)$str;
  505. $result = preg_replace_callback('/&#x([0-9a-f]+);/i', $callback1, $result);
  506. $result = preg_replace_callback('/&#([0-9]+);/', $callback2, $result);
  507. // Replace literal entities (if desired)
  508. if ($htmlent) {
  509. $trans_tbl = self::get_entities_table();
  510. // It should be safe to search for ascii strings and replace them with utf-8 here.
  511. $result = strtr($result, $trans_tbl);
  512. }
  513. // Return utf8-ised string
  514. return $result;
  515. }
  516. /**
  517. * Converts all Unicode chars > 127 to numeric entities &#nnnn; or &#xnnn;.
  518. *
  519. * @param string $str input string
  520. * @param boolean $dec output decadic only number entities
  521. * @param boolean $nonnum remove all non-numeric entities
  522. * @return string converted string
  523. */
  524. public static function utf8_to_entities($str, $dec=false, $nonnum=false) {
  525. static $callback = null ;
  526. if ($nonnum) {
  527. $str = self::entities_to_utf8($str, true);
  528. }
  529. // Avoid some notices from Typo3 code
  530. $oldlevel = error_reporting(E_PARSE);
  531. $result = self::typo3()->utf8_to_entities((string)$str);
  532. error_reporting($oldlevel);
  533. if ($dec) {
  534. if (!$callback) {
  535. $callback = function($matches) {
  536. return '&#' . hexdec($matches[1]) . ';';
  537. };
  538. }
  539. $result = preg_replace_callback('/&#x([0-9a-f]+);/i', $callback, $result);
  540. }
  541. return $result;
  542. }
  543. /**
  544. * Removes the BOM from unicode string {@link http://unicode.org/faq/utf_bom.html}
  545. *
  546. * @param string $str input string
  547. * @return string
  548. */
  549. public static function trim_utf8_bom($str) {
  550. $bom = "\xef\xbb\xbf";
  551. if (strpos($str, $bom) === 0) {
  552. return substr($str, strlen($bom));
  553. }
  554. return $str;
  555. }
  556. /**
  557. * There are a number of Unicode non-characters including the byte-order mark (which may appear
  558. * multiple times in a string) and also other ranges. These can cause problems for some
  559. * processing.
  560. *
  561. * This function removes the characters using string replace, so that the rest of the string
  562. * remains unchanged.
  563. *
  564. * @param string $value Input string
  565. * @return string Cleaned string value
  566. * @since Moodle 3.5
  567. */
  568. public static function remove_unicode_non_characters($value) {
  569. // Set up list of all Unicode non-characters for fast replacing.
  570. if (!self::$noncharacters) {
  571. self::$noncharacters = [];
  572. // This list of characters is based on the Unicode standard. It includes the last two
  573. // characters of each code planes 0-16 inclusive...
  574. for ($plane = 0; $plane <= 16; $plane++) {
  575. $base = ($plane === 0 ? '' : dechex($plane));
  576. self::$noncharacters[] = html_entity_decode('&#x' . $base . 'fffe;');
  577. self::$noncharacters[] = html_entity_decode('&#x' . $base . 'ffff;');
  578. }
  579. // ...And the character range U+FDD0 to U+FDEF.
  580. for ($char = 0xfdd0; $char <= 0xfdef; $char++) {
  581. self::$noncharacters[] = html_entity_decode('&#x' . dechex($char) . ';');
  582. }
  583. }
  584. // Do character replacement.
  585. return str_replace(self::$noncharacters, '', $value);
  586. }
  587. /**
  588. * Returns encoding options for select boxes, utf-8 and platform encoding first
  589. *
  590. * @return array encodings
  591. */
  592. public static function get_encodings() {
  593. $encodings = array();
  594. $encodings['UTF-8'] = 'UTF-8';
  595. $winenc = strtoupper(get_string('localewincharset', 'langconfig'));
  596. if ($winenc != '') {
  597. $encodings[$winenc] = $winenc;
  598. }
  599. $nixenc = strtoupper(get_string('oldcharset', 'langconfig'));
  600. $encodings[$nixenc] = $nixenc;
  601. foreach (self::typo3()->synonyms as $enc) {
  602. $enc = strtoupper($enc);
  603. $encodings[$enc] = $enc;
  604. }
  605. return $encodings;
  606. }
  607. /**
  608. * Returns the utf8 string corresponding to the unicode value
  609. * (from php.net, courtesy - romans@void.lv)
  610. *
  611. * @param int $num one unicode value
  612. * @return string the UTF-8 char corresponding to the unicode value
  613. */
  614. public static function code2utf8($num) {
  615. if ($num < 128) {
  616. return chr($num);
  617. }
  618. if ($num < 2048) {
  619. return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
  620. }
  621. if ($num < 65536) {
  622. return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
  623. }
  624. if ($num < 2097152) {
  625. return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
  626. }
  627. return '';
  628. }
  629. /**
  630. * Returns the code of the given UTF-8 character
  631. *
  632. * @param string $utf8char one UTF-8 character
  633. * @return int the code of the given character
  634. */
  635. public static function utf8ord($utf8char) {
  636. if ($utf8char == '') {
  637. return 0;
  638. }
  639. $ord0 = ord($utf8char[0]);
  640. if ($ord0 >= 0 && $ord0 <= 127) {
  641. return $ord0;
  642. }
  643. $ord1 = ord($utf8char[1]);
  644. if ($ord0 >= 192 && $ord0 <= 223) {
  645. return ($ord0 - 192) * 64 + ($ord1 - 128);
  646. }
  647. $ord2 = ord($utf8char[2]);
  648. if ($ord0 >= 224 && $ord0 <= 239) {
  649. return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
  650. }
  651. $ord3 = ord($utf8char[3]);
  652. if ($ord0 >= 240 && $ord0 <= 247) {
  653. return ($ord0 - 240) * 262144 + ($ord1 - 128 )* 4096 + ($ord2 - 128) * 64 + ($ord3 - 128);
  654. }
  655. return false;
  656. }
  657. /**
  658. * Makes first letter of each word capital - words must be separated by spaces.
  659. * Use with care, this function does not work properly in many locales!!!
  660. *
  661. * @param string $text input string
  662. * @return string
  663. */
  664. public static function strtotitle($text) {
  665. if (empty($text)) {
  666. return $text;
  667. }
  668. if (function_exists('mb_convert_case')) {
  669. return mb_convert_case($text, MB_CASE_TITLE, 'UTF-8');
  670. }
  671. $text = self::strtolower($text);
  672. $words = explode(' ', $text);
  673. foreach ($words as $i=>$word) {
  674. $length = self::strlen($word);
  675. if (!$length) {
  676. continue;
  677. } else if ($length == 1) {
  678. $words[$i] = self::strtoupper($word);
  679. } else {
  680. $letter = self::substr($word, 0, 1);
  681. $letter = self::strtoupper($letter);
  682. $rest = self::substr($word, 1);
  683. $words[$i] = $letter.$rest;
  684. }
  685. }
  686. return implode(' ', $words);
  687. }
  688. }