PageRenderTime 70ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/core/string_api.php

http://github.com/mantisbt/mantisbt
PHP | 1003 lines | 537 code | 107 blank | 359 comment | 102 complexity | c235c1845c9eb9901c1467cb13b0803e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. # MantisBT - A PHP based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * String Processing API
  17. *
  18. * @package CoreAPI
  19. * @subpackage StringProcessingAPI
  20. * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  21. * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  22. * @link http://www.mantisbt.org
  23. *
  24. * @uses access_api.php
  25. * @uses authentication_api.php
  26. * @uses bug_api.php
  27. * @uses bugnote_api.php
  28. * @uses config_api.php
  29. * @uses constant_inc.php
  30. * @uses email_api.php
  31. * @uses event_api.php
  32. * @uses helper_api.php
  33. * @uses lang_api.php
  34. * @uses user_api.php
  35. * @uses utility_api.php
  36. */
  37. require_api( 'access_api.php' );
  38. require_api( 'authentication_api.php' );
  39. require_api( 'bug_api.php' );
  40. require_api( 'bugnote_api.php' );
  41. require_api( 'config_api.php' );
  42. require_api( 'constant_inc.php' );
  43. require_api( 'email_api.php' );
  44. require_api( 'event_api.php' );
  45. require_api( 'helper_api.php' );
  46. require_api( 'lang_api.php' );
  47. require_api( 'user_api.php' );
  48. require_api( 'utility_api.php' );
  49. $g_cache_html_valid_tags = '';
  50. $g_cache_html_valid_tags_single_line = '';
  51. /**
  52. * Preserve spaces at beginning of lines.
  53. * Lines must be separated by \n rather than <br />
  54. * @param string $p_string String to be processed.
  55. * @return string
  56. */
  57. function string_preserve_spaces_at_bol( $p_string ) {
  58. $t_lines = explode( "\n", $p_string );
  59. $t_line_count = count( $t_lines );
  60. for( $i = 0;$i < $t_line_count;$i++ ) {
  61. $t_count = 0;
  62. $t_prefix = '';
  63. $t_char = mb_substr( $t_lines[$i], $t_count, 1 );
  64. $t_spaces = 0;
  65. while( ( $t_char == ' ' ) || ( $t_char == "\t" ) ) {
  66. if( $t_char == ' ' ) {
  67. $t_spaces++;
  68. } else {
  69. $t_spaces += 4;
  70. }
  71. # 1 tab = 4 spaces, can be configurable.
  72. $t_count++;
  73. $t_char = mb_substr( $t_lines[$i], $t_count, 1 );
  74. }
  75. for( $j = 0;$j < $t_spaces;$j++ ) {
  76. $t_prefix .= '&#160;';
  77. }
  78. $t_lines[$i] = $t_prefix . mb_substr( $t_lines[$i], $t_count );
  79. }
  80. return implode( "\n", $t_lines );
  81. }
  82. /**
  83. * Prepare a string to be printed without being broken into multiple lines
  84. * @param string $p_string String to be processed.
  85. * @return string
  86. */
  87. function string_no_break( $p_string ) {
  88. if( strpos( $p_string, ' ' ) !== false ) {
  89. return '<span class="nowrap">' . $p_string . '</span>';
  90. } else {
  91. return $p_string;
  92. }
  93. }
  94. /**
  95. * Similar to nl2br, but fixes up a problem where new lines are doubled between
  96. * html pre tags.
  97. * additionally, wrap the text an $p_wrap character intervals if the config is set
  98. * @param string $p_string String to be processed.
  99. * @param integer $p_wrap Number of characters to wrap text at.
  100. * @return string
  101. */
  102. function string_nl2br( $p_string, $p_wrap = 100 ) {
  103. $t_pieces = preg_split( '/(<pre[^>]*>.*?<\/pre>)/is', $p_string, -1, PREG_SPLIT_DELIM_CAPTURE );
  104. if( isset( $t_pieces[1] ) ) {
  105. $t_output = '';
  106. foreach( $t_pieces as $t_piece ) {
  107. if( preg_match( '/(<pre[^>]*>.*?<\/pre>)/is', $t_piece ) ) {
  108. $t_piece = preg_replace( '/<br[^>]*?>/', '', $t_piece );
  109. # @@@ thraxisp - this may want to be replaced by html_entity_decode (or equivalent)
  110. # if other encoded characters are a problem
  111. $t_piece = preg_replace( '/&#160;/', ' ', $t_piece );
  112. if( ON == config_get( 'wrap_in_preformatted_text' ) ) {
  113. # Use PCRE_UTF8 modifier to ensure a correct char count
  114. $t_output .= preg_replace( '/([^\n]{' . $p_wrap . ',}?[\s]+)(?!<\/pre>)/u', "$1", $t_piece );
  115. } else {
  116. $t_output .= $t_piece;
  117. }
  118. } else {
  119. $t_output .= nl2br( $t_piece );
  120. }
  121. }
  122. return $t_output;
  123. } else {
  124. return nl2br( $p_string );
  125. }
  126. }
  127. /**
  128. * Prepare a multiple line string for display to HTML
  129. * @param string $p_string String to be processed.
  130. * @return string
  131. */
  132. function string_display( $p_string ) {
  133. return event_signal( 'EVENT_DISPLAY_TEXT', $p_string, true );
  134. }
  135. /**
  136. * Prepare a single line string for display to HTML
  137. * @param string $p_string String to be processed.
  138. * @return string
  139. */
  140. function string_display_line( $p_string ) {
  141. return event_signal( 'EVENT_DISPLAY_TEXT', $p_string, false );
  142. }
  143. /**
  144. * Prepare a string for display to HTML and add href anchors for URLs, emails
  145. * and bug references
  146. * @param string $p_string String to be processed.
  147. * @return string
  148. */
  149. function string_display_links( $p_string ) {
  150. return event_signal( 'EVENT_DISPLAY_FORMATTED', $p_string, true );
  151. }
  152. /**
  153. * Prepare a single line string for display to HTML and add href anchors for
  154. * URLs, emails and bug references
  155. * @param string $p_string String to be processed.
  156. * @return string
  157. */
  158. function string_display_line_links( $p_string ) {
  159. return event_signal( 'EVENT_DISPLAY_FORMATTED', $p_string, false );
  160. }
  161. /**
  162. * Prepare a string for display in rss
  163. * @param string $p_string String to be processed.
  164. * @return string
  165. */
  166. function string_rss_links( $p_string ) {
  167. # rss can not start with &#160; which spaces will be replaced into by string_display().
  168. $t_string = trim( $p_string );
  169. $t_string = event_signal( 'EVENT_DISPLAY_RSS', $t_string );
  170. # another escaping to escape the special characters created by the generated links
  171. return string_html_specialchars( $t_string );
  172. }
  173. /**
  174. * Prepare a string for plain text display in email
  175. * @param string $p_string String to be processed.
  176. * @return string
  177. */
  178. function string_email( $p_string ) {
  179. return string_strip_hrefs( $p_string );
  180. }
  181. /**
  182. * Prepare a string for plain text display in email and add URLs for bug
  183. * links
  184. * @param string $p_string String to be processed.
  185. * @return string
  186. */
  187. function string_email_links( $p_string ) {
  188. return event_signal( 'EVENT_DISPLAY_EMAIL', $p_string );
  189. }
  190. /**
  191. * Process a string for display in a textarea box
  192. * @param string $p_string String to be processed.
  193. * @return string
  194. */
  195. function string_textarea( $p_string ) {
  196. return string_html_specialchars( $p_string );
  197. }
  198. /**
  199. * Process a string for display in a text box
  200. * @param string $p_string String to be processed.
  201. * @return string
  202. */
  203. function string_attribute( $p_string ) {
  204. return string_html_specialchars( $p_string );
  205. }
  206. /**
  207. * Process a string for inclusion in a URL as a GET parameter
  208. * @param string $p_string String to be processed.
  209. * @return string
  210. */
  211. function string_url( $p_string ) {
  212. return rawurlencode( $p_string );
  213. }
  214. /**
  215. * validate the url as part of this site before continuing
  216. * @param string $p_url URL to be processed.
  217. * @param boolean $p_return_absolute Whether to return the absolute URL to this Mantis instance.
  218. * @return string
  219. */
  220. function string_sanitize_url( $p_url, $p_return_absolute = false ) {
  221. $t_url = strip_tags( urldecode( $p_url ) );
  222. $t_path = rtrim( config_get_global( 'path' ), '/' );
  223. $t_short_path = rtrim( config_get_global( 'short_path' ), '/' );
  224. $t_pattern = '(?:/*(?P<script>[^\?#]*))(?:\?(?P<query>[^#]*))?(?:#(?P<anchor>[^#]*))?';
  225. # Break the given URL into pieces for path, script, query, and anchor
  226. $t_type = 0;
  227. if( preg_match( '@^(?P<path>' . preg_quote( $t_path, '@' ) . ')' . $t_pattern . '$@', $t_url, $t_matches ) ) {
  228. $t_type = 1;
  229. } else if( !empty( $t_short_path )
  230. && preg_match( '@^(?P<path>' . preg_quote( $t_short_path, '@' ) . ')' . $t_pattern . '$@', $t_url, $t_matches )
  231. ) {
  232. $t_type = 2;
  233. } else if( preg_match( '@^(?P<path>)' . $t_pattern . '$@', $t_url, $t_matches ) ) {
  234. $t_type = 3;
  235. }
  236. # Check for URL's pointing to other domains
  237. if( 0 == $t_type || empty( $t_matches['script'] ) ||
  238. 3 == $t_type && preg_match( '@(?:[^:]*)?:/*@', $t_url ) > 0 ) {
  239. return ( $p_return_absolute ? $t_path . '/' : '' ) . 'index.php';
  240. }
  241. # Start extracting regex matches
  242. # Encode backslashes to prevent unwanted escaping of a leading '/' allowing
  243. # redirection to external sites
  244. $t_script = strtr( $t_matches['script'], array( '\\' => '%5C' ) );
  245. $t_script_path = $t_matches['path'];
  246. # Clean/encode query params
  247. $t_query = '';
  248. if( isset( $t_matches['query'] ) ) {
  249. $t_pairs = array();
  250. parse_str( html_entity_decode( $t_matches['query'] ), $t_pairs );
  251. $t_clean_pairs = array();
  252. foreach( $t_pairs as $t_key => $t_value ) {
  253. if( is_array( $t_value ) ) {
  254. foreach( $t_value as $t_value_each ) {
  255. $t_clean_pairs[] .= rawurlencode( $t_key ) . '[]=' . rawurlencode( $t_value_each );
  256. }
  257. } else {
  258. $t_clean_pairs[] = rawurlencode( $t_key ) . '=' . rawurlencode( $t_value );
  259. }
  260. }
  261. if( !empty( $t_clean_pairs ) ) {
  262. $t_query = '?' . implode( '&', $t_clean_pairs );
  263. }
  264. }
  265. # encode link anchor
  266. $t_anchor = '';
  267. if( isset( $t_matches['anchor'] ) ) {
  268. $t_anchor = '#' . rawurlencode( $t_matches['anchor'] );
  269. }
  270. # Return an appropriate re-combined URL string
  271. if( $p_return_absolute ) {
  272. return $t_path . '/' . $t_script . $t_query . $t_anchor;
  273. } else {
  274. return ( !empty( $t_script_path ) ? $t_script_path . '/' : '' ) . $t_script . $t_query . $t_anchor;
  275. }
  276. }
  277. /**
  278. * Process $p_string, looking for bug ID references and creating bug view
  279. * links for them.
  280. *
  281. * Returns the processed string.
  282. *
  283. * If $p_include_anchor is true, include the href tag, otherwise just insert
  284. * the URL
  285. *
  286. * The bug tag ('#' by default) must be at the beginning of the string or
  287. * preceded by a character that is not a letter, a number or an underscore
  288. *
  289. * if $p_include_anchor = false, $p_fqdn is ignored and assumed to true.
  290. * @param string $p_string String to be processed.
  291. * @param boolean $p_include_anchor Whether to include the href tag or just the URL.
  292. * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
  293. * @param boolean $p_fqdn Whether to return an absolute or relative link.
  294. * @return string
  295. */
  296. function string_process_bug_link( $p_string, $p_include_anchor = true, $p_detail_info = true, $p_fqdn = false ) {
  297. static $s_bug_link_callback = array();
  298. $t_tag = config_get( 'bug_link_tag' );
  299. # bail if the link tag is blank
  300. if( '' == $t_tag || $p_string == '' ) {
  301. return $p_string;
  302. }
  303. if( !isset( $s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] ) ) {
  304. if( $p_include_anchor ) {
  305. $s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
  306. function( $p_array ) use( $p_detail_info, $p_fqdn ) {
  307. $c_bug_id = (int)$p_array[2];
  308. if( bug_exists( $c_bug_id ) ) {
  309. $t_project_id = bug_get_field( $c_bug_id, 'project_id' );
  310. $t_view_bug_threshold = config_get( 'view_bug_threshold', null, null, $t_project_id );
  311. if( access_has_bug_level( $t_view_bug_threshold, $c_bug_id ) ) {
  312. return $p_array[1] .
  313. string_get_bug_view_link(
  314. $c_bug_id,
  315. (boolean)$p_detail_info,
  316. (boolean)$p_fqdn
  317. );
  318. }
  319. }
  320. return $p_array[0];
  321. }; # end of bug link callback closure
  322. } else {
  323. $s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
  324. function( $p_array ) {
  325. $c_bug_id = (int)$p_array[2];
  326. if( bug_exists( $c_bug_id ) ) {
  327. # Create link regardless of user's access to the bug
  328. return $p_array[1] .
  329. string_get_bug_view_url_with_fqdn( $c_bug_id );
  330. }
  331. return $p_array[0];
  332. }; # end of bug link callback closure
  333. }
  334. }
  335. $p_string = preg_replace_callback(
  336. '/(^|[^\w&])' . preg_quote( $t_tag, '/' ) . '(\d+)\b/',
  337. $s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn],
  338. $p_string
  339. );
  340. return $p_string;
  341. }
  342. /**
  343. * Process $p_string, looking for bugnote ID references and creating bug view
  344. * links for them.
  345. *
  346. * Returns the processed string.
  347. *
  348. * If $p_include_anchor is true, include the href tag, otherwise just insert
  349. * the URL
  350. *
  351. * The bugnote tag ('~' by default) must be at the beginning of the string or
  352. * preceded by a character that is not a letter, a number or an underscore
  353. *
  354. * if $p_include_anchor = false, $p_fqdn is ignored and assumed to true.
  355. * @param string $p_string String to be processed.
  356. * @param boolean $p_include_anchor Whether to include the href tag or just the URL.
  357. * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
  358. * @param boolean $p_fqdn Whether to return an absolute or relative link.
  359. * @return string
  360. */
  361. function string_process_bugnote_link( $p_string, $p_include_anchor = true, $p_detail_info = true, $p_fqdn = false ) {
  362. static $s_bugnote_link_callback = array();
  363. $t_tag = config_get( 'bugnote_link_tag' );
  364. # bail if the link tag is blank
  365. if( '' == $t_tag || $p_string == '' ) {
  366. return $p_string;
  367. }
  368. if( !isset( $s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] ) ) {
  369. if( $p_include_anchor ) {
  370. $s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
  371. function( $p_array ) use( $p_detail_info, $p_fqdn ) {
  372. global $g_project_override;
  373. $c_bugnote_id = (int)$p_array[2];
  374. if( bugnote_exists( $c_bugnote_id ) ) {
  375. $t_bug_id = bugnote_get_field( $c_bugnote_id, 'bug_id' );
  376. if( bug_exists( $t_bug_id ) ) {
  377. $g_project_override = bug_get_field( $t_bug_id, 'project_id' );
  378. if( access_compare_level(
  379. user_get_access_level( auth_get_current_user_id(),
  380. bug_get_field( $t_bug_id, 'project_id' ) ),
  381. config_get( 'private_bugnote_threshold' )
  382. )
  383. || bugnote_get_field( $c_bugnote_id, 'reporter_id' ) == auth_get_current_user_id()
  384. || bugnote_get_field( $c_bugnote_id, 'view_state' ) == VS_PUBLIC
  385. ) {
  386. $g_project_override = null;
  387. return $p_array[1] .
  388. string_get_bugnote_view_link(
  389. $t_bug_id,
  390. $c_bugnote_id,
  391. (boolean)$p_detail_info,
  392. (boolean)$p_fqdn
  393. );
  394. }
  395. $g_project_override = null;
  396. }
  397. }
  398. return $p_array[0];
  399. }; # end of bugnote link callback closure
  400. } else {
  401. $s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
  402. function( $p_array ) {
  403. $c_bugnote_id = (int)$p_array[2];
  404. if( bugnote_exists( $c_bugnote_id ) ) {
  405. $t_bug_id = bugnote_get_field( $c_bugnote_id, 'bug_id' );
  406. if( $t_bug_id && bug_exists( $t_bug_id ) ) {
  407. return $p_array[1] .
  408. string_get_bugnote_view_url_with_fqdn( $t_bug_id, $c_bugnote_id );
  409. }
  410. }
  411. return $p_array[0];
  412. }; # end of bugnote link callback closure
  413. }
  414. }
  415. $p_string = preg_replace_callback(
  416. '/(^|[^\w])' . preg_quote( $t_tag, '/' ) . '(\d+)\b/',
  417. $s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn],
  418. $p_string
  419. );
  420. return $p_string;
  421. }
  422. /**
  423. * Search email addresses and URLs for a few common protocols in the given
  424. * string, and replace occurrences with href anchors.
  425. * @param string $p_string String to be processed.
  426. * @return string
  427. */
  428. function string_insert_hrefs( $p_string ) {
  429. static $s_url_regex = null;
  430. static $s_email_regex = null;
  431. if( !config_get( 'html_make_links' ) ) {
  432. return $p_string;
  433. }
  434. # Initialize static variables
  435. if( is_null( $s_url_regex ) ) {
  436. # URL protocol. The regex accepts a small subset from the list of valid
  437. # IANA permanent and provisional schemes defined in
  438. # http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
  439. $t_url_protocol = '(?:https?|s?ftp|file|irc[6s]?|ssh|telnet|nntp|git|svn(?:\+ssh)?|cvs):\/\/';
  440. # %2A notation in url's
  441. $t_url_hex = '%[[:digit:]A-Fa-f]{2}';
  442. # valid set of characters that may occur in url scheme. Note: - should be first (A-F != -AF).
  443. $t_url_valid_chars = '-_.,!~*\';\/?%^\\\\:@&={\|}+$#[:alnum:]\pL';
  444. $t_url_chars = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)\[\]])";
  445. $t_url_chars2 = "(?:${t_url_hex}|[${t_url_valid_chars}])";
  446. $t_url_chars_in_brackets = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)])";
  447. $t_url_chars_in_parens = "(?:${t_url_hex}|[${t_url_valid_chars}\[\]])";
  448. $t_url_part1 = $t_url_chars;
  449. $t_url_part2 = "(?:\(${t_url_chars_in_parens}*\)|\[${t_url_chars_in_brackets}*\]|${t_url_chars2})";
  450. $s_url_regex = "/(${t_url_protocol}(${t_url_part1}*?${t_url_part2}+))/su";
  451. # e-mail regex
  452. $s_email_regex = substr_replace( email_regex_simple(), '(?:mailto:)?', 1, 0 );
  453. }
  454. # Find any URL in a string and replace it with a clickable link
  455. $p_string = preg_replace_callback(
  456. $s_url_regex,
  457. function ( $p_match ) {
  458. $t_url_href = 'href="' . rtrim( $p_match[1], '.' ) . '"';
  459. if( config_get( 'html_make_links' ) == LINKS_NEW_WINDOW ) {
  460. $t_url_target = ' target="_blank"';
  461. } else {
  462. $t_url_target = '';
  463. }
  464. return "<a ${t_url_href}${t_url_target}>${p_match[1]}</a>";
  465. },
  466. $p_string
  467. );
  468. # Find any email addresses in the string and replace them with a clickable
  469. # mailto: link, making sure that we skip processing of any existing anchor
  470. # tags, to avoid parts of URLs such as https://user@example.com/ or
  471. # http://user:password@example.com/ to be not treated as an email.
  472. $p_string = string_process_exclude_anchors(
  473. $p_string,
  474. function( $p_string ) use ( $s_email_regex ) {
  475. return preg_replace( $s_email_regex, '<a href="mailto:\0">\0</a>', $p_string );
  476. }
  477. );
  478. return $p_string;
  479. }
  480. /**
  481. * Processes a string, ignoring anchor tags.
  482. * Applies the specified callback function to the text between anchor tags;
  483. * the anchors themselves will be left as-is.
  484. * @param string $p_string String to process
  485. * @param callable $p_callback Function to apply
  486. * @return string
  487. */
  488. function string_process_exclude_anchors( $p_string, $p_callback ) {
  489. static $s_anchor_regex = '/(<a[^>]*>.*?<\/a>)/is';
  490. $t_pieces = preg_split( $s_anchor_regex, $p_string, null, PREG_SPLIT_DELIM_CAPTURE );
  491. $t_string = '';
  492. foreach( $t_pieces as $t_piece ) {
  493. if( preg_match( $s_anchor_regex, $t_piece ) ) {
  494. $t_string .= $t_piece;
  495. } else {
  496. $t_string .= $p_callback( $t_piece );
  497. }
  498. }
  499. return $t_string;
  500. }
  501. /**
  502. * Detect href anchors in the string and replace them with URLs and email addresses
  503. * @param string $p_string String to be processed.
  504. * @return string
  505. */
  506. function string_strip_hrefs( $p_string ) {
  507. # First grab mailto: hrefs. We don't care whether the URL is actually
  508. # correct - just that it's inside an href attribute.
  509. $p_string = preg_replace( '/<a\s[^\>]*href="mailto:([^\"]+)"[^\>]*>[^\<]*<\/a>/si', '\1', $p_string );
  510. # Then grab any other href
  511. $p_string = preg_replace( '/<a\s[^\>]*href="([^\"]+)"[^\>]*>[^\<]*<\/a>/si', '\1', $p_string );
  512. return $p_string;
  513. }
  514. /**
  515. * This function looks for text with htmlentities
  516. * like &lt;b&gt; and converts it into the corresponding
  517. * html < b > tag based on the configuration information
  518. * @param string $p_string String to be processed.
  519. * @param boolean $p_multiline Whether the string being processed is a multi-line string.
  520. * @return string
  521. */
  522. function string_restore_valid_html_tags( $p_string, $p_multiline = true ) {
  523. global $g_cache_html_valid_tags_single_line, $g_cache_html_valid_tags;
  524. if( is_blank( ( $p_multiline ? $g_cache_html_valid_tags : $g_cache_html_valid_tags_single_line ) ) ) {
  525. $t_html_valid_tags = config_get_global( $p_multiline ? 'html_valid_tags' : 'html_valid_tags_single_line' );
  526. if( OFF === $t_html_valid_tags || is_blank( $t_html_valid_tags ) ) {
  527. return $p_string;
  528. }
  529. $t_tags = explode( ',', $t_html_valid_tags );
  530. foreach( $t_tags as $t_key => $t_value ) {
  531. if( !is_blank( $t_value ) ) {
  532. $t_tags[$t_key] = trim( $t_value );
  533. }
  534. }
  535. $t_tags = implode( '|', $t_tags );
  536. if( $p_multiline ) {
  537. $g_cache_html_valid_tags = $t_tags;
  538. } else {
  539. $g_cache_html_valid_tags_single_line = $t_tags;
  540. }
  541. } else {
  542. $t_tags = ( $p_multiline ? $g_cache_html_valid_tags : $g_cache_html_valid_tags_single_line );
  543. }
  544. $p_string = preg_replace( '/&lt;(' . $t_tags . ')\s*&gt;/ui', '<\\1>', $p_string );
  545. $p_string = preg_replace( '/&lt;\/(' . $t_tags . ')\s*&gt;/ui', '</\\1>', $p_string );
  546. return preg_replace( '/&lt;(' . $t_tags . ')\s*\/&gt;/ui', '<\\1 />', $p_string );
  547. }
  548. /**
  549. * return the name of a bug page
  550. * $p_action should be something like 'view', 'update', or 'report'
  551. * @param string $p_action A valid action being performed - currently one of view, update or report.
  552. * @return string
  553. */
  554. function string_get_bug_page( $p_action ) {
  555. switch( $p_action ) {
  556. case 'view':
  557. return 'bug_view_page.php';
  558. case 'update':
  559. return 'bug_update_page.php';
  560. case 'report':
  561. return 'bug_report_page.php';
  562. }
  563. trigger_error( ERROR_GENERIC, ERROR );
  564. }
  565. /**
  566. * return an href anchor that links to a bug VIEW page for the given bug
  567. * @param integer $p_bug_id A bug identifier.
  568. * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
  569. * @param boolean $p_fqdn Whether to return an absolute or relative link.
  570. * @return string
  571. */
  572. function string_get_bug_view_link( $p_bug_id, $p_detail_info = true, $p_fqdn = false ) {
  573. if( bug_exists( $p_bug_id ) ) {
  574. $t_link = '<a href="';
  575. if( $p_fqdn ) {
  576. $t_link .= config_get_global( 'path' );
  577. } else {
  578. $t_link .= config_get_global( 'short_path' );
  579. }
  580. $t_link .= string_get_bug_view_url( $p_bug_id ) . '"';
  581. if( $p_detail_info ) {
  582. $t_summary = string_attribute( bug_get_field( $p_bug_id, 'summary' ) );
  583. $t_project_id = bug_get_field( $p_bug_id, 'project_id' );
  584. $t_status = string_attribute( get_enum_element( 'status', bug_get_field( $p_bug_id, 'status' ), $t_project_id ) );
  585. $t_link .= ' title="[' . $t_status . '] ' . $t_summary . '"';
  586. $t_resolved = bug_get_field( $p_bug_id, 'status' ) >= config_get( 'bug_resolved_status_threshold', null, null, $t_project_id );
  587. if( $t_resolved ) {
  588. $t_link .= ' class="resolved"';
  589. }
  590. }
  591. $t_link .= '>' . bug_format_id( $p_bug_id ) . '</a>';
  592. } else {
  593. $t_link = bug_format_id( $p_bug_id );
  594. }
  595. return $t_link;
  596. }
  597. /**
  598. * return an href anchor that links to a bug VIEW page for the given bug
  599. * @param integer $p_bug_id A bug identifier.
  600. * @param integer $p_bugnote_id A bugnote identifier.
  601. * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
  602. * @param boolean $p_fqdn Whether to return an absolute or relative link.
  603. * @return string
  604. */
  605. function string_get_bugnote_view_link( $p_bug_id, $p_bugnote_id, $p_detail_info = true, $p_fqdn = false ) {
  606. $t_bug_id = (int)$p_bug_id;
  607. if( bug_exists( $t_bug_id ) && bugnote_exists( $p_bugnote_id ) ) {
  608. $t_link = '<a href="';
  609. if( $p_fqdn ) {
  610. $t_link .= config_get_global( 'path' );
  611. } else {
  612. $t_link .= config_get_global( 'short_path' );
  613. }
  614. $t_link .= string_get_bugnote_view_url( $p_bug_id, $p_bugnote_id ) . '"';
  615. if( $p_detail_info ) {
  616. $t_reporter = string_attribute( user_get_name( bugnote_get_field( $p_bugnote_id, 'reporter_id' ) ) );
  617. $t_update_date = string_attribute( date( config_get( 'normal_date_format' ), ( bugnote_get_field( $p_bugnote_id, 'last_modified' ) ) ) );
  618. $t_link .= ' title="' . bug_format_id( $t_bug_id ) . ': [' . $t_update_date . '] ' . $t_reporter . '"';
  619. }
  620. $t_link .= '>' . bug_format_id( $t_bug_id ) . ':' . bugnote_format_id( $p_bugnote_id ) . '</a>';
  621. } else {
  622. $t_link = bugnote_format_id( $t_bug_id ) . ':' . bugnote_format_id( $p_bugnote_id );
  623. }
  624. return $t_link;
  625. }
  626. /**
  627. * return the name and GET parameters of a bug VIEW page for the given bug
  628. * @param integer $p_bug_id A bug identifier.
  629. * @return string
  630. */
  631. function string_get_bug_view_url( $p_bug_id ) {
  632. return 'view.php?id=' . $p_bug_id;
  633. }
  634. /**
  635. * return the name and GET parameters of a bug VIEW page for the given bug
  636. * @param integer $p_bug_id A bug identifier.
  637. * @param integer $p_bugnote_id A bugnote identifier.
  638. * @return string
  639. */
  640. function string_get_bugnote_view_url( $p_bug_id, $p_bugnote_id ) {
  641. return 'view.php?id=' . $p_bug_id . '#c' . $p_bugnote_id;
  642. }
  643. /**
  644. * return the name and GET parameters of a bug VIEW page for the given bug
  645. * account for the user preference and site override
  646. * The returned url includes the fully qualified domain, hence it is suitable to be included
  647. * in emails.
  648. * @param integer $p_bug_id A bug identifier.
  649. * @param integer $p_bugnote_id A bug note identifier.
  650. * @return string
  651. */
  652. function string_get_bugnote_view_url_with_fqdn( $p_bug_id, $p_bugnote_id ) {
  653. return config_get_global( 'path' ) . string_get_bug_view_url( $p_bug_id ) . '#c' . $p_bugnote_id;
  654. }
  655. /**
  656. * return the name and GET parameters of a bug VIEW page for the given bug
  657. * account for the user preference and site override
  658. * The returned url includes the fully qualified domain, hence it is suitable to be included in emails.
  659. * @param integer $p_bug_id A bug identifier.
  660. * @return string
  661. */
  662. function string_get_bug_view_url_with_fqdn( $p_bug_id ) {
  663. return config_get_global( 'path' ) . string_get_bug_view_url( $p_bug_id );
  664. }
  665. /**
  666. * return an href anchor that links to a bug UPDATE page for the given bug
  667. * @param integer $p_bug_id A bug identifier.
  668. * @return string
  669. */
  670. function string_get_bug_update_link( $p_bug_id ) {
  671. $t_summary = string_attribute( bug_get_field( $p_bug_id, 'summary' ) );
  672. return '<a href="' . helper_mantis_url( string_get_bug_update_url( $p_bug_id ) ) . '" title="' . $t_summary . '">' . bug_format_id( $p_bug_id ) . '</a>';
  673. }
  674. /**
  675. * return the name and GET parameters of a bug UPDATE page
  676. * @param integer $p_bug_id A bug identifier.
  677. * @return string
  678. */
  679. function string_get_bug_update_url( $p_bug_id ) {
  680. return string_get_bug_update_page() . '?bug_id=' . $p_bug_id;
  681. }
  682. /**
  683. * return the name of a bug UPDATE page
  684. * @return string
  685. */
  686. function string_get_bug_update_page() {
  687. return string_get_bug_page( 'update' );
  688. }
  689. /**
  690. * return an href anchor that links to a bug REPORT page
  691. * @return string
  692. */
  693. function string_get_bug_report_link() {
  694. return '<a href="' . helper_mantis_url( string_get_bug_report_url() ) . '">' . lang_get( 'report_bug_link' ) . '</a>';
  695. }
  696. /**
  697. * return the name of a bug REPORT page
  698. * @return string
  699. */
  700. function string_get_bug_report_url() {
  701. return string_get_bug_page( 'report' );
  702. }
  703. /**
  704. * return the complete URL link to the verify page including the confirmation hash
  705. * @param integer $p_user_id A valid user identifier.
  706. * @param string $p_confirm_hash The confirmation hash value to include in the link.
  707. * @return string
  708. */
  709. function string_get_confirm_hash_url( $p_user_id, $p_confirm_hash ) {
  710. return config_get_global( 'path' ) . 'verify.php?id=' . string_url( $p_user_id ) . '&confirm_hash=' . string_url( $p_confirm_hash );
  711. }
  712. /**
  713. * Format date for display
  714. * @param integer $p_date A date value to process.
  715. * @return string
  716. */
  717. function string_format_complete_date( $p_date ) {
  718. return date( config_get( 'complete_date_format' ), $p_date );
  719. }
  720. /**
  721. * Shorten a string for display on a dropdown to prevent the page rendering too wide
  722. * ref issues #4630, #5072, #5131
  723. * @param string $p_string The string to process.
  724. * @param integer $p_max The maximum length of the string to use.
  725. * If not set, defaults to max_dropdown_length configuration variable.
  726. * @return string
  727. */
  728. function string_shorten( $p_string, $p_max = null ) {
  729. if( $p_max === null ) {
  730. $t_max = config_get( 'max_dropdown_length' );
  731. } else {
  732. $t_max = (int)$p_max;
  733. }
  734. if( ( $t_max > 0 ) && ( mb_strlen( $p_string ) > $t_max ) ) {
  735. $t_pattern = '/([\s|.|,|\-|_|\/|\?]+)/';
  736. $t_bits = preg_split( $t_pattern, $p_string, -1, PREG_SPLIT_DELIM_CAPTURE );
  737. $t_string = '';
  738. $t_last = $t_bits[count( $t_bits ) - 1];
  739. $t_last_len = strlen( $t_last );
  740. if( count( $t_bits ) == 1 ) {
  741. $t_string .= mb_substr( $t_last, 0, $t_max - 3 );
  742. $t_string .= '...';
  743. } else {
  744. foreach( $t_bits as $t_bit ) {
  745. if( ( mb_strlen( $t_string ) + mb_strlen( $t_bit ) + $t_last_len + 3 <= $t_max ) || ( strpos( $t_bit, '.,-/?' ) > 0 ) ) {
  746. $t_string .= $t_bit;
  747. } else {
  748. break;
  749. }
  750. }
  751. $t_string .= '...' . $t_last;
  752. }
  753. return $t_string;
  754. } else {
  755. return $p_string;
  756. }
  757. }
  758. /**
  759. * Normalize a string by removing leading, trailing and excessive internal spaces
  760. * note a space is used as the pattern instead of '\s' to make it work with UTF-8 strings
  761. * @param string $p_string The string to process.
  762. * @return string
  763. */
  764. function string_normalize( $p_string ) {
  765. return preg_replace( '/ +/', ' ', trim( $p_string ) );
  766. }
  767. /**
  768. * remap a field name to a string name (for sort filter)
  769. * @param string $p_string The string to process.
  770. * @return string
  771. */
  772. function string_get_field_name( $p_string ) {
  773. $t_map = array(
  774. 'attachment_count' => 'attachments',
  775. 'category_id' => 'category',
  776. 'handler_id' => 'assigned_to',
  777. 'id' => 'email_bug',
  778. 'last_updated' => 'updated',
  779. 'project_id' => 'email_project',
  780. 'reporter_id' => 'reporter',
  781. 'view_state' => 'view_status',
  782. );
  783. $t_string = $p_string;
  784. if( isset( $t_map[$p_string] ) ) {
  785. $t_string = $t_map[$p_string];
  786. }
  787. return lang_get_defaulted( $t_string );
  788. }
  789. /**
  790. * Calls htmlentities on the specified string, passing along
  791. * the current character set.
  792. * @param string $p_string The string to process.
  793. * @return string
  794. */
  795. function string_html_entities( $p_string ) {
  796. return htmlentities( $p_string, ENT_COMPAT, 'utf-8' );
  797. }
  798. /**
  799. * Calls htmlspecialchars on the specified string, handling utf8
  800. * @param string $p_string The string to process.
  801. * @return string
  802. */
  803. function string_html_specialchars( $p_string ) {
  804. # achumakov: @ added to avoid warning output in unsupported codepages
  805. # e.g. 8859-2, windows-1257, Korean, which are treated as 8859-1.
  806. # This is VERY important for Eastern European, Baltic and Korean languages
  807. return preg_replace( '/&amp;(#[0-9]+|[a-z]+);/i', '&$1;', @htmlspecialchars( $p_string, ENT_COMPAT, 'utf-8' ) );
  808. }
  809. /**
  810. * Prepares a string to be used as part of header().
  811. * @param string $p_string The string to process.
  812. * @return string
  813. */
  814. function string_prepare_header( $p_string ) {
  815. $t_string= explode( "\n", $p_string, 2 );
  816. $t_string= explode( "\r", $t_string[0], 2 );
  817. return $t_string[0];
  818. }
  819. /**
  820. * Replacement for str_pad. $padStr may contain multi-byte characters.
  821. *
  822. * @author Oliver Saunders <oliver (a) osinternetservices.com>
  823. * @param string $input
  824. * @param int $length
  825. * @param string $padStr
  826. * @param int $type ( same constants as str_pad )
  827. * @return string
  828. * @see http://www.php.net/str_pad
  829. * @see utf8_substr
  830. */
  831. function utf8_str_pad( $input, $length, $padStr = ' ', $type = STR_PAD_RIGHT ) {
  832. $inputLen = mb_strlen($input);
  833. if ($length <= $inputLen) {
  834. return $input;
  835. }
  836. $padStrLen = mb_strlen($padStr);
  837. $padLen = $length - $inputLen;
  838. if ($type == STR_PAD_RIGHT) {
  839. $repeatTimes = ceil($padLen / $padStrLen);
  840. return mb_substr($input . str_repeat($padStr, $repeatTimes), 0, $length);
  841. }
  842. if ($type == STR_PAD_LEFT) {
  843. $repeatTimes = ceil($padLen / $padStrLen);
  844. return mb_substr(str_repeat($padStr, $repeatTimes), 0, floor($padLen)) . $input;
  845. }
  846. if ($type == STR_PAD_BOTH) {
  847. $padLen/= 2;
  848. $padAmountLeft = floor($padLen);
  849. $padAmountRight = ceil($padLen);
  850. $repeatTimesLeft = ceil($padAmountLeft / $padStrLen);
  851. $repeatTimesRight = ceil($padAmountRight / $padStrLen);
  852. $paddingLeft = mb_substr(str_repeat($padStr, $repeatTimesLeft), 0, $padAmountLeft);
  853. $paddingRight = mb_substr(str_repeat($padStr, $repeatTimesRight), 0, $padAmountLeft);
  854. return $paddingLeft . $input . $paddingRight;
  855. }
  856. trigger_error('utf8_str_pad: Unknown padding type (' . $type . ')',E_USER_ERROR);
  857. }
  858. /**
  859. * Return the number of UTF-8 characters in a string
  860. * @param string $p_string
  861. * @return integer number of UTF-8 characters in string
  862. * @deprecated mb_strlen() should be used in preference to this function
  863. */
  864. function utf8_strlen( $p_string ) {
  865. error_parameters( __FUNCTION__ . '()', 'mb_strlen()' );
  866. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  867. return mb_strlen( $p_string );
  868. }
  869. /**
  870. * Get part of string
  871. * @param string $p_string
  872. * @param integer $p_offset
  873. * @param integer $p_length
  874. * @return mixed string or FALSE if failure
  875. * @deprecated mb_substr() should be used in preference to this function
  876. */
  877. function utf8_substr( $p_string, $p_offset, $p_length = NULL ) {
  878. error_parameters( __FUNCTION__ . '()', 'mb_substr()' );
  879. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  880. return mb_substr( $p_string, $p_offset, $p_length );
  881. }
  882. /**
  883. * Make a string lowercase
  884. * @param string $p_string
  885. * @return string with all alphabetic characters converted to lowercase
  886. * @deprecated mb_strtolower() should be used in preference to this function
  887. */
  888. function utf8_strtolower( $p_string ) {
  889. error_parameters( __FUNCTION__ . '()', 'mb_strtolower()' );
  890. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  891. return mb_strtolower( $p_string );
  892. }