TextExtHelper.php 12 KB

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