OSDN Git Service

https://github.com/rahuldottech/Dabr
[embrj/master.git] / i / common / Autolink.php
1 <?php
2 /**
3  * @author     Mike Cochrane <mikec@mikenz.geek.nz>
4  * @author     Nick Pope <nick@nickpope.me.uk>
5  * @copyright  Copyright © 2010, Mike Cochrane, Nick Pope
6  * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License v2.0
7  * @package    Twitter
8  */
9
10 require_once 'Regex.php';
11 require_once 'Extractor.php';
12
13 /**
14  * Twitter Autolink Class
15  *
16  * Parses tweets and generates HTML anchor tags around URLs, usernames,
17  * username/list pairs and hashtags.
18  *
19  * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
20  * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
21  * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
22  *
23  * @author     Mike Cochrane <mikec@mikenz.geek.nz>
24  * @author     Nick Pope <nick@nickpope.me.uk>
25  * @copyright  Copyright © 2010, Mike Cochrane, Nick Pope
26  * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License v2.0
27  * @package    Twitter
28  */
29 class Twitter_Autolink extends Twitter_Regex {
30
31   /**
32    * CSS class for auto-linked URLs.
33    *
34    * @var  string
35    */
36   protected $class_url = 'url';
37
38   /**
39    * CSS class for auto-linked username URLs.
40    *
41    * @var  string
42    */
43   protected $class_user = 'username';
44
45   /**
46    * CSS class for auto-linked list URLs.
47    *
48    * @var  string
49    */
50   protected $class_list = 'list';
51
52   /**
53    * CSS class for auto-linked hashtag URLs.
54    *
55    * @var  string
56    */
57   protected $class_hash = 'hashtag';
58
59   /**
60    * CSS class for auto-linked cashtag URLs.
61    *
62    * @var  string
63    */
64   protected $class_cash = 'cashtag';
65
66   /**
67    * URL base for username links (the username without the @ will be appended).
68    *
69    * @var  string
70    */
71   protected $url_base_user = BASE_URL;
72
73   /**
74    * URL base for list links (the username/list without the @ will be appended).
75    *
76    * @var  string
77    */
78   protected $url_base_list = BASE_URL.'lists/';
79
80   /**
81    * URL base for hashtag links (the hashtag without the # will be appended).
82    *
83    * @var  string
84    */
85   protected $url_base_hash = BASE_URL.'hash/';
86
87   /**
88    * URL base for cashtag links (the hashtag without the $ will be appended).
89    *
90    * @var  string
91    */
92   protected $url_base_cash = BASE_URL.'search?query=%24';
93
94   /**
95    * Whether to include the value 'nofollow' in the 'rel' attribute.
96    *
97    * @var  bool
98    */
99   protected $nofollow = true;
100
101   /**
102    * Whether to include the value 'external' in the 'rel' attribute.
103    *
104    * Often this is used to be matched on in JavaScript for dynamically adding
105    * the 'target' attribute which is deprecated in HTML 4.01.  In HTML 5 it has
106    * been undeprecated and thus the 'target' attribute can be used.  If this is
107    * set to false then the 'target' attribute will be output.
108    *
109    * @var  bool
110    */
111   protected $external = true;
112
113   /**
114    * The scope to open the link in.
115    *
116    * Support for the 'target' attribute was deprecated in HTML 4.01 but has
117    * since been reinstated in HTML 5.  To output the 'target' attribute you
118    * must disable the adding of the string 'external' to the 'rel' attribute.
119    *
120    * @var  string
121    */
122   protected $target = '_blank';
123
124   /**
125    * attribute for invisible span tag
126    *
127    * @var string
128    */
129   protected $invisibleTagAttrs = "style='position:absolute;left:-9999px;'";
130
131   /**
132    *
133    * @var Twitter_Extractor
134    */
135   protected $extractor = null;
136
137   /**
138    * Provides fluent method chaining.
139    *
140    * @param  string  $tweet        The tweet to be converted.
141    * @param  bool    $full_encode  Whether to encode all special characters.
142    *
143    * @see  __construct()
144    *
145    * @return  Twitter_Autolink
146    */
147   public static function create($tweet = null, $full_encode = false) {
148     return new self($tweet, $full_encode);
149   }
150
151   /**
152    * Reads in a tweet to be parsed and converted to contain links.
153    *
154    * As the intent is to produce links and output the modified tweet to the
155    * user, we take this opportunity to ensure that we escape user input.
156    *
157    * @see  htmlspecialchars()
158    *
159    * @param  string  $tweet        The tweet to be converted.
160    * @param  bool    $escape       Whether to escape the tweet (default: true).
161    * @param  bool    $full_encode  Whether to encode all special characters.
162    */
163   public function __construct($tweet = null, $escape = true, $full_encode = false) {
164     if ($escape && !empty($tweet)) {
165       if ($full_encode) {
166         parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
167       } else {
168         parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
169       }
170     } else {
171       parent::__construct($tweet);
172     }
173     $this->extractor = Twitter_Extractor::create();
174   }
175
176   /**
177    * CSS class for auto-linked URLs.
178    *
179    * @return  string  CSS class for URL links.
180    */
181   public function getURLClass() {
182     return $this->class_url;
183   }
184
185   /**
186    * CSS class for auto-linked URLs.
187    *
188    * @param  string  $v  CSS class for URL links.
189    *
190    * @return  Twitter_Autolink  Fluid method chaining.
191    */
192   public function setURLClass($v) {
193     $this->class_url = trim($v);
194     return $this;
195   }
196
197   /**
198    * CSS class for auto-linked username URLs.
199    *
200    * @return  string  CSS class for username links.
201    */
202   public function getUsernameClass() {
203     return $this->class_user;
204   }
205
206   /**
207    * CSS class for auto-linked username URLs.
208    *
209    * @param  string  $v  CSS class for username links.
210    *
211    * @return  Twitter_Autolink  Fluid method chaining.
212    */
213   public function setUsernameClass($v) {
214     $this->class_user = trim($v);
215     return $this;
216   }
217
218   /**
219    * CSS class for auto-linked username/list URLs.
220    *
221    * @return  string  CSS class for username/list links.
222    */
223   public function getListClass() {
224     return $this->class_list;
225   }
226
227   /**
228    * CSS class for auto-linked username/list URLs.
229    *
230    * @param  string  $v  CSS class for username/list links.
231    *
232    * @return  Twitter_Autolink  Fluid method chaining.
233    */
234   public function setListClass($v) {
235     $this->class_list = trim($v);
236     return $this;
237   }
238
239   /**
240    * CSS class for auto-linked hashtag URLs.
241    *
242    * @return  string  CSS class for hashtag links.
243    */
244   public function getHashtagClass() {
245     return $this->class_hash;
246   }
247
248   /**
249    * CSS class for auto-linked hashtag URLs.
250    *
251    * @param  string  $v  CSS class for hashtag links.
252    *
253    * @return  Twitter_Autolink  Fluid method chaining.
254    */
255   public function setHashtagClass($v) {
256     $this->class_hash = trim($v);
257     return $this;
258   }
259
260   /**
261    * CSS class for auto-linked cashtag URLs.
262    *
263    * @return  string  CSS class for cashtag links.
264    */
265   public function getCashtagClass() {
266     return $this->class_cash;
267   }
268
269   /**
270    * CSS class for auto-linked cashtag URLs.
271    *
272    * @param  string  $v  CSS class for cashtag links.
273    *
274    * @return  Twitter_Autolink  Fluid method chaining.
275    */
276   public function setCashtagClass($v) {
277     $this->class_cash = trim($v);
278     return $this;
279   }
280
281   /**
282    * Whether to include the value 'nofollow' in the 'rel' attribute.
283    *
284    * @return  bool  Whether to add 'nofollow' to the 'rel' attribute.
285    */
286   public function getNoFollow() {
287     return $this->nofollow;
288   }
289
290   /**
291    * Whether to include the value 'nofollow' in the 'rel' attribute.
292    *
293    * @param  bool  $v  The value to add to the 'target' attribute.
294    *
295    * @return  Twitter_Autolink  Fluid method chaining.
296    */
297   public function setNoFollow($v) {
298     $this->nofollow = $v;
299     return $this;
300   }
301
302   /**
303    * Whether to include the value 'external' in the 'rel' attribute.
304    *
305    * Often this is used to be matched on in JavaScript for dynamically adding
306    * the 'target' attribute which is deprecated in HTML 4.01.  In HTML 5 it has
307    * been undeprecated and thus the 'target' attribute can be used.  If this is
308    * set to false then the 'target' attribute will be output.
309    *
310    * @return  bool  Whether to add 'external' to the 'rel' attribute.
311    */
312   public function getExternal() {
313     return $this->external;
314   }
315
316   /**
317    * Whether to include the value 'external' in the 'rel' attribute.
318    *
319    * Often this is used to be matched on in JavaScript for dynamically adding
320    * the 'target' attribute which is deprecated in HTML 4.01.  In HTML 5 it has
321    * been undeprecated and thus the 'target' attribute can be used.  If this is
322    * set to false then the 'target' attribute will be output.
323    *
324    * @param  bool  $v  The value to add to the 'target' attribute.
325    *
326    * @return  Twitter_Autolink  Fluid method chaining.
327    */
328   public function setExternal($v) {
329     $this->external = $v;
330     return $this;
331   }
332
333   /**
334    * The scope to open the link in.
335    *
336    * Support for the 'target' attribute was deprecated in HTML 4.01 but has
337    * since been reinstated in HTML 5.  To output the 'target' attribute you
338    * must disable the adding of the string 'external' to the 'rel' attribute.
339    *
340    * @return  string  The value to add to the 'target' attribute.
341    */
342   public function getTarget() {
343     return $this->target;
344   }
345
346   /**
347    * The scope to open the link in.
348    *
349    * Support for the 'target' attribute was deprecated in HTML 4.01 but has
350    * since been reinstated in HTML 5.  To output the 'target' attribute you
351    * must disable the adding of the string 'external' to the 'rel' attribute.
352    *
353    * @param  string  $v  The value to add to the 'target' attribute.
354    *
355    * @return  Twitter_Autolink  Fluid method chaining.
356    */
357   public function setTarget($v) {
358     $this->target = trim($v);
359     return $this;
360   }
361
362   /**
363    * Autolink with entities
364    *
365    * @param string $tweet
366    * @param array $entities
367    * @return string
368    * @since 1.1.0
369    */
370   public function autoLinkEntities($tweet = null, $entities) {
371     if (is_null($tweet)) {
372       $tweet = $this->tweet;
373     }
374
375     $text = '';
376     $beginIndex = 0;
377     foreach ($entities as $entity) {
378       if (isset($entity['screen_name'])) {
379         $text .= mb_substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex + 1);
380       } else {
381         $text .= mb_substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
382       }
383
384       if (isset($entity['url'])) {
385         $text .= $this->linkToUrl($entity);
386       } elseif (isset($entity['hashtag'])) {
387         $text .= $this->linkToHashtag($entity, $tweet);
388       } elseif (isset($entity['screen_name'])) {
389         $text .= $this->linkToMentionAndList($entity);
390       } elseif (isset($entity['cashtag'])) {
391         $text .= $this->linkToCashtag($entity, $tweet);
392       }
393       $beginIndex = $entity['indices'][1];
394     }
395     $text .= mb_substr($tweet, $beginIndex, mb_strlen($tweet));
396     return $text;
397   }
398
399   /**
400    * Auto-link hashtags, URLs, usernames and lists, with JSON entities.
401    *
402    * @param  string The tweet to be converted
403    * @param  mixed  The entities info
404    * @return string that auto-link HTML added
405    * @since 1.1.0
406    */
407   public function autoLinkWithJson($tweet = null, $json) {
408     // concatenate entities
409     $entities = array();
410     if (is_object($json)) {
411       $json = $this->object2array($json);
412     }
413     if (is_array($json)) {
414       foreach ($json as $key => $vals) {
415         $entities = array_merge($entities, $json[$key]);
416       }
417     }
418
419     // map JSON entity to twitter-text entity
420     foreach ($entities as $idx => $entity) {
421       if (!empty($entity['text'])) {
422         $entities[$idx]['hashtag'] = $entity['text'];
423       }
424     }
425
426     $entities = $this->extractor->removeOverlappingEntities($entities);
427     return $this->autoLinkEntities($tweet, $entities);
428   }
429
430   /**
431    * convert Object to Array
432    *
433    * @param mixed $obj
434    * @return array
435    */
436   protected function object2array($obj) {
437     $array = (array)$obj;
438     foreach ($array as $key => $var) {
439       if (is_object($var) || is_array($var)) {
440         $array[$key] = $this->object2array($var);
441       }
442     }
443     return $array;
444   }
445
446   /**
447    * Auto-link hashtags, URLs, usernames and lists.
448    *
449    * @param  string The tweet to be converted
450    * @return string that auto-link HTML added
451    * @since 1.1.0
452    */
453   public function autoLink($tweet = null) {
454     if (is_null($tweet)) {
455       $tweet = $this->tweet;
456     }
457     $entities = $this->extractor->extractURLWithoutProtocol(false)->extractEntitiesWithIndices($tweet);
458     return $this->autoLinkEntities($tweet, $entities);
459   }
460
461   /**
462    * Auto-link the @username and @username/list references in the provided text. Links to @username references will
463    * have the usernameClass CSS classes added. Links to @username/list references will have the listClass CSS class
464    * added.
465    *
466    * @return string that auto-link HTML added
467    * @since 1.1.0
468    */
469   public function autoLinkUsernamesAndLists($tweet = null) {
470     if (is_null($tweet)) {
471       $tweet = $this->tweet;
472     }
473     $entities = $this->extractor->extractMentionsOrListsWithIndices($tweet);
474     return $this->autoLinkEntities($tweet, $entities);
475   }
476
477   /**
478    * Auto-link #hashtag references in the provided Tweet text. The #hashtag links will have the hashtagClass CSS class
479    * added.
480    *
481    * @return string that auto-link HTML added
482    * @since 1.1.0
483    */
484   public function autoLinkHashtags($tweet = null) {
485     if (is_null($tweet)) {
486       $tweet = $this->tweet;
487     }
488     $entities = $this->extractor->extractHashtagsWithIndices($tweet);
489     return $this->autoLinkEntities($tweet, $entities);
490   }
491
492   /**
493    * Auto-link URLs in the Tweet text provided.
494    * <p/>
495    * This only auto-links URLs with protocol.
496    *
497    * @return string that auto-link HTML added
498    * @since 1.1.0
499    */
500   public function autoLinkURLs($tweet = null) {
501     if (is_null($tweet)) {
502       $tweet = $this->tweet;
503     }
504     $entities = $this->extractor->extractURLWithoutProtocol(false)->extractURLsWithIndices($tweet);
505     return $this->autoLinkEntities($tweet, $entities);
506   }
507
508   /**
509    * Auto-link $cashtag references in the provided Tweet text. The $cashtag links will have the cashtagClass CSS class
510    * added.
511    *
512    * @return string that auto-link HTML added
513    * @since 1.1.0
514    */
515   public function autoLinkCashtags($tweet = null) {
516     if (is_null($tweet)) {
517       $tweet = $this->tweet;
518     }
519     $entities = $this->extractor->extractCashtagsWithIndices($tweet);
520     return $this->autoLinkEntities($tweet, $entities);
521   }
522
523   public function linkToUrl($entity) {
524     if (!empty($this->class_url)) $attributes['class'] = $this->class_url;
525     $attributes['href'] = $entity['url'];
526     $linkText = $this->escapeHTML($entity['url']);
527
528     if (!empty($entity['display_url']) && !empty($entity['expanded_url'])) {
529       // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
530       // should contain the full original URL (expanded_url), not the display URL.
531       //
532       // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
533       // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
534       // Elements with font-size:0 get copied even though they are not visible.
535       // Note that display:none doesn't work here. Elements with display:none don't get copied.
536       //
537       // Additionally, we want to *display* ellipses, but we don't want them copied.  To make this happen we
538       // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
539       // everything with the tco-ellipsis class.
540       //
541       // As an example: The user tweets "hi http://longdomainname.com/foo"
542       // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
543       // This will get rendered as:
544       // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
545       //   …
546       //   <!-- There's a chance the onCopy event handler might not fire. In case that happens,
547       //        we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
548       //        The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
549       //        fire, it doesn't get copied.  Otherwise the copied text would have two spaces in a row,
550       //        e.g. "hi  http://longdomainname.com/foo".
551       //   <span style='font-size:0'>&nbsp;</span>
552       // </span>
553       // <span style='font-size:0'>  <!-- This stuff should get copied but not displayed -->
554       //   http://longdomai
555       // </span>
556       // <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
557       //   nname.com/foo
558       // </span>
559       // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
560       //   <span style='font-size:0'>&nbsp;</span>
561       //   …
562       // </span>
563       //
564       // Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
565       // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
566       // For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
567       $displayURL = $entity['display_url'];
568       $expandedURL = $entity['expanded_url'];
569       $displayURLSansEllipses = preg_replace('/…/u', '', $displayURL);
570       $diplayURLIndexInExpandedURL = mb_strpos($expandedURL, $displayURLSansEllipses);
571
572       if ($diplayURLIndexInExpandedURL !== false) {
573         $beforeDisplayURL = mb_substr($expandedURL, 0, $diplayURLIndexInExpandedURL);
574         $afterDisplayURL = mb_substr($expandedURL, $diplayURLIndexInExpandedURL + mb_strlen($displayURLSansEllipses));
575         $precedingEllipsis = (preg_match('/\A…/u', $displayURL)) ? '…' : '';
576         $followingEllipsis = (preg_match('/…\z/u', $displayURL)) ? '…' : '';
577
578         $invisibleSpan = "<span {$this->invisibleTagAttrs}>";
579
580         $linkText = "<span class='tco-ellipsis'>{$precedingEllipsis}{$invisibleSpan}&nbsp;</span></span>";
581         $linkText .= "{$invisibleSpan}{$this->escapeHTML($beforeDisplayURL)}</span>";
582         $linkText .= "<span class='js-display-url'>{$this->escapeHTML($displayURLSansEllipses)}</span>";
583         $linkText .= "{$invisibleSpan}{$this->escapeHTML($afterDisplayURL)}</span>";
584         $linkText .= "<span class='tco-ellipsis'>{$invisibleSpan}&nbsp;</span>{$followingEllipsis}</span>";
585       } else {
586         $linkText = $entity['display_url'];
587       }
588       $attributes['title'] = $entity['expanded_url'];
589     } else if (!empty($entity['display_url'])) {
590       $linkText = $entity['display_url'];
591     }
592
593     return $this->linkToText($entity, $linkText, $attributes);
594   }
595
596   /**
597    *
598    * @param array  $entity
599    * @param string $tweet
600    * @return string
601    * @since 1.1.0
602    */
603   public function linkToHashtag($entity, $tweet = null) {
604     if (is_null($tweet)) {
605       $tweet = $this->tweet;
606     }
607
608     $attributes = array();
609     $class = array();
610     $hash = mb_substr($tweet, $entity['indices'][0], 1);
611     $linkText = $hash . $entity['hashtag'];
612
613     $attributes['href'] = $this->url_base_hash . $entity['hashtag'];
614     $attributes['title'] = '#' . $entity['hashtag'];
615     if (!empty($this->class_hash)) {
616       $class[] = $this->class_hash;
617     }
618     if (preg_match(self::$patterns['rtl_chars'], $linkText)) {
619       $class[] = 'rtl';
620     }
621     if (!empty($class)) {
622       $attributes['class'] = join(' ', $class);
623     }
624
625     return $this->linkToText($entity, $linkText, $attributes);
626   }
627
628   /**
629    *
630    * @param array  $entity
631    * @return string
632    * @since 1.1.0
633    */
634   public function linkToMentionAndList($entity) {
635     $attributes = array();
636
637     if (!empty($entity['list_slug'])) {
638       # Replace the list and username
639       $linkText = $entity['screen_name'] . $entity['list_slug'];
640       $class = $this->class_list;
641       $url = $this->url_base_list . $linkText;
642     } else {
643       # Replace the username
644       $linkText = $entity['screen_name'];
645       $class = $this->class_user;
646       $url = $this->url_base_user . $linkText;
647     }
648     if (!empty($class)) {
649       $attributes['class'] = $class;
650     }
651     $attributes['href'] = $url;
652
653     return $this->linkToText($entity, $linkText, $attributes);
654   }
655
656   /**
657    *
658    * @param array  $entity
659    * @param string $tweet
660    * @return string
661    * @since 1.1.0
662    */
663   public function linkToCashtag($entity, $tweet = null) {
664     if (is_null($tweet)) {
665       $tweet = $this->tweet;
666     }
667     $attributes = array();
668     $doller = mb_substr($tweet, $entity['indices'][0], 1);
669     $linkText = $doller . $entity['cashtag'];
670     $attributes['href'] = $this->url_base_cash . $entity['cashtag'];
671     $attributes['title'] = $linkText;
672     if (!empty($this->class_cash)) {
673       $attributes['class'] = $this->class_cash;
674     }
675
676     return $this->linkToText($entity, $linkText, $attributes);
677   }
678
679   /**
680    * Adds links to all elements in the tweet.
681    *
682    * @param boolean $loose if false, using autoLinkEntities
683    * @return  string  The modified tweet.
684    * @deprecated since version 1.1.0
685    */
686   public function addLinks($loose = false) {
687     if (!$loose) {
688       return $this->autoLink();
689     }
690
691     // loose mode
692     $original = $this->tweet;
693     $this->tweet = $this->addLinksToURLs($loose);
694     $this->tweet = $this->addLinksToHashtags($loose);
695     $this->tweet = $this->addLinksToCashtags($loose);
696     $this->tweet = $this->addLinksToUsernamesAndLists($loose);
697     $modified = $this->tweet;
698     $this->tweet = $original;
699     return $modified;
700   }
701
702   /**
703    * Adds links to hashtag elements in the tweet.
704    *
705    * @param boolean $loose if false, using autoLinkEntities
706    * @return  string  The modified tweet.
707    * @deprecated since version 1.1.0
708    */
709   public function addLinksToHashtags($loose = false) {
710     if (!$loose) {
711       return $this->autoLinkHashtags();
712     }
713     return preg_replace_callback(
714       self::$patterns['valid_hashtag'],
715       array($this, '_addLinksToHashtags'),
716       $this->tweet);
717   }
718
719   /**
720    * Adds links to cashtag elements in the tweet.
721    *
722    * @param boolean $loose if false, using autoLinkEntities
723    * @return  string  The modified tweet.
724    * @deprecated since version 1.1.0
725    */
726   public function addLinksToCashtags($loose = false) {
727     if (!$loose) {
728       return $this->autoLinkCashtags();
729     }
730     return preg_replace_callback(
731       self::$patterns['valid_cashtag'],
732       array($this, '_addLinksToCashtags'),
733       $this->tweet);
734   }
735
736   /**
737    * Adds links to URL elements in the tweet.
738    *
739    * @param boolean $loose if false, using autoLinkEntities
740    * @return  string  The modified tweet
741    * @deprecated since version 1.1.0.
742    */
743   public function addLinksToURLs($loose = false) {
744     if (!$loose) {
745       return $this->autoLinkURLs();
746     }
747     return preg_replace_callback(
748       self::$patterns['valid_url'],
749       array($this, '_addLinksToURLs'),
750       $this->tweet);
751   }
752
753   /**
754    * Adds links to username/list elements in the tweet.
755    *
756    * @param boolean $loose if false, using autoLinkEntities
757    * @return  string  The modified tweet.
758    * @deprecated since version 1.1.0
759    */
760   public function addLinksToUsernamesAndLists($loose = false) {
761     if (!$loose) {
762       return $this->autoLinkUsernamesAndLists();
763     }
764     return preg_replace_callback(
765       self::$patterns['valid_mentions_or_lists'],
766       array($this, '_addLinksToUsernamesAndLists'),
767       $this->tweet);
768   }
769
770   /**
771    *
772    * @param array $entity
773    * @param string $text
774    * @param array $attributes
775    * @return string
776    * @since 1.1.0
777    */
778   public function linkToText(array $entity, $text, $attributes = array()) {
779     $rel = array();
780     if ($this->external) $rel[] = 'external';
781     if ($this->nofollow) $rel[] = 'nofollow';
782     if (!empty($rel)) {
783       $attributes['rel'] = join(' ', $rel);
784     }
785     if ($this->target) $attributes['target'] = $this->target;
786
787     $link = '<a';
788     foreach ($attributes as $key => $val) {
789       $link .= ' ' . $key . '="' . $this->escapeHTML($val) . '"';
790     }
791     $link .= '>' . $text . '</a>';
792     return $link;
793   }
794
795   /**
796    * html escape
797    *
798    * @param string $text
799    * @return string
800    */
801   protected function escapeHTML($text) {
802     return htmlspecialchars($text, ENT_QUOTES, 'UTF-8', false);
803   }
804
805   /**
806    * Wraps a tweet element in an HTML anchor tag using the provided URL.
807    *
808    * This is a helper function to perform the generation of the link.
809    *
810    * @param  string  $url      The URL to use as the href.
811    * @param  string  $class    The CSS class(es) to apply (space separated).
812    * @param  string  $element  The tweet element to wrap.
813    *
814    * @return  string  The tweet element with a link applied.
815    * @deprecated since version 1.1.0
816    */
817   protected function wrap($url, $class, $element) {
818     $link  = '<a';
819     if ($class) $link .= ' class="'.$class.'"';
820     $link .= ' href="'.$url.'"';
821     $rel = array();
822     if ($this->external) $rel[] = 'external';
823     if ($this->nofollow) $rel[] = 'nofollow';
824     if (!empty($rel)) $link .= ' rel="'.implode(' ', $rel).'"';
825     if ($this->target) $link .= ' target="'.$this->target.'"';
826     $link .= '>'.$element.'</a>';
827     return $link;
828   }
829
830   /**
831    * Wraps a tweet element in an HTML anchor tag using the provided URL.
832    *
833    * This is a helper function to perform the generation of the hashtag link.
834    *
835    * @param  string  $url      The URL to use as the href.
836    * @param  string  $class    The CSS class(es) to apply (space separated).
837    * @param  string  $element  The tweet element to wrap.
838    *
839    * @return  string  The tweet element with a link applied.
840    * @deprecated since version 1.1.0
841    */
842   protected function wrapHash($url, $class, $element) {
843     $title = preg_replace('/#/u', '#', $element);
844     $link  = '<a';
845     $link .= ' href="'.$url.'"';
846     $link .= ' title="'.$title.'"';
847     if ($class) $link .= ' class="'.$class.'"';
848     $rel = array();
849     if ($this->external) $rel[] = 'external';
850     if ($this->nofollow) $rel[] = 'nofollow';
851     if (!empty($rel)) $link .= ' rel="'.implode(' ', $rel).'"';
852     if ($this->target) $link .= ' target="'.$this->target.'"';
853     $link .= '>'.$element.'</a>';
854     return $link;
855   }
856
857   /**
858    * Callback used by the method that adds links to hashtags.
859    *
860    * @see  addLinksToHashtags()
861    * @param  array  $matches  The regular expression matches.
862    * @return  string  The link-wrapped hashtag.
863    * @deprecated since version 1.1.0
864    */
865   protected function _addLinksToHashtags($matches) {
866     list($all, $before, $hash, $tag, $after) = array_pad($matches, 5, '');
867     if (preg_match(self::$patterns['end_hashtag_match'], $after)
868         || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after))
869         || preg_match('!\A</!', $after)) {
870       return $all;
871     }
872     $replacement = $before;
873     $element = $hash . $tag;
874     $url = $this->url_base_hash . $tag;
875     $class_hash = $this->class_hash;
876     if (preg_match(self::$patterns['rtl_chars'], $element)) {
877       $class_hash .= ' rtl';
878     }
879     $replacement .= $this->wrapHash($url, $class_hash, $element);
880     return $replacement;
881   }
882
883   /**
884    * Callback used by the method that adds links to cashtags.
885    *
886    * @see  addLinksToCashtags()
887    * @param  array  $matches  The regular expression matches.
888    * @return  string  The link-wrapped cashtag.
889    * @deprecated since version 1.1.0
890    */
891   protected function _addLinksToCashtags($matches) {
892     list($all, $before, $cash, $tag, $after) = array_pad($matches, 5, '');
893     if (preg_match(self::$patterns['end_cashtag_match'], $after)
894         || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after))
895         || preg_match('!\A</!', $after)) {
896       return $all;
897     }
898     $replacement = $before;
899     $element = $cash . $tag;
900     $url = $this->url_base_cash . $tag;
901     $replacement .= $this->wrapHash($url, $this->class_cash, $element);
902     return $replacement;
903   }
904
905   /**
906    * Callback used by the method that adds links to URLs.
907    *
908    * @see  addLinksToURLs()
909    * @param  array  $matches  The regular expression matches.
910    * @return  string  The link-wrapped URL.
911    * @deprecated since version 1.1.0
912    */
913   protected function _addLinksToURLs($matches) {
914     list($all, $before, $url, $protocol, $domain, $path, $query) = array_pad($matches, 7, '');
915     $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8', false);
916     if (!$protocol) return $all;
917     return $before . $this->wrap($url, $this->class_url, $url);
918   }
919
920   /**
921    * Callback used by the method that adds links to username/list pairs.
922    *
923    * @see  addLinksToUsernamesAndLists()
924    * @param  array  $matches  The regular expression matches.
925    * @return  string  The link-wrapped username/list pair.
926    * @deprecated since version 1.1.0
927    */
928   protected function _addLinksToUsernamesAndLists($matches) {
929     list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, '');
930     # If $after is not empty, there is an invalid character.
931     if (!empty($slash_listname)) {
932       # Replace the list and username
933       $element = $username . $slash_listname;
934       $class = $this->class_list;
935       $url = $this->url_base_list . $element;
936     } else {
937       if (preg_match(self::$patterns['end_mention_match'], $after)) return $all;
938       # Replace the username
939       $element = $username;
940       $class = $this->class_user;
941       $url = $this->url_base_user . $element;
942     }
943     # XXX: Due to use of preg_replace_callback() for multiple replacements in a
944     #      single tweet and also as only the match is replaced and we have to
945     #      use a look-ahead for $after because there is no equivalent for the
946     #      $' (dollar apostrophe) global from Ruby, we MUST NOT append $after.
947     return $before . $at . $this->wrap($url, $class, $element);
948   }
949
950 }