PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/Field/src/Utility/TextToolbox.php

http://github.com/QuickAppsCMS/QuickApps-CMS
PHP | 411 lines | 264 code | 35 blank | 112 comment | 10 complexity | 21cbd2965e023266e207be21d90b2575 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Licensed under The GPL-3.0 License
  4. * For full copyright and license information, please see the LICENSE.txt
  5. * Redistributions of files must retain the above copyright notice.
  6. *
  7. * @since 2.0.0
  8. * @author Christopher Castro <chris@quickapps.es>
  9. * @link http://www.quickappscms.org
  10. * @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
  11. */
  12. namespace Field\Utility;
  13. use CMS\Shortcode\ShortcodeTrait;
  14. use Field\Lib\Parsedown;
  15. use Field\Model\Entity\Field;
  16. /**
  17. * Text utility class.
  18. *
  19. * Utility methods used by TextField Handler.
  20. */
  21. class TextToolbox
  22. {
  23. use ShortcodeTrait;
  24. /**
  25. * Holds an instance of this class.
  26. *
  27. * @var \Field\Utility\TextToolbox
  28. */
  29. protected static $_instance = null;
  30. /**
  31. * Instance of markdown parser class.
  32. *
  33. * @var \Field\Lib\Parsedown
  34. */
  35. protected static $_MarkdownParser;
  36. /**
  37. * Returns an instance of this class.
  38. *
  39. * Useful when we need to use some of the trait methods.
  40. *
  41. * @return \Field\Utility\TextToolbox
  42. */
  43. public static function getInstance()
  44. {
  45. if (!static::$_instance) {
  46. static::$_instance = new TextToolBox();
  47. }
  48. return static::$_instance;
  49. }
  50. /**
  51. * Formats the given field.
  52. *
  53. * @param \Field\Model\Entity\Field $field The field being rendered
  54. * @return string
  55. */
  56. public static function formatter(Field $field)
  57. {
  58. $viewModeSettings = $field->viewModeSettings;
  59. $processing = $field->metadata->settings['text_processing'];
  60. $formatter = $viewModeSettings['formatter'];
  61. $content = static::process($field->value, $processing);
  62. switch ($formatter) {
  63. case 'plain':
  64. $content = static::filterText($content);
  65. break;
  66. case 'trimmed':
  67. $len = $viewModeSettings['trim_length'];
  68. $content = static::trimmer($content, $len);
  69. break;
  70. }
  71. return $content;
  72. }
  73. /**
  74. * Process the given text to its corresponding format.
  75. *
  76. * @param string $content Content to process
  77. * @param string $processor "plain", "filtered", "markdown" or "full"
  78. * @return string
  79. */
  80. public static function process($content, $processor)
  81. {
  82. switch ($processor) {
  83. case 'plain':
  84. $content = static::plainProcessor($content);
  85. break;
  86. case 'filtered':
  87. $content = static::filteredProcessor($content);
  88. break;
  89. case 'markdown':
  90. $content = static::markdownProcessor($content);
  91. break;
  92. }
  93. return $content;
  94. }
  95. /**
  96. * Process text in plain mode.
  97. *
  98. * - No HTML tags allowed.
  99. * - Web page addresses and e-mail addresses turn into links automatically.
  100. * - Lines and paragraphs break automatically.
  101. *
  102. * @param string $text The text to process
  103. * @return string
  104. */
  105. public static function plainProcessor($text)
  106. {
  107. $text = static::emailToLink($text);
  108. $text = static::urlToLink($text);
  109. $text = nl2br($text);
  110. return $text;
  111. }
  112. /**
  113. * Process text in full HTML mode.
  114. *
  115. * - Web page addresses turn into links automatically.
  116. * - E-mail addresses turn into links automatically.
  117. *
  118. * @param string $text The text to process
  119. * @return string
  120. */
  121. public static function fullProcessor($text)
  122. {
  123. $text = static::emailToLink($text);
  124. $text = static::urlToLink($text);
  125. return $text;
  126. }
  127. /**
  128. * Process text in filtered HTML mode.
  129. *
  130. * - Web page addresses turn into links automatically.
  131. * - E-mail addresses turn into links automatically.
  132. * - Allowed HTML tags: `<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>`
  133. * - Lines and paragraphs break automatically.
  134. *
  135. * @param string $text The text to process
  136. * @return string
  137. */
  138. public static function filteredProcessor($text)
  139. {
  140. $text = static::emailToLink($text);
  141. $text = static::urlToLink($text);
  142. $text = strip_tags($text, '<a><em><strong><cite><blockquote><code><ul><ol><li><dl><dt><dd>');
  143. return $text;
  144. }
  145. /**
  146. * Process text in markdown mode.
  147. *
  148. * - [Markdown](http://en.wikipedia.org/wiki/Markdown) text format allowed only.
  149. *
  150. * @param string $text The text to process
  151. * @return string
  152. */
  153. public static function markdownProcessor($text)
  154. {
  155. $MarkdownParser = static::getMarkdownParser();
  156. $text = $MarkdownParser->parse($text);
  157. $text = static::emailToLink($text);
  158. $text = str_replace('<p>h', '<p> h', $text);
  159. $text = static::urlToLink($text);
  160. return $text;
  161. }
  162. /**
  163. * Attempts to close any unclosed HTML tag.
  164. *
  165. * @param string $html HTML content to fix
  166. * @return string
  167. */
  168. public static function closeOpenTags($html)
  169. {
  170. preg_match_all("#<([a-z]+)( .*)?(?!/)>#iU", $html, $result);
  171. $openedTags = $result[1];
  172. preg_match_all("#</([a-z]+)>#iU", $html, $result);
  173. $closedTags = $result[1];
  174. $lenOpened = count($openedTags);
  175. if (count($closedTags) == $lenOpened) {
  176. return $html;
  177. }
  178. $openedTags = array_reverse($openedTags);
  179. for ($i = 0; $i < $lenOpened; $i++) {
  180. if (!in_array($openedTags[$i], $closedTags)) {
  181. $html .= '</' . $openedTags[$i] . '>';
  182. } else {
  183. unset($closedTags[array_search($openedTags[$i], $closedTags)]);
  184. }
  185. }
  186. return $html;
  187. }
  188. /**
  189. * Safely strip HTML tags.
  190. *
  191. * @param string $html HTML content
  192. * @return string
  193. */
  194. public static function stripHtmlTags($html)
  195. {
  196. $html = preg_replace(
  197. [
  198. '@<head[^>]*?>.*?</head>@siu',
  199. '@<style[^>]*?>.*?</style>@siu',
  200. '@<object[^>]*?.*?</object>@siu',
  201. '@<embed[^>]*?.*?</embed>@siu',
  202. '@<applet[^>]*?.*?</applet>@siu',
  203. '@<noframes[^>]*?.*?</noframes>@siu',
  204. '@<noscript[^>]*?.*?</noscript>@siu',
  205. '@<noembed[^>]*?.*?</noembed>@siu',
  206. '@</?((address)|(blockquote)|(center)|(del))@iu',
  207. '@</?((div)|(h[1-9])|(ins)|(isindex)|(p)|(pre))@iu',
  208. '@</?((dir)|(dl)|(dt)|(dd)|(li)|(menu)|(ol)|(ul))@iu',
  209. '@</?((table)|(th)|(td)|(caption))@iu',
  210. '@</?((form)|(button)|(fieldset)|(legend)|(input))@iu',
  211. '@</?((label)|(select)|(optgroup)|(option)|(textarea))@iu',
  212. '@</?((frameset)|(frame)|(iframe))@iu',
  213. ],
  214. [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', "$0", "$0", "$0", "$0", "$0", "$0", "$0", "$0"],
  215. $html
  216. );
  217. return strip_tags($html, '<script>');
  218. }
  219. /**
  220. * Convert any URL to a "<a>" HTML tag.
  221. *
  222. * It will ignores URLs in existing `<a>` tags.
  223. *
  224. * @param string $text The text where to look for links
  225. * @return string
  226. */
  227. public static function urlToLink($text)
  228. {
  229. $pattern = [
  230. '/[^\\\](?<!http:\/\/|https:\/\/|\"|=|\'|\'>|\">)(www\..*?)(\s|\Z|\.\Z|\.\s|\<|\>|,)/iu',
  231. '/[^\\\](?<!\"|=|\'|\'>|\">|site:)(https?:\/\/(www){0,1}.*?)(\s|\Z|\.\Z|\.\s|\<|\>|,)/iu',
  232. '/[\\\\](?<!\"|=|\'|\'>|\">|site:)(https?:\/\/(www){0,1}.*?)(\s|\Z|\.\Z|\.\s|\<|\>|,)/iu',
  233. ];
  234. $replacement = [
  235. '<a href="http://$1">$1</a>$2',
  236. '<a href="$1" target="_blank">$1</a>$3',
  237. '$1$3'
  238. ];
  239. return preg_replace($pattern, $replacement, $text);
  240. }
  241. /**
  242. * Convert any email to a "mailto" link.
  243. *
  244. * Escape character is `\`. For example, "\demo@email.com" won't be converted
  245. * to link.
  246. *
  247. * @param string $text The text where to look for emails addresses
  248. * @return string
  249. */
  250. public static function emailToLink($text)
  251. {
  252. preg_match_all("/([\\\a-z0-9_\-\.]+)@([a-z0-9-]{1,64})\.([a-z]{2,10})/iu", $text, $emails);
  253. foreach ($emails[0] as $email) {
  254. $email = trim($email);
  255. if ($email[0] == '\\') {
  256. $text = str_replace($email, mb_substr($email, 1), $text);
  257. } else {
  258. $text = str_replace($email, static::emailObfuscator($email), $text);
  259. }
  260. }
  261. return $text;
  262. }
  263. /**
  264. * Protects email address so bots can not read it.
  265. *
  266. * Replaces emails address with an encoded JS script, so there is no way bots
  267. * can read an email address from the generated HTML source code.
  268. *
  269. * @param string $email The email to obfuscate
  270. * @return string
  271. */
  272. public static function emailObfuscator($email)
  273. {
  274. $link = str_rot13('<a href="mailto:' . $email . '" rel="nofollow">' . $email . '</a>');
  275. $out = '<script type="text/javascript">' . "\n";
  276. $out .= ' document.write(\'' . $link . '\'.replace(/[a-zA-Z]/g, function(c) {' . "\n";
  277. $out .= ' return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);' . "\n";
  278. $out .= ' }));' . "\n";
  279. $out .= '</script>' . "\n";
  280. $out .= '<noscript>[' . __d('field', 'Turn on JavaScript to see the email address.') . ']</noscript>' . "\n";
  281. return $out;
  282. }
  283. /**
  284. * Strips HTML tags and any shortcode.
  285. *
  286. * @param string $text The text to process
  287. * @return string
  288. */
  289. public static function filterText($text)
  290. {
  291. return static::getInstance()->stripShortcodes(static::stripHtmlTags($text));
  292. }
  293. /**
  294. * Safely trim a text.
  295. *
  296. * This method is HTML aware, it will not "destroy" any HTML tag. You can trim
  297. * the text to a given number of characters, or you can give a string as second
  298. * argument which will be used to cut the given text and return the first part.
  299. *
  300. * ## Examples:
  301. *
  302. * $text = '
  303. * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  304. * Fusce augue nulla, iaculis adipiscing risus sed, pharetra tempor risus.
  305. * <!-- readmore -->
  306. * Ut volutpat nisl enim, quic sit amet quam ut lacus condimentum volutpat in eu magna.
  307. * Phasellus a dolor cursus, aliquam felis sit amet, feugiat orci. Donec vel consec.';
  308. *
  309. * echo $this->trimmer($text, '<!-- readmore -->');
  310. *
  311. * // outputs:
  312. * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  313. * Fusce augue nulla, iaculis adipiscing risus sed, pharetra tempor risus.
  314. *
  315. * echo $this->trimmer('Lorem ipsum dolor sit amet, consectetur adipiscing elit', 10);
  316. * // out: "Lorem ipsu ..."
  317. *
  318. * @param string $text The text to trim
  319. * @param string|int|false $len Either a string indicating where to cut the
  320. * text, or a integer to trim text to that number of characters. If not given
  321. * (false by default) text will be trimmed to 600 characters length.
  322. * @param string $ellipsis Will be used as ending and appended to the trimmed
  323. * string. Defaults to ` ...`
  324. * @return string
  325. */
  326. public static function trimmer($text, $len = false, $ellipsis = ' ...')
  327. {
  328. if (!preg_match('/[0-9]+/i', $len)) {
  329. $parts = explode($len, $text);
  330. return static::closeOpenTags($parts[0]);
  331. }
  332. $len = $len === false || !is_numeric($len) || $len <= 0 ? 600 : $len;
  333. $text = static::filterText($text);
  334. $textLen = mb_strlen($text);
  335. if ($textLen > $len) {
  336. return mb_substr($text, 0, $len) . $ellipsis;
  337. }
  338. return $text;
  339. }
  340. /**
  341. * Gets a markdown parser instance.
  342. *
  343. * @return \Field\Lib\Parsedown
  344. */
  345. public static function getMarkdownParser()
  346. {
  347. if (empty(static::$_MarkdownParser)) {
  348. static::$_MarkdownParser = new Parsedown();
  349. }
  350. return static::$_MarkdownParser;
  351. }
  352. /**
  353. * Debug friendly object properties.
  354. *
  355. * @return array
  356. */
  357. public function __debugInfo()
  358. {
  359. $properties = get_object_vars($this);
  360. $properties['_instance'] = '(object) TextToolbox';
  361. return $properties;
  362. }
  363. }