text.lib.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * This is the text library for Chamilo.
  5. * It is loaded during the global initialization,
  6. * so the functions below are available everywhere.
  7. *
  8. * @package chamilo.library
  9. */
  10. define('EXERCISE_NUMBER_OF_DECIMALS', 2);
  11. /**
  12. * This function strips all html-tags found in the input string and outputs a pure text.
  13. * Mostly, the function is to be used before language or encoding detection of the input string.
  14. *
  15. * @param string $string the input string with html-tags to be converted to plain text
  16. *
  17. * @return string the returned plain text as a result
  18. */
  19. function api_html_to_text($string)
  20. {
  21. // These purifications have been found experimentally, for nice looking output.
  22. $string = preg_replace('/<br[^>]*>/i', "\n", $string);
  23. $string = preg_replace('/<\/?(div|p|h[1-6]|table|ol|ul|blockquote)[^>]*>/i', "\n", $string);
  24. $string = preg_replace('/<\/(tr|li)[^>]*>/i', "\n", $string);
  25. $string = preg_replace('/<\/(td|th)[^>]*>/i', "\t", $string);
  26. $string = strip_tags($string);
  27. // Line endings unification and cleaning.
  28. $string = str_replace(["\r\n", "\n\r", "\r"], "\n", $string);
  29. $string = preg_replace('/\s*\n/', "\n", $string);
  30. $string = preg_replace('/\n+/', "\n", $string);
  31. return trim($string);
  32. }
  33. /**
  34. * Detects encoding of html-formatted text.
  35. *
  36. * @param string $string the input html-formatted text
  37. *
  38. * @return string returns the detected encoding
  39. */
  40. function api_detect_encoding_html($string)
  41. {
  42. if (@preg_match('/<head.*(<meta[^>]*content=[^>]*>).*<\/head>/si', $string, $matches)) {
  43. if (@preg_match('/<meta[^>]*charset=(.*)["\';][^>]*>/si', $matches[1], $matches)) {
  44. return api_refine_encoding_id(trim($matches[1]));
  45. }
  46. }
  47. return api_detect_encoding(api_html_to_text($string));
  48. }
  49. /**
  50. * Converts the text of a html-document to a given encoding, the meta-tag is changed accordingly.
  51. *
  52. * @param string $string the input full-html document
  53. * @param string the new encoding value to be set
  54. */
  55. function api_set_encoding_html(&$string, $encoding)
  56. {
  57. $old_encoding = api_detect_encoding_html($string);
  58. if (@preg_match('/(.*<head.*)(<meta[^>]*content=[^>]*>)(.*<\/head>.*)/si', $string, $matches)) {
  59. $meta = $matches[2];
  60. if (@preg_match("/(<meta[^>]*charset=)(.*)([\"';][^>]*>)/si", $meta, $matches1)) {
  61. $meta = $matches1[1].$encoding.$matches1[3];
  62. $string = $matches[1].$meta.$matches[3];
  63. } else {
  64. $string = $matches[1].'<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/>'.$matches[3];
  65. }
  66. } else {
  67. $count = 1;
  68. if (strpos('</head>', strtolower($string)) !== false) {
  69. $string = str_ireplace(
  70. '</head>',
  71. '<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/></head>',
  72. $string,
  73. $count
  74. );
  75. } else {
  76. $string = str_ireplace(
  77. '<body>',
  78. '<head><meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/></head><body>',
  79. $string,
  80. $count
  81. );
  82. }
  83. }
  84. $string = api_convert_encoding($string, $encoding, $old_encoding);
  85. }
  86. /**
  87. * Returns the title of a html document.
  88. *
  89. * @param string $string the contents of the input document
  90. * @param string $output_encoding The encoding of the retrieved title.
  91. * If the value is not set, the system encoding is assumed.
  92. * @param string $input_encoding The encoding of the input document. If the value is not set, it is detected.
  93. *
  94. * @return string the retrieved title, html-entities and extra-whitespace between the words are cleaned
  95. */
  96. function api_get_title_html(&$string, $output_encoding = null, $input_encoding = null)
  97. {
  98. if (@preg_match('/<head.+<title[^>]*>(.*)<\/title>/msi', $string, $matches)) {
  99. if (empty($output_encoding)) {
  100. $output_encoding = api_get_system_encoding();
  101. }
  102. if (empty($input_encoding)) {
  103. $input_encoding = api_detect_encoding_html($string);
  104. }
  105. return trim(
  106. @preg_replace(
  107. '/\s+/',
  108. ' ',
  109. api_html_entity_decode(
  110. api_convert_encoding($matches[1], $output_encoding, $input_encoding),
  111. ENT_QUOTES,
  112. $output_encoding
  113. )
  114. )
  115. );
  116. }
  117. return '';
  118. }
  119. /* XML processing functions */
  120. // A regular expression for accessing declared encoding within xml-formatted text.
  121. // Published by Steve Minutillo,
  122. // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss/
  123. define('_PCRE_XML_ENCODING', '/<\?xml.*encoding=[\'"](.*?)[\'"].*\?>/m');
  124. /**
  125. * Detects encoding of xml-formatted text.
  126. *
  127. * @param string $string the input xml-formatted text
  128. * @param string $default_encoding This is the default encoding to be returned
  129. * if there is no way the xml-text's encoding to be detected. If it not spesified, the system encoding is assumed then.
  130. *
  131. * @return string returns the detected encoding
  132. *
  133. * @todo The second parameter is to be eliminated. See api_detect_encoding_html().
  134. */
  135. function api_detect_encoding_xml($string, $default_encoding = null)
  136. {
  137. if (preg_match(_PCRE_XML_ENCODING, $string, $matches)) {
  138. return api_refine_encoding_id($matches[1]);
  139. }
  140. if (api_is_valid_utf8($string)) {
  141. return 'UTF-8';
  142. }
  143. if (empty($default_encoding)) {
  144. $default_encoding = _api_mb_internal_encoding();
  145. }
  146. return api_refine_encoding_id($default_encoding);
  147. }
  148. /**
  149. * Converts character encoding of a xml-formatted text.
  150. * If inside the text the encoding is declared, it is modified accordingly.
  151. *
  152. * @param string $string the text being converted
  153. * @param string $to_encoding the encoding that text is being converted to
  154. * @param string $from_encoding (optional) The encoding that text is being converted from.
  155. * If it is omited, it is tried to be detected then.
  156. *
  157. * @return string returns the converted xml-text
  158. */
  159. function api_convert_encoding_xml($string, $to_encoding, $from_encoding = null)
  160. {
  161. return _api_convert_encoding_xml($string, $to_encoding, $from_encoding);
  162. }
  163. /**
  164. * Converts character encoding of a xml-formatted text into UTF-8.
  165. * If inside the text the encoding is declared, it is set to UTF-8.
  166. *
  167. * @param string $string the text being converted
  168. * @param string $from_encoding (optional) The encoding that text is being converted from.
  169. * If it is omited, it is tried to be detected then.
  170. *
  171. * @return string returns the converted xml-text
  172. */
  173. function api_utf8_encode_xml($string, $from_encoding = null)
  174. {
  175. return _api_convert_encoding_xml($string, 'UTF-8', $from_encoding);
  176. }
  177. /**
  178. * Converts character encoding of a xml-formatted text from UTF-8 into a specified encoding.
  179. * If inside the text the encoding is declared, it is modified accordingly.
  180. *
  181. * @param string $string the text being converted
  182. * @param string $to_encoding (optional) The encoding that text is being converted to.
  183. * If it is omitted, the platform character set is assumed.
  184. *
  185. * @return string returns the converted xml-text
  186. */
  187. function api_utf8_decode_xml($string, $to_encoding = 'UTF-8')
  188. {
  189. return _api_convert_encoding_xml($string, $to_encoding, 'UTF-8');
  190. }
  191. /**
  192. * Converts character encoding of a xml-formatted text.
  193. * If inside the text the encoding is declared, it is modified accordingly.
  194. *
  195. * @param string $string the text being converted
  196. * @param string $to_encoding the encoding that text is being converted to
  197. * @param string $from_encoding (optional) The encoding that text is being converted from.
  198. * If the value is empty, it is tried to be detected then.
  199. *
  200. * @return string returns the converted xml-text
  201. */
  202. function _api_convert_encoding_xml(&$string, $to_encoding, $from_encoding)
  203. {
  204. if (empty($from_encoding)) {
  205. $from_encoding = api_detect_encoding_xml($string);
  206. }
  207. $to_encoding = api_refine_encoding_id($to_encoding);
  208. if (!preg_match('/<\?xml.*\?>/m', $string, $matches)) {
  209. return api_convert_encoding(
  210. '<?xml version="1.0" encoding="'.$to_encoding.'"?>'."\n".$string,
  211. $to_encoding,
  212. $from_encoding
  213. );
  214. }
  215. if (!preg_match(_PCRE_XML_ENCODING, $string)) {
  216. if (strpos($matches[0], 'standalone') !== false) {
  217. // The encoding option should precede the standalone option,
  218. // othewise DOMDocument fails to load the document.
  219. $replace = str_replace('standalone', ' encoding="'.$to_encoding.'" standalone', $matches[0]);
  220. } else {
  221. $replace = str_replace('?>', ' encoding="'.$to_encoding.'"?>', $matches[0]);
  222. }
  223. return api_convert_encoding(str_replace($matches[0], $replace, $string), $to_encoding, $from_encoding);
  224. }
  225. global $_api_encoding;
  226. $_api_encoding = api_refine_encoding_id($to_encoding);
  227. return api_convert_encoding(
  228. preg_replace_callback(
  229. _PCRE_XML_ENCODING,
  230. '_api_convert_encoding_xml_callback',
  231. $string
  232. ),
  233. $to_encoding,
  234. $from_encoding
  235. );
  236. }
  237. /**
  238. * A callback for serving the function _api_convert_encoding_xml().
  239. *
  240. * @param array $matches input array of matches corresponding to the xml-declaration
  241. *
  242. * @return string returns the xml-declaration with modified encoding
  243. */
  244. function _api_convert_encoding_xml_callback($matches)
  245. {
  246. global $_api_encoding;
  247. return str_replace($matches[1], $_api_encoding, $matches[0]);
  248. }
  249. /* Functions for supporting ASCIIMathML mathematical formulas and ASCIIsvg maathematical graphics */
  250. /**
  251. * Dectects ASCIIMathML formula presence within a given html text.
  252. *
  253. * @param string $html the input html text
  254. *
  255. * @return bool returns TRUE when there is a formula found or FALSE otherwise
  256. */
  257. function api_contains_asciimathml($html)
  258. {
  259. if (!preg_match_all('/<span[^>]*class\s*=\s*[\'"](.*?)[\'"][^>]*>/mi', $html, $matches)) {
  260. return false;
  261. }
  262. foreach ($matches[1] as $string) {
  263. $string = ' '.str_replace(',', ' ', $string).' ';
  264. if (preg_match('/\sAM\s/m', $string)) {
  265. return true;
  266. }
  267. }
  268. return false;
  269. }
  270. /**
  271. * Dectects ASCIIsvg graphics presence within a given html text.
  272. *
  273. * @param string $html the input html text
  274. *
  275. * @return bool returns TRUE when there is a graph found or FALSE otherwise
  276. */
  277. function api_contains_asciisvg($html)
  278. {
  279. if (!preg_match_all('/<embed([^>]*?)>/mi', $html, $matches)) {
  280. return false;
  281. }
  282. foreach ($matches[1] as $string) {
  283. $string = ' '.str_replace(',', ' ', $string).' ';
  284. if (preg_match('/sscr\s*=\s*[\'"](.*?)[\'"]/m', $string)) {
  285. return true;
  286. }
  287. }
  288. return false;
  289. }
  290. /* Miscellaneous text processing functions */
  291. /**
  292. * Convers a string from camel case into underscore.
  293. * Works correctly with ASCII strings only, implementation for human-language strings is not necessary.
  294. *
  295. * @param string $string The input string (ASCII)
  296. *
  297. * @return string The converted result string
  298. */
  299. function api_camel_case_to_underscore($string)
  300. {
  301. return strtolower(preg_replace('/([a-z])([A-Z])/', "$1_$2", $string));
  302. }
  303. /**
  304. * Converts a string with underscores into camel case.
  305. * Works correctly with ASCII strings only, implementation for human-language strings is not necessary.
  306. *
  307. * @param string $string The input string (ASCII)
  308. * @param bool $capitalise_first_char (optional)
  309. * If true (default), the function capitalises the first char in the result string
  310. *
  311. * @return string The converted result string
  312. */
  313. function api_underscore_to_camel_case($string, $capitalise_first_char = true)
  314. {
  315. if ($capitalise_first_char) {
  316. $string = ucfirst($string);
  317. }
  318. return preg_replace_callback('/_([a-z])/', '_api_camelize', $string);
  319. }
  320. // A function for internal use, only for this library.
  321. function _api_camelize($match)
  322. {
  323. return strtoupper($match[1]);
  324. }
  325. /**
  326. * Truncates a string.
  327. *
  328. * @author Brouckaert Olivier
  329. *
  330. * @param string $text the text to truncate
  331. * @param int $length The approximate desired length. The length of the suffix below is to be added to
  332. * have the total length of the result string.
  333. * @param string $suffix a suffix to be added as a replacement
  334. * @param string $encoding (optional) The encoding to be used. If it is omitted,
  335. * the platform character set will be used by default.
  336. * @param bool $middle if this parameter is true, truncation is done in the middle of the string
  337. *
  338. * @return string truncated string, decorated with the given suffix (replacement)
  339. */
  340. function api_trunc_str($text, $length = 30, $suffix = '...', $middle = false, $encoding = null)
  341. {
  342. if (empty($encoding)) {
  343. $encoding = api_get_system_encoding();
  344. }
  345. $text_length = api_strlen($text, $encoding);
  346. if ($text_length <= $length) {
  347. return $text;
  348. }
  349. if ($middle) {
  350. return rtrim(
  351. api_substr(
  352. $text,
  353. 0,
  354. round($length / 2),
  355. $encoding
  356. )
  357. ).
  358. $suffix.
  359. ltrim(
  360. api_substr(
  361. $text,
  362. -round($length / 2),
  363. $text_length,
  364. $encoding
  365. )
  366. );
  367. }
  368. return rtrim(api_substr($text, 0, $length, $encoding)).$suffix;
  369. }
  370. /**
  371. * Handling simple and double apostrofe in order that strings be stored properly in database.
  372. *
  373. * @author Denes Nagy
  374. *
  375. * @param string variable - the variable to be revised
  376. *
  377. * @return string
  378. */
  379. function domesticate($input)
  380. {
  381. $input = stripslashes($input);
  382. $input = str_replace("'", "''", $input);
  383. $input = str_replace('"', "''", $input);
  384. return $input;
  385. }
  386. /**
  387. * function make_clickable($string).
  388. *
  389. * @desc Completes url contained in the text with "<a href ...".
  390. * However the function simply returns the submitted text without any
  391. * transformation if it already contains some "<a href:" or "<img src=".
  392. *
  393. * @param string $text text to be converted
  394. *
  395. * @return text after conversion
  396. *
  397. * @author Rewritten by Nathan Codding - Feb 6, 2001.
  398. * completed by Hugues Peeters - July 22, 2002
  399. *
  400. * Actually this function is taken from the PHP BB 1.4 script
  401. * - Goes through the given string, and replaces xxxx://yyyy with an HTML <a> tag linking
  402. * to that URL
  403. * - Goes through the given string, and replaces www.xxxx.yyyy[zzzz] with an HTML <a> tag linking
  404. * to http://www.xxxx.yyyy[/zzzz]
  405. * - Goes through the given string, and replaces xxxx@yyyy with an HTML mailto: tag linking
  406. * to that email address
  407. * - Only matches these 2 patterns either after a space, or at the beginning of a line
  408. *
  409. * Notes: the email one might get annoying - it's easy to make it more restrictive, though.. maybe
  410. * have it require something like xxxx@yyyy.zzzz or such. We'll see.
  411. */
  412. /**
  413. * Callback to convert URI match to HTML A element.
  414. *
  415. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link * make_clickable()}.
  416. *
  417. * @since Wordpress 2.3.2
  418. *
  419. * @param array $matches single Regex Match
  420. *
  421. * @return string HTML A element with URI address
  422. */
  423. function _make_url_clickable_cb($matches)
  424. {
  425. $url = $matches[2];
  426. if (')' == $matches[3] && strpos($url, '(')) {
  427. // If the trailing character is a closing parethesis, and the URL has an opening
  428. // parenthesis in it, add the closing parenthesis to the URL.
  429. // Then we can let the parenthesis balancer do its thing below.
  430. $url .= $matches[3];
  431. $suffix = '';
  432. } else {
  433. $suffix = $matches[3];
  434. }
  435. // Include parentheses in the URL only if paired
  436. while (substr_count($url, '(') < substr_count($url, ')')) {
  437. $suffix = strrchr($url, ')').$suffix;
  438. $url = substr($url, 0, strrpos($url, ')'));
  439. }
  440. $url = esc_url($url);
  441. if (empty($url)) {
  442. return $matches[0];
  443. }
  444. return $matches[1]."<a href=\"$url\" rel=\"nofollow\">$url</a>".$suffix;
  445. }
  446. /**
  447. * Checks and cleans a URL.
  448. *
  449. * A number of characters are removed from the URL. If the URL is for displaying
  450. * (the default behaviour) ampersands are also replaced. The 'clean_url' filter
  451. * is applied to the returned cleaned URL.
  452. *
  453. * @since wordpress 2.8.0
  454. *
  455. * @uses \wp_kses_bad_protocol() To only permit protocols in the URL set
  456. * via $protocols or the common ones set in the function.
  457. *
  458. * @param string $url the URL to be cleaned
  459. * @param array $protocols Optional. An array of acceptable protocols.
  460. * Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher',
  461. * 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
  462. * @param string $_context Private. Use esc_url_raw() for database usage.
  463. *
  464. * @return string the cleaned $url after the 'clean_url' filter is applied
  465. */
  466. function esc_url($url, $protocols = null, $_context = 'display')
  467. {
  468. //$original_url = $url;
  469. if ('' == $url) {
  470. return $url;
  471. }
  472. $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
  473. $strip = ['%0d', '%0a', '%0D', '%0A'];
  474. $url = _deep_replace($strip, $url);
  475. $url = str_replace(';//', '://', $url);
  476. /* If the URL doesn't appear to contain a scheme, we
  477. * presume it needs http:// appended (unless a relative
  478. * link starting with /, # or ? or a php file).
  479. */
  480. if (strpos($url, ':') === false && !in_array($url[0], ['/', '#', '?']) &&
  481. !preg_match('/^[a-z0-9-]+?\.php/i', $url)) {
  482. $url = 'http://'.$url;
  483. }
  484. return Security::remove_XSS($url);
  485. /*// Replace ampersands and single quotes only when displaying.
  486. if ( 'display' == $_context ) {
  487. $url = wp_kses_normalize_entities( $url );
  488. $url = str_replace( '&amp;', '&#038;', $url );
  489. $url = str_replace( "'", '&#039;', $url );
  490. }
  491. if ( '/' === $url[0] ) {
  492. $good_protocol_url = $url;
  493. } else {
  494. if ( ! is_array( $protocols ) )
  495. $protocols = wp_allowed_protocols();
  496. $good_protocol_url = wp_kses_bad_protocol( $url, $protocols );
  497. if ( strtolower( $good_protocol_url ) != strtolower( $url ) )
  498. return '';
  499. }
  500. /**
  501. * Filter a string cleaned and escaped for output as a URL.
  502. *
  503. * @since 2.3.0
  504. *
  505. * @param string $good_protocol_url The cleaned URL to be returned.
  506. * @param string $original_url The URL prior to cleaning.
  507. * @param string $_context If 'display', replace ampersands and single quotes only.
  508. */
  509. //return apply_filters( 'clean_url', $good_protocol_url, $original_url, $_context );98
  510. }
  511. /**
  512. * Perform a deep string replace operation to ensure the values in $search are no longer present.
  513. *
  514. * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values
  515. * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that
  516. * str_replace would return
  517. *
  518. * @since wordpress 2.8.1
  519. *
  520. * @param string|array $search The value being searched for, otherwise known as the needle.
  521. * An array may be used to designate multiple needles.
  522. * @param string $subject the string being searched and replaced on, otherwise known as the haystack
  523. *
  524. * @return string the string with the replaced svalues
  525. */
  526. function _deep_replace($search, $subject)
  527. {
  528. $subject = (string) $subject;
  529. $count = 1;
  530. while ($count) {
  531. $subject = str_replace($search, '', $subject, $count);
  532. }
  533. return $subject;
  534. }
  535. /**
  536. * Callback to convert URL match to HTML A element.
  537. *
  538. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link * make_clickable()}.
  539. *
  540. * @since wordpress 2.3.2
  541. *
  542. * @param array $matches single Regex Match
  543. *
  544. * @return string HTML A element with URL address
  545. */
  546. function _make_web_ftp_clickable_cb($matches)
  547. {
  548. $ret = '';
  549. $dest = $matches[2];
  550. $dest = 'http://'.$dest;
  551. $dest = esc_url($dest);
  552. if (empty($dest)) {
  553. return $matches[0];
  554. }
  555. // removed trailing [.,;:)] from URL
  556. if (in_array(substr($dest, -1), ['.', ',', ';', ':', ')']) === true) {
  557. $ret = substr($dest, -1);
  558. $dest = substr($dest, 0, strlen($dest) - 1);
  559. }
  560. return $matches[1]."<a href=\"$dest\" rel=\"nofollow\">$dest</a>$ret";
  561. }
  562. /**
  563. * Callback to convert email address match to HTML A element.
  564. *
  565. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link * make_clickable()}.
  566. *
  567. * @since wordpress 2.3.2
  568. *
  569. * @param array $matches single Regex Match
  570. *
  571. * @return string HTML A element with email address
  572. */
  573. function _make_email_clickable_cb($matches)
  574. {
  575. $email = $matches[2].'@'.$matches[3];
  576. return $matches[1]."<a href=\"mailto:$email\">$email</a>";
  577. }
  578. /**
  579. * Convert plaintext URI to HTML links.
  580. *
  581. * Converts URI, www and ftp, and email addresses. Finishes by fixing links
  582. * within links.
  583. *
  584. * @since wordpress 0.71
  585. *
  586. * @param string $text content to convert URIs
  587. *
  588. * @return string content with converted URIs
  589. */
  590. function make_clickable($text)
  591. {
  592. $r = '';
  593. // split out HTML tags
  594. $textarr = preg_split('/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
  595. $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
  596. foreach ($textarr as $piece) {
  597. if (preg_match('|^<code[\s>]|i', $piece) || preg_match('|^<pre[\s>]|i', $piece)) {
  598. $nested_code_pre++;
  599. } elseif (('</code>' === strtolower($piece) || '</pre>' === strtolower($piece)) && $nested_code_pre) {
  600. $nested_code_pre--;
  601. }
  602. if ($nested_code_pre ||
  603. empty($piece) ||
  604. ($piece[0] === '<' && !preg_match('|^<\s*[\w]{1,20}+://|', $piece))
  605. ) {
  606. $r .= $piece;
  607. continue;
  608. }
  609. // Long strings might contain expensive edge cases ...
  610. if (10000 < strlen($piece)) {
  611. // ... break it up
  612. foreach (_split_str_by_whitespace($piece, 2100) as $chunk) {
  613. // 2100: Extra room for scheme and leading and trailing paretheses
  614. if (2101 < strlen($chunk)) {
  615. $r .= $chunk; // Too big, no whitespace: bail.
  616. } else {
  617. $r .= make_clickable($chunk);
  618. }
  619. }
  620. } else {
  621. $ret = " $piece "; // Pad with whitespace to simplify the regexes
  622. $url_clickable = '~
  623. ([\\s(<.,;:!?]) # 1: Leading whitespace, or punctuation
  624. ( # 2: URL
  625. [\\w]{1,20}+:// # Scheme and hier-part prefix
  626. (?=\S{1,2000}\s) # Limit to URLs less than about 2000 characters long
  627. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character
  628. (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
  629. [\'.,;:!?)] # Punctuation URL character
  630. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
  631. )*
  632. )
  633. (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing)
  634. ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character.
  635. // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
  636. $ret = preg_replace_callback($url_clickable, '_make_url_clickable_cb', $ret);
  637. $ret = preg_replace_callback(
  638. '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is',
  639. '_make_web_ftp_clickable_cb',
  640. $ret
  641. );
  642. $ret = preg_replace_callback(
  643. '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i',
  644. '_make_email_clickable_cb',
  645. $ret
  646. );
  647. $ret = substr($ret, 1, -1); // Remove our whitespace padding.
  648. $r .= $ret;
  649. }
  650. }
  651. // Cleanup of accidental links within links
  652. $r = preg_replace('#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r);
  653. return $r;
  654. }
  655. /**
  656. * Breaks a string into chunks by splitting at whitespace characters.
  657. * The length of each returned chunk is as close to the specified length goal as possible,
  658. * with the caveat that each chunk includes its trailing delimiter.
  659. * Chunks longer than the goal are guaranteed to not have any inner whitespace.
  660. *
  661. * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
  662. *
  663. * Input string must have no null characters (or eventual transformations on output chunks
  664. * must not care about null characters)
  665. *
  666. * <code>
  667. * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234 890 123456789 1234567890a 45678 1 3 5 7 90 ", 10 ) ==
  668. * array (
  669. * 0 => '1234 67890 ', // 11 characters: Perfect split
  670. * 1 => '1234 ', // 5 characters: '1234 67890a' was too long
  671. * 2 => '67890a cd ', // 10 characters: '67890a cd 1234' was too long
  672. * 3 => '1234 890 ', // 11 characters: Perfect split
  673. * 4 => '123456789 ', // 10 characters: '123456789 1234567890a' was too long
  674. * 5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split
  675. * 6 => ' 45678 ', // 11 characters: Perfect split
  676. * 7 => '1 3 5 7 9', // 9 characters: End of $string
  677. * );
  678. * </code>
  679. *
  680. * @since wordpress 3.4.0
  681. *
  682. * @param string $string the string to split
  683. * @param int $goal the desired chunk length
  684. *
  685. * @return array numeric array of chunks
  686. */
  687. function _split_str_by_whitespace($string, $goal)
  688. {
  689. $chunks = [];
  690. $string_nullspace = strtr($string, "\r\n\t\v\f ", "\000\000\000\000\000\000");
  691. while ($goal < strlen($string_nullspace)) {
  692. $pos = strrpos(substr($string_nullspace, 0, $goal + 1), "\000");
  693. if (false === $pos) {
  694. $pos = strpos($string_nullspace, "\000", $goal + 1);
  695. if (false === $pos) {
  696. break;
  697. }
  698. }
  699. $chunks[] = substr($string, 0, $pos + 1);
  700. $string = substr($string, $pos + 1);
  701. $string_nullspace = substr($string_nullspace, $pos + 1);
  702. }
  703. if ($string) {
  704. $chunks[] = $string;
  705. }
  706. return $chunks;
  707. }
  708. /**
  709. * This functions cuts a paragraph
  710. * i.e cut('Merry Xmas from Lima',13) = "Merry Xmas fr...".
  711. *
  712. * @param string The text to "cut"
  713. * @param int Count of chars
  714. * @param bool Whether to embed in a <span title="...">...</span>
  715. *
  716. * @return string
  717. * */
  718. function cut($text, $maxchar, $embed = false)
  719. {
  720. if (api_strlen($text) > $maxchar) {
  721. if ($embed) {
  722. return '<p title="'.$text.'">'.api_substr($text, 0, $maxchar).'...</p>';
  723. }
  724. return api_substr($text, 0, $maxchar).' ...';
  725. }
  726. return $text;
  727. }
  728. /**
  729. * Show a number as only integers if no decimals, but will show 2 decimals if exist.
  730. *
  731. * @param mixed Number to convert
  732. * @param int Decimal points 0=never, 1=if needed, 2=always
  733. * @param string $decimalPoint
  734. * @param string $thousandsSeparator
  735. *
  736. * @return mixed An integer or a float depends on the parameter
  737. */
  738. function float_format($number, $flag = 1, $decimalPoint = '.', $thousandsSeparator = ',')
  739. {
  740. if (is_numeric($number)) {
  741. if (!$number) {
  742. $result = ($flag == 2 ? '0.'.str_repeat('0', EXERCISE_NUMBER_OF_DECIMALS) : '0');
  743. } else {
  744. if (floor($number) == $number) {
  745. $result = number_format(
  746. $number,
  747. ($flag == 2 ? EXERCISE_NUMBER_OF_DECIMALS : 0),
  748. $decimalPoint,
  749. $thousandsSeparator
  750. );
  751. } else {
  752. $result = number_format(
  753. round($number, 2),
  754. ($flag == 0 ? 0 : EXERCISE_NUMBER_OF_DECIMALS),
  755. $decimalPoint,
  756. $thousandsSeparator
  757. );
  758. }
  759. }
  760. return $result;
  761. }
  762. }
  763. // TODO: To be checked for correct timezone management.
  764. /**
  765. * Function to obtain last week timestamps.
  766. *
  767. * @return array Times for every day inside week
  768. */
  769. function get_last_week()
  770. {
  771. $week = date('W');
  772. $year = date('Y');
  773. $lastweek = $week - 1;
  774. if ($lastweek == 0) {
  775. $week = 52;
  776. $year--;
  777. }
  778. $lastweek = sprintf("%02d", $lastweek);
  779. $arrdays = [];
  780. for ($i = 1; $i <= 7; $i++) {
  781. $arrdays[] = strtotime("$year"."W$lastweek"."$i");
  782. }
  783. return $arrdays;
  784. }
  785. /**
  786. * Gets the week from a day.
  787. *
  788. * @param string Date in UTC (2010-01-01 12:12:12)
  789. *
  790. * @return int Returns an integer with the week number of the year
  791. */
  792. function get_week_from_day($date)
  793. {
  794. if (!empty($date)) {
  795. $time = api_strtotime($date, 'UTC');
  796. return date('W', $time);
  797. } else {
  798. return date('W');
  799. }
  800. }
  801. /**
  802. * This function splits the string into words and then joins them back together again one by one.
  803. * Example: "Test example of a long string"
  804. * substrwords(5) = Test ... *.
  805. *
  806. * @param string
  807. * @param int the max number of character
  808. * @param string how the string will be end
  809. *
  810. * @return a reduce string
  811. */
  812. function substrwords($text, $maxchar, $end = '...')
  813. {
  814. if (strlen($text) > $maxchar) {
  815. $words = explode(" ", $text);
  816. $output = '';
  817. $i = 0;
  818. while (1) {
  819. $length = (strlen($output) + strlen($words[$i]));
  820. if ($length > $maxchar) {
  821. break;
  822. } else {
  823. $output = $output." ".$words[$i];
  824. $i++;
  825. }
  826. }
  827. } else {
  828. $output = $text;
  829. return $output;
  830. }
  831. return $output.$end;
  832. }
  833. function implode_with_key($glue, $array)
  834. {
  835. if (!empty($array)) {
  836. $string = '';
  837. foreach ($array as $key => $value) {
  838. if (empty($value)) {
  839. $value = 'null';
  840. }
  841. $string .= $key." : ".$value." $glue ";
  842. }
  843. return $string;
  844. }
  845. return '';
  846. }
  847. /**
  848. * Transform the file size in a human readable format.
  849. *
  850. * @param int $file_size Size of the file in bytes
  851. *
  852. * @return string A human readable representation of the file size
  853. */
  854. function format_file_size($file_size)
  855. {
  856. $file_size = intval($file_size);
  857. if ($file_size >= 1073741824) {
  858. $file_size = (round($file_size / 1073741824 * 100) / 100).'G';
  859. } elseif ($file_size >= 1048576) {
  860. $file_size = (round($file_size / 1048576 * 100) / 100).'M';
  861. } elseif ($file_size >= 1024) {
  862. $file_size = (round($file_size / 1024 * 100) / 100).'k';
  863. } else {
  864. $file_size = $file_size.'B';
  865. }
  866. return $file_size;
  867. }
  868. /**
  869. * Converts an string CLEANYO[admin][amann,acostea]
  870. * into an array:.
  871. *
  872. * array(
  873. * CLEANYO
  874. * admin
  875. * amann,acostea
  876. * )
  877. *
  878. * @param $array
  879. *
  880. * @return array
  881. */
  882. function bracketsToArray($array)
  883. {
  884. return preg_split('/[\[\]]+/', $array, -1, PREG_SPLIT_NO_EMPTY);
  885. }
  886. /**
  887. * @param string $string
  888. * @param bool $capitalizeFirstCharacter
  889. *
  890. * @return mixed
  891. */
  892. function underScoreToCamelCase($string, $capitalizeFirstCharacter = true)
  893. {
  894. $str = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
  895. if (!$capitalizeFirstCharacter) {
  896. $str[0] = strtolower($str[0]);
  897. }
  898. return $str;
  899. }
  900. /**
  901. * @param string $value
  902. */
  903. function trim_value(&$value)
  904. {
  905. $value = trim($value);
  906. }
  907. /**
  908. * Strips only the given tags in the given HTML string.
  909. *
  910. * @param string $html
  911. * @param array $tags
  912. *
  913. * @return string
  914. */
  915. function strip_tags_blacklist($html, $tags)
  916. {
  917. foreach ($tags as $tag) {
  918. $regex = '#<\s*'.$tag.'[^>]*>.*?<\s*/\s*'.$tag.'>#msi';
  919. $html = preg_replace($regex, '', $html);
  920. }
  921. return $html;
  922. }