123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848 |
- <?php
- /**
- * @author Mike Cochrane <mikec@mikenz.geek.nz>
- * @author Nick Pope <nick@nickpope.me.uk>
- * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
- */
- namespace App\Util\Lexer;
- use Illuminate\Support\Str;
- use App\Services\AutolinkService;
- /**
- * Twitter Autolink Class.
- *
- * Parses tweets and generates HTML anchor tags around URLs, usernames,
- * username/list pairs and hashtags.
- *
- * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
- * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
- * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
- *
- * @author Mike Cochrane <mikec@mikenz.geek.nz>
- * @author Nick Pope <nick@nickpope.me.uk>
- * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
- */
- class Autolink extends Regex
- {
- /**
- * CSS class for auto-linked URLs.
- *
- * @var string
- */
- protected $class_url = '';
- /**
- * CSS class for auto-linked username URLs.
- *
- * @var string
- */
- protected $class_user = 'u-url mention';
- /**
- * CSS class for auto-linked list URLs.
- *
- * @var string
- */
- protected $class_list = 'u-url list-slug';
- /**
- * CSS class for auto-linked hashtag URLs.
- *
- * @var string
- */
- protected $class_hash = 'u-url hashtag';
- /**
- * CSS class for auto-linked cashtag URLs.
- *
- * @var string
- */
- protected $class_cash = 'u-url cashtag';
- /**
- * URL base for username links (the username without the @ will be appended).
- *
- * @var string
- */
- protected $url_base_user = null;
- /**
- * URL base for list links (the username/list without the @ will be appended).
- *
- * @var string
- */
- protected $url_base_list = null;
- /**
- * URL base for hashtag links (the hashtag without the # will be appended).
- *
- * @var string
- */
- protected $url_base_hash = null;
- /**
- * URL base for cashtag links (the hashtag without the $ will be appended).
- *
- * @var string
- */
- protected $url_base_cash = null;
- /**
- * Whether to include the value 'nofollow' in the 'rel' attribute.
- *
- * @var bool
- */
- protected $nofollow = true;
- /**
- * Whether to include the value 'noopener' in the 'rel' attribute.
- *
- * @var bool
- */
- protected $noopener = true;
- /**
- * Whether to include the value 'external' in the 'rel' attribute.
- *
- * Often this is used to be matched on in JavaScript for dynamically adding
- * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
- * been undeprecated and thus the 'target' attribute can be used. If this is
- * set to false then the 'target' attribute will be output.
- *
- * @var bool
- */
- protected $external = true;
- /**
- * The scope to open the link in.
- *
- * Support for the 'target' attribute was deprecated in HTML 4.01 but has
- * since been reinstated in HTML 5. To output the 'target' attribute you
- * must disable the adding of the string 'external' to the 'rel' attribute.
- *
- * @var string
- */
- protected $target = '_blank';
- /**
- * attribute for invisible span tag.
- *
- * @var string
- */
- protected $invisibleTagAttrs = "style='position:absolute;left:-9999px;'";
- /**
- * @var Extractor
- */
- protected $extractor = null;
- /**
- * @var autolinkActiveUsersOnly
- */
- protected $autolinkActiveUsersOnly = false;
- /**
- * Provides fluent method chaining.
- *
- * @param string $tweet The tweet to be converted.
- * @param bool $full_encode Whether to encode all special characters.
- *
- * @see __construct()
- *
- * @return Autolink
- */
- public static function create($tweet = null, $full_encode = false)
- {
- return new static($tweet, $full_encode);
- }
- /**
- * Reads in a tweet to be parsed and converted to contain links.
- *
- * As the intent is to produce links and output the modified tweet to the
- * user, we take this opportunity to ensure that we escape user input.
- *
- * @see htmlspecialchars()
- *
- * @param string $tweet The tweet to be converted.
- * @param bool $escape Whether to escape the tweet (default: true).
- * @param bool $full_encode Whether to encode all special characters.
- */
- public function __construct($tweet = null, $escape = true, $full_encode = false)
- {
- if ($escape && !empty($tweet)) {
- if ($full_encode) {
- parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
- } else {
- parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
- }
- } else {
- parent::__construct($tweet);
- }
- $this->extractor = Extractor::create();
- $this->url_base_user = config('app.url').'/';
- $this->url_base_list = config('app.url').'/';
- $this->url_base_hash = config('app.url').'/discover/tags/';
- $this->url_base_cash = config('app.url').'/search?q=%24';
- }
- public function setBaseUserPath($path = '/')
- {
- $this->url_base_user = config('app.url') . $path;
- $this->target = null;
- $this->external = null;
- return $this;
- }
- public function setBaseHashPath($path = '/discover/tags/')
- {
- $this->url_base_hash = config('app.url') . $path;
- $this->target = null;
- $this->external = null;
- return $this;
- }
- public function setAutolinkActiveUsersOnly($active)
- {
- $this->autolinkActiveUsersOnly = $active;
- $this->target = null;
- $this->external = null;
- return $this;
- }
- /**
- * CSS class for auto-linked URLs.
- *
- * @return string CSS class for URL links.
- */
- public function getURLClass()
- {
- return $this->class_url;
- }
- /**
- * CSS class for auto-linked URLs.
- *
- * @param string $v CSS class for URL links.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setURLClass($v)
- {
- $this->class_url = trim($v);
- return $this;
- }
- /**
- * CSS class for auto-linked username URLs.
- *
- * @return string CSS class for username links.
- */
- public function getUsernameClass()
- {
- return $this->class_user;
- }
- /**
- * CSS class for auto-linked username URLs.
- *
- * @param string $v CSS class for username links.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setUsernameClass($v)
- {
- $this->class_user = trim($v);
- return $this;
- }
- /**
- * CSS class for auto-linked username/list URLs.
- *
- * @return string CSS class for username/list links.
- */
- public function getListClass()
- {
- return $this->class_list;
- }
- /**
- * CSS class for auto-linked username/list URLs.
- *
- * @param string $v CSS class for username/list links.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setListClass($v)
- {
- $this->class_list = trim($v);
- return $this;
- }
- /**
- * CSS class for auto-linked hashtag URLs.
- *
- * @return string CSS class for hashtag links.
- */
- public function getHashtagClass()
- {
- return $this->class_hash;
- }
- /**
- * CSS class for auto-linked hashtag URLs.
- *
- * @param string $v CSS class for hashtag links.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setHashtagClass($v)
- {
- $this->class_hash = trim($v);
- return $this;
- }
- /**
- * CSS class for auto-linked cashtag URLs.
- *
- * @return string CSS class for cashtag links.
- */
- public function getCashtagClass()
- {
- return $this->class_cash;
- }
- /**
- * CSS class for auto-linked cashtag URLs.
- *
- * @param string $v CSS class for cashtag links.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setCashtagClass($v)
- {
- $this->class_cash = trim($v);
- return $this;
- }
- /**
- * Whether to include the value 'nofollow' in the 'rel' attribute.
- *
- * @return bool Whether to add 'nofollow' to the 'rel' attribute.
- */
- public function getNoFollow()
- {
- return $this->nofollow;
- }
- /**
- * Whether to include the value 'nofollow' in the 'rel' attribute.
- *
- * @param bool $v The value to add to the 'target' attribute.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setNoFollow($v)
- {
- $this->nofollow = $v;
- return $this;
- }
- /**
- * Whether to include the value 'external' in the 'rel' attribute.
- *
- * Often this is used to be matched on in JavaScript for dynamically adding
- * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
- * been undeprecated and thus the 'target' attribute can be used. If this is
- * set to false then the 'target' attribute will be output.
- *
- * @return bool Whether to add 'external' to the 'rel' attribute.
- */
- public function getExternal()
- {
- return $this->external;
- }
- /**
- * Whether to include the value 'external' in the 'rel' attribute.
- *
- * Often this is used to be matched on in JavaScript for dynamically adding
- * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
- * been undeprecated and thus the 'target' attribute can be used. If this is
- * set to false then the 'target' attribute will be output.
- *
- * @param bool $v The value to add to the 'target' attribute.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setExternal($v)
- {
- $this->external = $v;
- return $this;
- }
- /**
- * The scope to open the link in.
- *
- * Support for the 'target' attribute was deprecated in HTML 4.01 but has
- * since been reinstated in HTML 5. To output the 'target' attribute you
- * must disable the adding of the string 'external' to the 'rel' attribute.
- *
- * @return string The value to add to the 'target' attribute.
- */
- public function getTarget()
- {
- return $this->target;
- }
- /**
- * The scope to open the link in.
- *
- * Support for the 'target' attribute was deprecated in HTML 4.01 but has
- * since been reinstated in HTML 5. To output the 'target' attribute you
- * must disable the adding of the string 'external' to the 'rel' attribute.
- *
- * @param string $v The value to add to the 'target' attribute.
- *
- * @return Autolink Fluid method chaining.
- */
- public function setTarget($v)
- {
- $this->target = trim($v);
- return $this;
- }
- /**
- * Autolink with entities.
- *
- * @param string $tweet
- * @param array $entities
- *
- * @return string
- *
- * @since 1.1.0
- */
- public function autoLinkEntities($tweet = null, $entities = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $text = '';
- $beginIndex = 0;
- foreach ($entities as $entity) {
- if (isset($entity['screen_name'])) {
- if(Str::startsWith($entity['screen_name'], '@')) {
- $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
- } else {
- $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
- }
- } else {
- $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
- }
- if (isset($entity['url'])) {
- $text .= $this->linkToUrl($entity);
- } elseif (isset($entity['hashtag'])) {
- $text .= $this->linkToHashtag($entity, $tweet);
- } elseif (isset($entity['screen_name'])) {
- $text .= $this->linkToMentionAndList($entity);
- } elseif (isset($entity['cashtag'])) {
- $text .= $this->linkToCashtag($entity, $tweet);
- }
- $beginIndex = $entity['indices'][1];
- }
- $text .= StringUtils::substr($tweet, $beginIndex, StringUtils::strlen($tweet));
- return $text;
- }
- /**
- * Auto-link hashtags, URLs, usernames and lists, with JSON entities.
- *
- * @param string The tweet to be converted
- * @param mixed The entities info
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLinkWithJson($tweet = null, $json = null)
- {
- // concatenate entities
- $entities = [];
- if (is_object($json)) {
- $json = $this->object2array($json);
- }
- if (is_array($json)) {
- foreach ($json as $key => $vals) {
- $entities = array_merge($entities, $json[$key]);
- }
- }
- // map JSON entity to twitter-text entity
- foreach ($entities as $idx => $entity) {
- if (!empty($entity['text'])) {
- $entities[$idx]['hashtag'] = $entity['text'];
- }
- }
- $entities = $this->extractor->removeOverlappingEntities($entities);
- return $this->autoLinkEntities($tweet, $entities);
- }
- /**
- * convert Object to Array.
- *
- * @param mixed $obj
- *
- * @return array
- */
- protected function object2array($obj)
- {
- $array = (array) $obj;
- foreach ($array as $key => $var) {
- if (is_object($var) || is_array($var)) {
- $array[$key] = $this->object2array($var);
- }
- }
- return $array;
- }
- /**
- * Auto-link hashtags, URLs, usernames and lists.
- *
- * @param string The tweet to be converted
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLink($tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $entities = $this->extractor->extractURLWithoutProtocol(false)->extractEntitiesWithIndices($tweet);
- return $this->autoLinkEntities($tweet, $entities);
- }
- /**
- * Auto-link the @username and @username/list references in the provided text. Links to @username references will
- * have the usernameClass CSS classes added. Links to @username/list references will have the listClass CSS class
- * added.
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLinkUsernamesAndLists($tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $entities = $this->extractor->extractMentionsOrListsWithIndices($tweet);
- if($this->autolinkActiveUsersOnly == true) {
- $entities = collect($entities)
- ->filter(function($entity) {
- return AutolinkService::mentionedUsernameExists($entity['screen_name']);
- })
- ->toArray();
- }
- return $this->autoLinkEntities($tweet, $entities);
- }
- /**
- * Auto-link #hashtag references in the provided Tweet text. The #hashtag links will have the hashtagClass CSS class
- * added.
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLinkHashtags($tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $entities = $this->extractor->extractHashtagsWithIndices($tweet);
- return $this->autoLinkEntities($tweet, $entities);
- }
- /**
- * Auto-link URLs in the Tweet text provided.
- * <p/>
- * This only auto-links URLs with protocol.
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLinkURLs($tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $entities = $this->extractor->extractURLWithoutProtocol(false)->extractURLsWithIndices($tweet);
- return $this->autoLinkEntities($tweet, $entities);
- }
- /**
- * Auto-link $cashtag references in the provided Tweet text. The $cashtag links will have the cashtagClass CSS class
- * added.
- *
- * @return string that auto-link HTML added
- *
- * @since 1.1.0
- */
- public function autoLinkCashtags($tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $entities = $this->extractor->extractCashtagsWithIndices($tweet);
- return $this->autoLinkEntities($tweet, $entities);
- }
- public function linkToUrl($entity)
- {
- if (!empty($this->class_url)) {
- $attributes['class'] = $this->class_url;
- }
- $attributes['href'] = $entity['url'];
- $linkText = $this->escapeHTML($entity['url']);
- if (!empty($entity['display_url']) && !empty($entity['expanded_url'])) {
- // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
- // should contain the full original URL (expanded_url), not the display URL.
- //
- // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
- // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
- // Elements with font-size:0 get copied even though they are not visible.
- // Note that display:none doesn't work here. Elements with display:none don't get copied.
- //
- // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
- // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
- // everything with the tco-ellipsis class.
- //
- // As an example: The user tweets "hi http://longdomainname.com/foo"
- // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
- // This will get rendered as:
- // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
- // …
- // <!-- There's a chance the onCopy event handler might not fire. In case that happens,
- // we include an here so that the … doesn't bump up against the URL and ruin it.
- // The is inside the tco-ellipsis span so that when the onCopy handler *does*
- // fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
- // e.g. "hi http://longdomainname.com/foo".
- // <span style='font-size:0'> </span>
- // </span>
- // <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
- // http://longdomai
- // </span>
- // <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
- // nname.com/foo
- // </span>
- // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
- // <span style='font-size:0'> </span>
- // …
- // </span>
- //
- // Exception: pic.socialhub.dev images, for which expandedUrl = "https://socialhub.dev/#!/username/status/1234/photo/1
- // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
- // For a pic.socialhub.dev URL, the only elided part will be the "https://", so this is fine.
- $displayURL = $entity['display_url'];
- $expandedURL = $entity['expanded_url'];
- $displayURLSansEllipses = preg_replace('/…/u', '', $displayURL);
- $diplayURLIndexInExpandedURL = mb_strpos($expandedURL, $displayURLSansEllipses);
- if ($diplayURLIndexInExpandedURL !== false) {
- $beforeDisplayURL = mb_substr($expandedURL, 0, $diplayURLIndexInExpandedURL);
- $afterDisplayURL = mb_substr($expandedURL, $diplayURLIndexInExpandedURL + mb_strlen($displayURLSansEllipses));
- $precedingEllipsis = (preg_match('/\A…/u', $displayURL)) ? '…' : '';
- $followingEllipsis = (preg_match('/…\z/u', $displayURL)) ? '…' : '';
- $invisibleSpan = "<span {$this->invisibleTagAttrs}>";
- $linkText = "<span class='tco-ellipsis'>{$precedingEllipsis}{$invisibleSpan} </span></span>";
- $linkText .= "{$invisibleSpan}{$this->escapeHTML($beforeDisplayURL)}</span>";
- $linkText .= "<span class='js-display-url'>{$this->escapeHTML($displayURLSansEllipses)}</span>";
- $linkText .= "{$invisibleSpan}{$this->escapeHTML($afterDisplayURL)}</span>";
- $linkText .= "<span class='tco-ellipsis'>{$invisibleSpan} </span>{$followingEllipsis}</span>";
- } else {
- $linkText = $entity['display_url'];
- }
- $attributes['title'] = $entity['expanded_url'];
- } elseif (!empty($entity['display_url'])) {
- $linkText = $entity['display_url'];
- }
- return $this->linkToText($entity, $linkText, $attributes);
- }
- /**
- * @param array $entity
- * @param string $tweet
- *
- * @return string
- *
- * @since 1.1.0
- */
- public function linkToHashtag($entity, $tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $this->target = false;
- $attributes = [];
- $class = [];
- $hash = StringUtils::substr($tweet, $entity['indices'][0], 1);
- $linkText = $hash.$entity['hashtag'];
- $attributes['href'] = $this->url_base_hash.$entity['hashtag'].'?src=hash';
- $attributes['title'] = '#'.$entity['hashtag'];
- if (!empty($this->class_hash)) {
- $class[] = $this->class_hash;
- }
- if (preg_match(self::$patterns['rtl_chars'], $linkText)) {
- $class[] = 'rtl';
- }
- if (!empty($class)) {
- $attributes['class'] = implode(' ', $class);
- }
- return $this->linkToText($entity, $linkText, $attributes);
- }
- /**
- * @param array $entity
- *
- * @return string
- *
- * @since 1.1.0
- */
- public function linkToMentionAndList($entity)
- {
- $attributes = [];
- $screen_name = $entity['screen_name'];
- if($this->autolinkActiveUsersOnly == true) {
- if(!AutolinkService::mentionedUsernameExists($screen_name)) {
- return Str::of($screen_name)->startsWith('@') ? $screen_name : "@{$screen_name}";
- }
- }
- if (!empty($entity['list_slug'])) {
- // Replace the list and username
- $linkText = Str::startsWith($screen_name, '@') ? $screen_name : '@'.$screen_name;
- $class = $this->class_list;
- $url = $this->url_base_list.$screen_name;
- } else {
- // Replace the username
- $linkText = Str::startsWith($screen_name, '@') ? $screen_name : '@'.$screen_name;
- $class = $this->class_user;
- $url = $this->url_base_user . $screen_name;
- }
- if (!empty($class)) {
- $attributes['class'] = $class;
- }
- $attributes['href'] = $url;
- return $this->linkToText($entity, $linkText, $attributes);
- }
- /**
- * @param array $entity
- * @param string $tweet
- *
- * @return string
- *
- * @since 1.1.0
- */
- public function linkToCashtag($entity, $tweet = null)
- {
- if (is_null($tweet)) {
- $tweet = $this->tweet;
- }
- $attributes = [];
- $doller = StringUtils::substr($tweet, $entity['indices'][0], 1);
- $linkText = $doller.$entity['cashtag'];
- $attributes['href'] = $this->url_base_cash.$entity['cashtag'];
- $attributes['title'] = $linkText;
- if (!empty($this->class_cash)) {
- $attributes['class'] = $this->class_cash;
- }
- return $this->linkToText($entity, $linkText, $attributes);
- }
- /**
- * @param array $entity
- * @param string $text
- * @param array $attributes
- *
- * @return string
- *
- * @since 1.1.0
- */
- public function linkToText(array $entity, $text, $attributes = [])
- {
- $rel = [];
- if ($this->external) {
- $rel[] = 'external';
- }
- if ($this->nofollow) {
- $rel[] = 'nofollow';
- }
- if ($this->noopener) {
- $rel[] = 'noopener';
- }
- if (!empty($rel)) {
- $attributes['rel'] = implode(' ', $rel);
- }
- if ($this->target) {
- $attributes['target'] = $this->target;
- }
- $link = '<a';
- foreach ($attributes as $key => $val) {
- $link .= ' '.$key.'="'.$this->escapeHTML($val).'"';
- }
- $link .= '>'.$text.'</a>';
- return $link;
- }
- /**
- * html escape.
- *
- * @param string $text
- *
- * @return string
- */
- protected function escapeHTML($text)
- {
- return htmlspecialchars($text, ENT_QUOTES, 'UTF-8', false);
- }
- }
|