cake/libs/view/helpers/text.php
| 1 | <?php |
|---|---|
| 2 | /* SVN FILE: $Id$ */ |
| 3 | /** |
| 4 | * Text Helper |
| 5 | * |
| 6 | * Text manipulations: Highlight, excerpt, truncate, strip of links, convert email addresses to mailto: links... |
| 7 | * |
| 8 | * PHP versions 4 and 5 |
| 9 | * |
| 10 | * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org) |
| 11 | * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) |
| 12 | * |
| 13 | * Licensed under The MIT License |
| 14 | * Redistributions of files must retain the above copyright notice. |
| 15 | * |
| 16 | * @filesource |
| 17 | * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) |
| 18 | * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project |
| 19 | * @package cake |
| 20 | * @subpackage cake.cake.libs.view.helpers |
| 21 | * @since CakePHP(tm) v 0.10.0.1076 |
| 22 | * @version $Revision$ |
| 23 | * @modifiedby $LastChangedBy$ |
| 24 | * @lastmodified $Date$ |
| 25 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License |
| 26 | */ |
| 27 | /** |
| 28 | * Included libraries. |
| 29 | * |
| 30 | */ |
| 31 | if (!class_exists('HtmlHelper')) { |
| 32 | App::import('Helper', 'Html'); |
| 33 | } |
| 34 | if (!class_exists('Multibyte')) { |
| 35 | App::import('Core', 'Multibyte'); |
| 36 | } |
| 37 | /** |
| 38 | * Text helper library. |
| 39 | * |
| 40 | * Text manipulations: Highlight, excerpt, truncate, strip of links, convert email addresses to mailto: links... |
| 41 | * |
| 42 | * @package cake |
| 43 | * @subpackage cake.cake.libs.view.helpers |
| 44 | */ |
| 45 | class TextHelper extends AppHelper { |
| 46 | /** |
| 47 | * Highlights a given phrase in a text. You can specify any expression in highlighter that |
| 48 | * may include the \1 expression to include the $phrase found. |
| 49 | * |
| 50 | * @param string $text Text to search the phrase in |
| 51 | * @param string $phrase The phrase that will be searched |
| 52 | * @param string $highlighter The piece of html with that the phrase will be highlighted |
| 53 | * @param boolean $considerHtml If true, will ignore any HTML tags, ensuring that only the correct text is highlighted |
| 54 | * @return string The highlighted text |
| 55 | * @access public |
| 56 | */ |
| 57 | function highlight($text, $phrase, $highlighter = '<span class="highlight">\1</span>', $considerHtml = false) { |
| 58 | if (empty($phrase)) { |
| 59 | return $text; |
| 60 | } |
| 61 | |
| 62 | if (is_array($phrase)) { |
| 63 | $replace = array(); |
| 64 | $with = array(); |
| 65 | |
| 66 | foreach ($phrase as $key => $value) { |
| 67 | $key = $value; |
| 68 | $value = $highlighter; |
| 69 | $key = '(' . $key . ')'; |
| 70 | if ($considerHtml) { |
| 71 | $key = '(?![^<]+>)' . $key . '(?![^<]+>)'; |
| 72 | } |
| 73 | $replace[] = '|' . $key . '|iu'; |
| 74 | $with[] = empty($value) ? $highlighter : $value; |
| 75 | } |
| 76 | |
| 77 | return preg_replace($replace, $with, $text); |
| 78 | } else { |
| 79 | $phrase = '(' . $phrase . ')'; |
| 80 | if ($considerHtml) { |
| 81 | $phrase = '(?![^<]+>)' . $phrase . '(?![^<]+>)'; |
| 82 | } |
| 83 | |
| 84 | return preg_replace('|'.$phrase.'|iu', $highlighter, $text); |
| 85 | } |
| 86 | } |
| 87 | /** |
| 88 | * Strips given text of all links (<a href=....) |
| 89 | * |
| 90 | * @param string $text Text |
| 91 | * @return string The text without links |
| 92 | * @access public |
| 93 | */ |
| 94 | function stripLinks($text) { |
| 95 | return preg_replace('|<a\s+[^>]+>|im', '', preg_replace('|<\/a>|im', '', $text)); |
| 96 | } |
| 97 | /** |
| 98 | * Adds links (<a href=....) to a given text, by finding text that begins with |
| 99 | * strings like http:// and ftp://. |
| 100 | * |
| 101 | * @param string $text Text to add links to |
| 102 | * @param array $htmlOptions Array of HTML options. |
| 103 | * @return string The text with links |
| 104 | * @access public |
| 105 | */ |
| 106 | function autoLinkUrls($text, $htmlOptions = array()) { |
| 107 | $options = 'array('; |
| 108 | foreach ($htmlOptions as $option => $value) { |
| 109 | $value = var_export($value, true); |
| 110 | $options .= "'$option' => $value, "; |
| 111 | } |
| 112 | $options .= ')'; |
| 113 | |
| 114 | $text = preg_replace_callback('#(?<!href="|">)((?:http|https|ftp|nntp)://[^ <]+)#i', create_function('$matches', |
| 115 | '$Html = new HtmlHelper(); $Html->tags = $Html->loadConfig(); return $Html->link($matches[0], $matches[0],' . $options . ');'), $text); |
| 116 | |
| 117 | return preg_replace_callback('#(?<!href="|">)(?<!http://|https://|ftp://|nntp://)(www\.[^\n\%\ <]+[^<\n\%\,\.\ <])(?<!\))#i', |
| 118 | create_function('$matches', '$Html = new HtmlHelper(); $Html->tags = $Html->loadConfig(); return $Html->link($matches[0], "http://" . strtolower($matches[0]),' . $options . ');'), $text); |
| 119 | } |
| 120 | /** |
| 121 | * Adds email links (<a href="mailto:....) to a given text. |
| 122 | * |
| 123 | * @param string $text Text |
| 124 | * @param array $htmlOptions Array of HTML options. |
| 125 | * @return string The text with links |
| 126 | * @access public |
| 127 | */ |
| 128 | function autoLinkEmails($text, $htmlOptions = array()) { |
| 129 | $options = 'array('; |
| 130 | |
| 131 | foreach ($htmlOptions as $option => $value) { |
| 132 | $options .= "'$option' => '$value', "; |
| 133 | } |
| 134 | $options .= ')'; |
| 135 | |
| 136 | return preg_replace_callback('#([_A-Za-z0-9+-]+(?:\.[_A-Za-z0-9+-]+)*@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)#', |
| 137 | create_function('$matches', '$Html = new HtmlHelper(); $Html->tags = $Html->loadConfig(); return $Html->link($matches[0], "mailto:" . $matches[0],' . $options . ');'), $text); |
| 138 | } |
| 139 | /** |
| 140 | * Convert all links and email adresses to HTML links. |
| 141 | * |
| 142 | * @param string $text Text |
| 143 | * @param array $htmlOptions Array of HTML options. |
| 144 | * @return string The text with links |
| 145 | * @access public |
| 146 | */ |
| 147 | function autoLink($text, $htmlOptions = array()) { |
| 148 | return $this->autoLinkEmails($this->autoLinkUrls($text, $htmlOptions), $htmlOptions); |
| 149 | } |
| 150 | /** |
| 151 | * Truncates text. |
| 152 | * |
| 153 | * Cuts a string to the length of $length and replaces the last characters |
| 154 | * with the ending if the text is longer than length. |
| 155 | * |
| 156 | * @param string $text String to truncate. |
| 157 | * @param integer $length Length of returned string, including ellipsis. |
| 158 | * @param mixed $ending If string, will be used as Ending and appended to the trimmed string. Can also be an associative array that can contain the last three params of this method. |
| 159 | * @param boolean $exact If false, $text will not be cut mid-word |
| 160 | * @param boolean $considerHtml If true, HTML tags would be handled correctly |
| 161 | * @return string Trimmed string. |
| 162 | */ |
| 163 | function truncate($text, $length = 100, $ending = '...', $exact = true, $considerHtml = false) { |
| 164 | if (is_array($ending)) { |
| 165 | extract($ending); |
| 166 | } |
| 167 | if ($considerHtml) { |
| 168 | if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { |
| 169 | return $text; |
| 170 | } |
| 171 | $totalLength = mb_strlen($ending); |
| 172 | $openTags = array(); |
| 173 | $truncate = ''; |
| 174 | preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); |
| 175 | foreach ($tags as $tag) { |
| 176 | if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) { |
| 177 | if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) { |
| 178 | array_unshift($openTags, $tag[2]); |
| 179 | } else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) { |
| 180 | $pos = array_search($closeTag[1], $openTags); |
| 181 | if ($pos !== false) { |
| 182 | array_splice($openTags, $pos, 1); |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | $truncate .= $tag[1]; |
| 187 | |
| 188 | $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); |
| 189 | if ($contentLength + $totalLength > $length) { |
| 190 | $left = $length - $totalLength; |
| 191 | $entitiesLength = 0; |
| 192 | if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) { |
| 193 | foreach ($entities[0] as $entity) { |
| 194 | if ($entity[1] + 1 - $entitiesLength <= $left) { |
| 195 | $left--; |
| 196 | $entitiesLength += mb_strlen($entity[0]); |
| 197 | } else { |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | $truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength); |
| 204 | break; |
| 205 | } else { |
| 206 | $truncate .= $tag[3]; |
| 207 | $totalLength += $contentLength; |
| 208 | } |
| 209 | if ($totalLength >= $length) { |
| 210 | break; |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | } else { |
| 215 | if (mb_strlen($text) <= $length) { |
| 216 | return $text; |
| 217 | } else { |
| 218 | $truncate = mb_substr($text, 0, $length - strlen($ending)); |
| 219 | } |
| 220 | } |
| 221 | if (!$exact) { |
| 222 | $spacepos = mb_strrpos($truncate, ' '); |
| 223 | if (isset($spacepos)) { |
| 224 | if ($considerHtml) { |
| 225 | $bits = mb_substr($truncate, $spacepos); |
| 226 | preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); |
| 227 | if (!empty($droppedTags)) { |
| 228 | foreach ($droppedTags as $closingTag) { |
| 229 | if (!in_array($closingTag[1], $openTags)) { |
| 230 | array_unshift($openTags, $closingTag[1]); |
| 231 | } |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | $truncate = mb_substr($truncate, 0, $spacepos); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | $truncate .= $ending; |
| 240 | |
| 241 | if ($considerHtml) { |
| 242 | foreach ($openTags as $tag) { |
| 243 | $truncate .= '</'.$tag.'>'; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | return $truncate; |
| 248 | } |
| 249 | /** |
| 250 | * Alias for truncate(). |
| 251 | * |
| 252 | * @see TextHelper::truncate() |
| 253 | * @access public |
| 254 | */ |
| 255 | function trim() { |
| 256 | $args = func_get_args(); |
| 257 | return call_user_func_array(array(&$this, 'truncate'), $args); |
| 258 | } |
| 259 | /** |
| 260 | * Extracts an excerpt from the text surrounding the phrase with a number of characters on each side determined by radius. |
| 261 | * |
| 262 | * @param string $text String to search the phrase in |
| 263 | * @param string $phrase Phrase that will be searched for |
| 264 | * @param integer $radius The amount of characters that will be returned on each side of the founded phrase |
| 265 | * @param string $ending Ending that will be appended |
| 266 | * @return string Modified string |
| 267 | * @access public |
| 268 | */ |
| 269 | function excerpt($text, $phrase, $radius = 100, $ending = "...") { |
| 270 | if (empty($text) or empty($phrase)) { |
| 271 | return $this->truncate($text, $radius * 2, $ending); |
| 272 | } |
| 273 | |
| 274 | $phraseLen = strlen($phrase); |
| 275 | if ($radius < $phraseLen) { |
| 276 | $radius = $phraseLen; |
| 277 | } |
| 278 | |
| 279 | $pos = strpos(strtolower($text), strtolower($phrase)); |
| 280 | |
| 281 | $startPos = 0; |
| 282 | if ($pos > $radius) { |
| 283 | $startPos = $pos - $radius; |
| 284 | } |
| 285 | |
| 286 | $textLen = strlen($text); |
| 287 | |
| 288 | $endPos = $pos + $phraseLen + $radius; |
| 289 | if ($endPos >= $textLen) { |
| 290 | $endPos = $textLen; |
| 291 | } |
| 292 | |
| 293 | $excerpt = substr($text, $startPos, $endPos - $startPos); |
| 294 | if ($startPos != 0) { |
| 295 | $excerpt = substr_replace($excerpt, $ending, 0, $phraseLen); |
| 296 | } |
| 297 | |
| 298 | if ($endPos != $textLen) { |
| 299 | $excerpt = substr_replace($excerpt, $ending, -$phraseLen); |
| 300 | } |
| 301 | |
| 302 | return $excerpt; |
| 303 | } |
| 304 | /** |
| 305 | * Creates a comma separated list where the last two items are joined with 'and', forming natural English |
| 306 | * |
| 307 | * @param array $list The list to be joined |
| 308 | * @return string |
| 309 | * @access public |
| 310 | */ |
| 311 | function toList($list, $and = 'and') { |
| 312 | $r = ''; |
| 313 | $c = count($list) - 1; |
| 314 | foreach ($list as $i => $item) { |
| 315 | $r .= $item; |
| 316 | if ($c > 0 && $i < $c) |
| 317 | { |
| 318 | $r .= ($i < $c - 1 ? ', ' : " {$and} "); |
| 319 | } |
| 320 | } |
| 321 | return $r; |
| 322 | } |
| 323 | /** |
| 324 | * Text-to-html parser, similar to Textile or RedCloth, only with a little different syntax. |
| 325 | * |
| 326 | * @param string $text String to "flay" |
| 327 | * @param boolean $allowHtml Set to true if if html is allowed |
| 328 | * @return string "Flayed" text |
| 329 | * @access public |
| 330 | * @todo Change this. We need a real Textile parser. |
| 331 | * @codeCoverageIgnoreStart |
| 332 | */ |
| 333 | function flay($text, $allowHtml = false) { |
| 334 | trigger_error(__('(TextHelper::flay) Deprecated: the Flay library is no longer supported and will be removed in a future version.', true), E_USER_WARNING); |
| 335 | if (!class_exists('Flay')) { |
| 336 | uses('flay'); |
| 337 | } |
| 338 | return Flay::toHtml($text, false, $allowHtml); |
| 339 | } |
| 340 | /** |
| 341 | * @codeCoverageIgnoreEnd |
| 342 | */ |
| 343 | } |
| 344 | ?> |
| 345 |