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