LooseAutolink.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <?php
  2. /**
  3. * @author Mike Cochrane <mikec@mikenz.geek.nz>
  4. * @author Nick Pope <nick@nickpope.me.uk>
  5. * @author Takashi Nojima
  6. * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
  7. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
  8. */
  9. namespace App\Util\Lexer;
  10. /**
  11. * Twitter LooseAutolink Class.
  12. *
  13. * Parses tweets and generates HTML anchor tags around URLs, usernames,
  14. * username/list pairs and hashtags.
  15. *
  16. * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
  17. * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
  18. * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
  19. *
  20. * @author Mike Cochrane <mikec@mikenz.geek.nz>
  21. * @author Nick Pope <nick@nickpope.me.uk>
  22. * @author Takashi Nojima
  23. * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
  24. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
  25. *
  26. * @since 1.8.0
  27. * @deprecated since version 1.9.0
  28. */
  29. class LooseAutolink extends Autolink
  30. {
  31. /**
  32. * Auto-link hashtags, URLs, usernames and lists.
  33. *
  34. * @param string The tweet to be converted
  35. *
  36. * @return string that auto-link HTML added
  37. *
  38. * @deprecated since version 1.9.0
  39. */
  40. public function autoLink($tweet = null)
  41. {
  42. if (!is_null($tweet)) {
  43. $this->tweet = $tweet;
  44. }
  45. return $this->addLinks();
  46. }
  47. /**
  48. * Auto-link the @username and @username/list references in the provided text. Links to @username references will
  49. * have the usernameClass CSS classes added. Links to @username/list references will have the listClass CSS class
  50. * added.
  51. *
  52. * @return string that auto-link HTML added
  53. */
  54. public function autoLinkUsernamesAndLists($tweet = null)
  55. {
  56. if (!is_null($tweet)) {
  57. $this->tweet = $tweet;
  58. }
  59. return $this->addLinksToUsernamesAndLists();
  60. }
  61. /**
  62. * Auto-link #hashtag references in the provided Tweet text. The #hashtag links will have the hashtagClass CSS class
  63. * added.
  64. *
  65. * @return string that auto-link HTML added
  66. */
  67. public function autoLinkHashtags($tweet = null)
  68. {
  69. if (!is_null($tweet)) {
  70. $this->tweet = $tweet;
  71. }
  72. return $this->addLinksToHashtags();
  73. }
  74. /**
  75. * Auto-link URLs in the Tweet text provided.
  76. * <p/>
  77. * This only auto-links URLs with protocol.
  78. *
  79. * @return string that auto-link HTML added
  80. */
  81. public function autoLinkURLs($tweet = null)
  82. {
  83. if (!is_null($tweet)) {
  84. $this->tweet = $tweet;
  85. }
  86. return $this->addLinksToURLs();
  87. }
  88. /**
  89. * Auto-link $cashtag references in the provided Tweet text. The $cashtag links will have the cashtagClass CSS class
  90. * added.
  91. *
  92. * @return string that auto-link HTML added
  93. */
  94. public function autoLinkCashtags($tweet = null)
  95. {
  96. if (!is_null($tweet)) {
  97. $this->tweet = $tweet;
  98. }
  99. return $this->addLinksToCashtags();
  100. }
  101. /**
  102. * Adds links to all elements in the tweet.
  103. *
  104. * @return string The modified tweet.
  105. *
  106. * @deprecated since version 1.9.0
  107. */
  108. public function addLinks()
  109. {
  110. $original = $this->tweet;
  111. $this->tweet = $this->addLinksToURLs();
  112. $this->tweet = $this->addLinksToHashtags();
  113. $this->tweet = $this->addLinksToCashtags();
  114. $this->tweet = $this->addLinksToUsernamesAndLists();
  115. $modified = $this->tweet;
  116. $this->tweet = $original;
  117. return $modified;
  118. }
  119. /**
  120. * Adds links to hashtag elements in the tweet.
  121. *
  122. * @return string The modified tweet.
  123. */
  124. public function addLinksToHashtags()
  125. {
  126. return preg_replace_callback(
  127. self::$patterns['valid_hashtag'],
  128. [$this, '_addLinksToHashtags'],
  129. $this->tweet
  130. );
  131. }
  132. /**
  133. * Adds links to cashtag elements in the tweet.
  134. *
  135. * @return string The modified tweet.
  136. */
  137. public function addLinksToCashtags()
  138. {
  139. return preg_replace_callback(
  140. self::$patterns['valid_cashtag'],
  141. [$this, '_addLinksToCashtags'],
  142. $this->tweet
  143. );
  144. }
  145. /**
  146. * Adds links to URL elements in the tweet.
  147. *
  148. * @return string The modified tweet
  149. */
  150. public function addLinksToURLs()
  151. {
  152. return preg_replace_callback(self::$patterns['valid_url'], [$this, '_addLinksToURLs'], $this->tweet);
  153. }
  154. /**
  155. * Adds links to username/list elements in the tweet.
  156. *
  157. * @return string The modified tweet.
  158. */
  159. public function addLinksToUsernamesAndLists()
  160. {
  161. return preg_replace_callback(
  162. self::$patterns['valid_mentions_or_lists'],
  163. [$this, '_addLinksToUsernamesAndLists'],
  164. $this->tweet
  165. );
  166. }
  167. /**
  168. * Wraps a tweet element in an HTML anchor tag using the provided URL.
  169. *
  170. * This is a helper function to perform the generation of the link.
  171. *
  172. * @param string $url The URL to use as the href.
  173. * @param string $class The CSS class(es) to apply (space separated).
  174. * @param string $element The tweet element to wrap.
  175. *
  176. * @return string The tweet element with a link applied.
  177. *
  178. * @deprecated since version 1.1.0
  179. */
  180. protected function wrap($url, $class, $element)
  181. {
  182. $link = '<a';
  183. if ($class) {
  184. $link .= ' class="'.$class.'"';
  185. }
  186. $link .= ' href="'.$url.'"';
  187. $rel = [];
  188. if ($this->external) {
  189. $rel[] = 'external';
  190. }
  191. if ($this->nofollow) {
  192. $rel[] = 'nofollow';
  193. }
  194. if (!empty($rel)) {
  195. $link .= ' rel="'.implode(' ', $rel).'"';
  196. }
  197. if ($this->target) {
  198. $link .= ' target="'.$this->target.'"';
  199. }
  200. $link .= '>'.$element.'</a>';
  201. return $link;
  202. }
  203. /**
  204. * Wraps a tweet element in an HTML anchor tag using the provided URL.
  205. *
  206. * This is a helper function to perform the generation of the hashtag link.
  207. *
  208. * @param string $url The URL to use as the href.
  209. * @param string $class The CSS class(es) to apply (space separated).
  210. * @param string $element The tweet element to wrap.
  211. *
  212. * @return string The tweet element with a link applied.
  213. */
  214. protected function wrapHash($url, $class, $element)
  215. {
  216. $title = preg_replace('/#/u', '#', $element);
  217. $link = '<a';
  218. $link .= ' href="'.$url.'"';
  219. $link .= ' title="'.$title.'"';
  220. if ($class) {
  221. $link .= ' class="'.$class.'"';
  222. }
  223. $rel = [];
  224. if ($this->external) {
  225. $rel[] = 'external';
  226. }
  227. if ($this->nofollow) {
  228. $rel[] = 'nofollow';
  229. }
  230. if (!empty($rel)) {
  231. $link .= ' rel="'.implode(' ', $rel).'"';
  232. }
  233. if ($this->target) {
  234. $link .= ' target="'.$this->target.'"';
  235. }
  236. $link .= '>'.$element.'</a>';
  237. return $link;
  238. }
  239. /**
  240. * Callback used by the method that adds links to hashtags.
  241. *
  242. * @see addLinksToHashtags()
  243. *
  244. * @param array $matches The regular expression matches.
  245. *
  246. * @return string The link-wrapped hashtag.
  247. */
  248. protected function _addLinksToHashtags($matches)
  249. {
  250. list($all, $before, $hash, $tag, $after) = array_pad($matches, 5, '');
  251. if (preg_match(self::$patterns['end_hashtag_match'], $after)
  252. || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after)) || preg_match('!\A</!', $after)) {
  253. return $all;
  254. }
  255. $replacement = $before;
  256. $element = $hash.$tag;
  257. $url = $this->url_base_hash.$tag;
  258. $class_hash = $this->class_hash;
  259. if (preg_match(self::$patterns['rtl_chars'], $element)) {
  260. $class_hash .= ' rtl';
  261. }
  262. $replacement .= $this->wrapHash($url, $class_hash, $element);
  263. return $replacement;
  264. }
  265. /**
  266. * Callback used by the method that adds links to cashtags.
  267. *
  268. * @see addLinksToCashtags()
  269. *
  270. * @param array $matches The regular expression matches.
  271. *
  272. * @return string The link-wrapped cashtag.
  273. */
  274. protected function _addLinksToCashtags($matches)
  275. {
  276. list($all, $before, $cash, $tag, $after) = array_pad($matches, 5, '');
  277. if (preg_match(self::$patterns['end_cashtag_match'], $after)
  278. || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after)) || preg_match('!\A</!', $after)) {
  279. return $all;
  280. }
  281. $replacement = $before;
  282. $element = $cash.$tag;
  283. $url = $this->url_base_cash.$tag;
  284. $replacement .= $this->wrapHash($url, $this->class_cash, $element);
  285. return $replacement;
  286. }
  287. /**
  288. * Callback used by the method that adds links to URLs.
  289. *
  290. * @see addLinksToURLs()
  291. *
  292. * @param array $matches The regular expression matches.
  293. *
  294. * @return string The link-wrapped URL.
  295. */
  296. protected function _addLinksToURLs($matches)
  297. {
  298. list($all, $before, $url, $protocol, $domain, $path, $query) = array_pad($matches, 7, '');
  299. $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8', false);
  300. if (!$protocol) {
  301. return $all;
  302. }
  303. return $before.$this->wrap($url, $this->class_url, $url);
  304. }
  305. /**
  306. * Callback used by the method that adds links to username/list pairs.
  307. *
  308. * @see addLinksToUsernamesAndLists()
  309. *
  310. * @param array $matches The regular expression matches.
  311. *
  312. * @return string The link-wrapped username/list pair.
  313. */
  314. protected function _addLinksToUsernamesAndLists($matches)
  315. {
  316. list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, '');
  317. // If $after is not empty, there is an invalid character.
  318. if (!empty($slash_listname)) {
  319. // Replace the list and username
  320. $element = $username.$slash_listname;
  321. $class = $this->class_list;
  322. $url = $this->url_base_list.$element;
  323. } else {
  324. if (preg_match(self::$patterns['end_mention_match'], $after)) {
  325. return $all;
  326. }
  327. // Replace the username
  328. $element = $username;
  329. $class = $this->class_user;
  330. $url = $this->url_base_user.$element;
  331. }
  332. // XXX: Due to use of preg_replace_callback() for multiple replacements in a
  333. // single tweet and also as only the match is replaced and we have to
  334. // use a look-ahead for $after because there is no equivalent for the
  335. // $' (dollar apostrophe) global from Ruby, we MUST NOT append $after.
  336. return $before.$at.$this->wrap($url, $class, $element);
  337. }
  338. }