TextExtHelper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <?php
  2. App::uses('TextHelper', 'View/Helper');
  3. App::uses('HtmlHelper', 'View/Helper');
  4. App::uses('View', 'View');
  5. /**
  6. * The core text helper is unsecure and outdated in functionality
  7. * this aims to compensate the deficiencies
  8. *
  9. * autoLinkEmails
  10. * - obfuscate (defaults to FALSE right now)
  11. * (- maxLength?)
  12. * - escape (defaults to TRUE for security reasons regarding plain text)
  13. *
  14. * autoLinkUrls
  15. * - stripProtocol (defaults To FALSE right now)
  16. * - maxLength (to shorten links in order to not mess up the layout in some cases - appends ...)
  17. * - escape (defaults to TRUE for security reasons regarding plain text)
  18. *
  19. */
  20. class TextExtHelper extends TextHelper {
  21. /**
  22. * Formats paragraphs around given text for all line breaks
  23. * <br /> added for single line return
  24. * <p> added for double line return
  25. *
  26. * @param string $text Text
  27. * @return string The text with proper <p> and <br /> tags
  28. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::autoParagraph
  29. */
  30. public function autoParagraph($text) {
  31. // for cake >= 2.4
  32. if (method_exists(get_parent_class(), 'autoParagraph')) {
  33. return parent::autoParagraph($text);
  34. }
  35. if (trim($text) !== '') {
  36. $text = preg_replace('|<br[^>]*>\s*<br[^>]*>|i', "\n\n", $text . "\n");
  37. $text = preg_replace("/\n\n+/", "\n\n", str_replace(array("\r\n", "\r"), "\n", $text));
  38. $texts = preg_split('/\n\s*\n/', $text, -1, PREG_SPLIT_NO_EMPTY);
  39. $text = '';
  40. foreach ($texts as $txt) {
  41. $text .= '<p>' . nl2br(trim($txt, "\n")) . "</p>\n";
  42. }
  43. $text = preg_replace('|<p>\s*</p>|', '', $text);
  44. }
  45. return $text;
  46. }
  47. /**
  48. * Convert all links and email adresses to HTML links.
  49. *
  50. * @param string $text Text
  51. * @param array $options Array of HTML options.
  52. * @return string The text with links
  53. * @link http://book.cakephp.org/view/1469/Text#autoLink-1620
  54. */
  55. public function autoLink($text, $options = array(), $htmlOptions = array()) {
  56. if (!isset($options['escape']) || $options['escape'] !== false) {
  57. $text = h($text);
  58. $options['escape'] = false;
  59. }
  60. return $this->autoLinkEmails($this->autoLinkUrls($text, $options, $htmlOptions), $options, $htmlOptions);
  61. }
  62. /**
  63. * Fix to allow obfuscation of email (js, img?)
  64. * @param string $text
  65. * @param htmlOptions (additionally - not yet supported by core):
  66. * - obfuscate: true/false (defaults to false)
  67. * @param array $options
  68. * - escape (defaults to true)
  69. * @return string html
  70. * @override
  71. */
  72. public function autoLinkEmails($text, $options = array(), $htmlOptions = array()) {
  73. if (!isset($options['escape']) || $options['escape'] !== false) {
  74. $text = h($text);
  75. }
  76. $linkOptions = 'array(';
  77. foreach ($htmlOptions as $option => $value) {
  78. $value = var_export($value, true);
  79. $linkOptions .= "'$option' => $value, ";
  80. }
  81. $linkOptions .= ')';
  82. $customOptions = 'array(';
  83. foreach ($options as $option => $value) {
  84. $value = var_export($value, true);
  85. $customOptions .= "'$option' => $value, ";
  86. }
  87. $customOptions .= ')';
  88. $atom = '[a-z0-9!#$%&\'*+\/=?^_`{|}~-]';
  89. return preg_replace_callback('/(' . $atom . '+(?:\.' . $atom . '+)*@[a-z0-9-]+(?:\.[a-z0-9-]+)+)/i',
  90. create_function('$matches', 'return TextExtHelper::prepareEmail($matches[0],' . $linkOptions . ',' . $customOptions . ');'), $text);
  91. }
  92. /**
  93. * @param string $email
  94. * @param options:
  95. * - obfuscate: true/false (defaults to false)
  96. * @return string html
  97. */
  98. public static function prepareEmail($email, $options = array(), $customOptions = array()) {
  99. $obfuscate = false;
  100. if (isset($options['obfuscate'])) {
  101. $obfuscate = $options['obfuscate'];
  102. unset($options['obfuscate']);
  103. }
  104. if (!isset($customOptions['escape']) || $customOptions['escape'] !== false) {
  105. $email = hDec($email);
  106. }
  107. $Html = new HtmlHelper(new View(null));
  108. //$Html->tags = $Html->loadConfig();
  109. //debug($Html->tags);
  110. if (!$obfuscate) {
  111. return $Html->link($email, "mailto:" . $email, $options);
  112. }
  113. $class = __CLASS__;
  114. $Common = new $class;
  115. $Common->Html = $Html;
  116. return $Common->encodeEmailUrl($email, null, array(), $options);
  117. }
  118. /**
  119. * Helper Function to Obfuscate Email by inserting a span tag (not more! not very secure on its own...)
  120. * each part of this mail now does not make sense anymore on its own
  121. * (striptags will not work either)
  122. * @param string email: necessary (and valid - containing one @)
  123. * @return string html
  124. */
  125. public function encodeEmail($mail) {
  126. list($mail1, $mail2) = explode('@', $mail);
  127. $encMail = $this->encodeText($mail1) . '<span>@</span>' . $this->encodeText($mail2);
  128. return $encMail;
  129. }
  130. /**
  131. * Obfuscates Email (works without JS!) to avoid lowlevel spam bots to get it
  132. * @param string mail: email to encode
  133. * @param string text: optional (if none is given, email will be text as well)
  134. * @param array attributes: html tag attributes
  135. * @param array params: ?subject=y&body=y to be attached to "mailto:xyz"
  136. * @return string html with js generated link around email (and non js fallback)
  137. */
  138. public function encodeEmailUrl($mail, $text=null, $params=array(), $attr = array()) {
  139. if (empty($class)) {
  140. $class = 'email';
  141. }
  142. $defaults = array(
  143. 'title' => __('for use in an external mail client'),
  144. 'class' => 'email',
  145. 'escape' => false
  146. );
  147. if (empty($text)) {
  148. $text = $this->encodeEmail($mail);
  149. }
  150. $encMail = 'mailto:' . $mail;
  151. //$encMail = $this->encodeText($encMail); # not possible
  152. // additionally there could be a span tag in between: email<span syle="display:none"></span>@web.de
  153. $querystring = '';
  154. foreach ($params as $key => $val) {
  155. if ($querystring) {
  156. $querystring .= "&$key=" . rawurlencode($val);
  157. } else {
  158. $querystring = "?$key=" . rawurlencode($val);
  159. }
  160. }
  161. $attr = array_merge($defaults, $attr);
  162. $xmail = $this->Html->link('', $encMail . $querystring, $attr);
  163. $xmail1 = mb_substr($xmail, 0, count($xmail) - 5);
  164. $xmail2 = mb_substr($xmail, -4, 4);
  165. $len = mb_strlen($xmail1);
  166. $i = 0;
  167. while ($i < $len) {
  168. $c = mt_rand(2, 6);
  169. $par[] = (mb_substr($xmail1, $i, $c));
  170. $i += $c;
  171. }
  172. $join = implode('\'+\'', $par);
  173. return '<script language=javascript><!--
  174. document.write(\'' . $join . '\');
  175. //--></script>
  176. ' . $text . '
  177. <script language=javascript><!--
  178. document.write(\'' . $xmail2 . '\');
  179. //--></script>';
  180. //return '<a class="'.$class.'" title="'.$title.'" href="'.$encmail.$querystring.'">'.$encText.'</a>';
  181. }
  182. /**
  183. * Encodes Piece of Text (without usage of JS!) to avoid lowlevel spam bots to get it
  184. * @param STRING text to encode
  185. * @return string html (randomly encoded)
  186. */
  187. public static function encodeText($text) {
  188. $encmail = '';
  189. $length = mb_strlen($text);
  190. for ($i = 0; $i < $length; $i++) {
  191. $encMod = mt_rand(0, 2);
  192. switch ($encMod) {
  193. case 0: // None
  194. $encmail .= mb_substr($text, $i, 1);
  195. break;
  196. case 1: // Decimal
  197. $encmail .= "&#" . ord(mb_substr($text, $i, 1)) . ';';
  198. break;
  199. case 2: // Hexadecimal
  200. $encmail .= "&#x" . dechex(ord(mb_substr($text, $i, 1))) . ';';
  201. break;
  202. }
  203. }
  204. return $encmail;
  205. }
  206. /**
  207. * Fix to allow shortened urls that do not break layout etc
  208. *
  209. * @param string $text
  210. * @param options (additionally - not yet supported by core):
  211. * - stripProtocol: bool (defaults to true)
  212. * - maxLength: int (defaults no none)
  213. * @param htmlOptions
  214. * - escape etc
  215. * @return string html
  216. * @override
  217. */
  218. public function autoLinkUrls($text, $options = array(), $htmlOptions = array()) {
  219. if (!isset($options['escape']) || $options['escape'] !== false) {
  220. $text = h($text);
  221. $matchString = 'hDec($matches[0])';
  222. } else {
  223. $matchString = '$matches[0]';
  224. }
  225. if (isset($htmlOptions['escape'])) {
  226. $options['escape'] = $htmlOptions['escape'];
  227. }
  228. //$htmlOptions['escape'] = false;
  229. $htmlOptions = var_export($htmlOptions, true);
  230. $customOptions = var_export($options, true);
  231. $text = preg_replace_callback('#(?<!href="|">)((?:https?|ftp|nntp)://[^\s<>()]+)#i', create_function('$matches',
  232. '$Html = new HtmlHelper(new View(null)); return $Html->link(TextExtHelper::prepareLinkName(hDec($matches[0]), ' . $customOptions . '), hDec($matches[0]),' . $htmlOptions . ');'), $text);
  233. return preg_replace_callback('#(?<!href="|">)(?<!http://|https://|ftp://|nntp://)(www\.[^\n\%\ <]+[^<\n\%\,\.\ <])(?<!\))#i',
  234. create_function('$matches', '$Html = new HtmlHelper(new View(null)); return $Html->link(TextExtHelper::prepareLinkName(hDec($matches[0]), ' . $customOptions . '), "http://" . hDec($matches[0]),' . $htmlOptions . ');'), $text);
  235. }
  236. /**
  237. * @param string $link
  238. * @param options:
  239. * - stripProtocol: bool (defaults to true)
  240. * - maxLength: int (defaults to 50)
  241. * - escape (defaults to false, true needed for hellip to work)
  242. * @return string html/$plain
  243. */
  244. public static function prepareLinkName($link, $options = array()) {
  245. # strip protocol if desired (default)
  246. if (!isset($options['stripProtocol']) || $options['stripProtocol'] !== false) {
  247. $link = self::stripProtocol($link);
  248. }
  249. if (!isset($options['maxLength'])) {
  250. $options['maxLength'] = 50; # should be long enough for most cases
  251. }
  252. # shorten display name if desired (default)
  253. if (!empty($options['maxLength']) && mb_strlen($link) > $options['maxLength']) {
  254. $link = mb_substr($link, 0, $options['maxLength']);
  255. # problematic with autoLink()
  256. if (!empty($options['html']) && isset($options['escape']) && $options['escape'] === false) {
  257. $link .= '&hellip;'; # only possible with escape => false!
  258. } else {
  259. $link .= '...';
  260. }
  261. }
  262. return $link;
  263. }
  264. /**
  265. * Remove http:// or other protocols from the link
  266. *
  267. * @param string $url
  268. * @return string strippedUrl
  269. */
  270. public static function stripProtocol($url) {
  271. $pieces = parse_url($url);
  272. if (empty($pieces['scheme'])) {
  273. return $url; # already stripped
  274. }
  275. return mb_substr($url, mb_strlen($pieces['scheme']) + 3); # +3 <=> :// # can only be 4 with "file" (file:///)...
  276. }
  277. /**
  278. * Minimizes the given url to a maximum length
  279. *
  280. * @param string $url the url
  281. * @param integer $max the maximum length
  282. * @param array $options
  283. * - placeholder
  284. * @return string the manipulated url (+ eventuell ...)
  285. */
  286. public function minimizeUrl($url = null, $max = null, $options = array()) {
  287. // check if there is nothing to do
  288. if (empty($url) || mb_strlen($url) <= (int)$max) {
  289. return (string)$url;
  290. }
  291. // http:// has not to be displayed, so
  292. if (mb_substr($url, 0, 7) === 'http://') {
  293. $url = mb_substr($url, 7);
  294. }
  295. // cut the parameters
  296. if (mb_strpos($url, '/') !== false) {
  297. $url = strtok($url, '/');
  298. }
  299. // return if the url is short enough
  300. if (mb_strlen($url) <= (int)$max) {
  301. return $url;
  302. }
  303. // otherwise cut a part in the middle (but only if long enough!!!)
  304. # TODO: more dynamically
  305. $placeholder = CHAR_HELLIP;
  306. if (!empty($options['placeholder'])) {
  307. $placeholder = $options['placeholder'];
  308. }
  309. $end = mb_substr($url, -5, 5);
  310. $front = mb_substr($url, 0, (int)$max - 8);
  311. return $front . $placeholder . $end;
  312. }
  313. /**
  314. * Transforming int values into ordinal numbers (1st, 3rd, etc.)
  315. * @param $num (INT) - the number to be suffixed.
  316. * @param $sup (BOOL) - whether to wrap the suffix in a superscript (<sup>) tag on output.
  317. * @return string ordinal
  318. */
  319. public static function ordinalNumber($num = 0, $sup = false) {
  320. $suff = '';
  321. if (!in_array(($num % 100), array(11, 12, 13))) {
  322. switch ($num % 10) {
  323. case 1:
  324. $suff = 'st';
  325. break;
  326. case 2:
  327. $suff = 'nd';
  328. break;
  329. case 3:
  330. $suff = 'rd';
  331. break;
  332. default:
  333. $suff = 'th';
  334. }
  335. }
  336. return ($sup) ? $num . '<sup>' . $suff . '</sup>' : $num . $suff;
  337. }
  338. /**
  339. * Syntax highlighting using php internal highlighting
  340. * @param string $filename
  341. * @param boolean $return (else echo directly)
  342. */
  343. public static function highlightFile($file, $return = true) {
  344. return highlight_file($file, $return);
  345. }
  346. /**
  347. * Syntax highlighting using php internal highlighting
  348. * @param string $contentstring
  349. * @param boolean $return (else echo directly)
  350. */
  351. public static function highlightString($string, $return = true) {
  352. return highlight_string($string, $return);
  353. }
  354. }