1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
21:
22: App::uses('AppHelper', 'View/Helper');
23: App::uses('HtmlHelper', 'Helper');
24: App::uses('Multibyte', 'I18n');
25:
26: 27: 28: 29: 30: 31: 32: 33: 34:
35: class TextHelper extends AppHelper {
36:
37: 38: 39: 40: 41:
42: public $helpers = array('Html');
43:
44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
59: public function highlight($text, $phrase, $options = array()) {
60: if (empty($phrase)) {
61: return $text;
62: }
63:
64: $default = array(
65: 'format' => '<span class="highlight">\1</span>',
66: 'html' => false
67: );
68: $options = array_merge($default, $options);
69: extract($options);
70:
71: if (is_array($phrase)) {
72: $replace = array();
73: $with = array();
74:
75: foreach ($phrase as $key => $segment) {
76: $segment = '(' . preg_quote($segment, '|') . ')';
77: if ($html) {
78: $segment = "(?![^<]+>)$segment(?![^<]+>)";
79: }
80:
81: $with[] = (is_array($format)) ? $format[$key] : $format;
82: $replace[] = "|$segment|iu";
83: }
84:
85: return preg_replace($replace, $with, $text);
86: } else {
87: $phrase = '(' . preg_quote($phrase, '|') . ')';
88: if ($html) {
89: $phrase = "(?![^<]+>)$phrase(?![^<]+>)";
90: }
91:
92: return preg_replace("|$phrase|iu", $format, $text);
93: }
94: }
95:
96: 97: 98: 99: 100: 101: 102:
103: public function stripLinks($text) {
104: return preg_replace('|<a\s+[^>]+>|im', '', preg_replace('|<\/a>|im', '', $text));
105: }
106:
107: 108: 109: 110: 111: 112: 113: 114: 115:
116: public function autoLinkUrls($text, $htmlOptions = array()) {
117: $this->_linkOptions = $htmlOptions;
118: $text = preg_replace_callback(
119: '#(?<!href="|src="|">)((?:https?|ftp|nntp)://[^\s<>()]+)#i',
120: array(&$this, '_linkBareUrl'),
121: $text
122: );
123: return preg_replace_callback(
124: '#(?<!href="|">)(?<!http://|https://|ftp://|nntp://)(www\.[^\n\%\ <]+[^<\n\%\,\.\ <])(?<!\))#i',
125: array(&$this, '_linkUrls'),
126: $text
127: );
128: }
129:
130: 131: 132: 133: 134: 135: 136:
137: protected function _linkBareUrl($matches) {
138: return $this->Html->link($matches[0], $matches[0], $this->_linkOptions);
139: }
140:
141: 142: 143: 144: 145: 146: 147:
148: protected function _linkUrls($matches) {
149: return $this->Html->link($matches[0], 'http://' . $matches[0], $this->_linkOptions);
150: }
151:
152: 153: 154: 155: 156: 157: 158:
159: protected function _linkEmails($matches) {
160: return $this->Html->link($matches[0], 'mailto:' . $matches[0], $this->_linkOptions);
161: }
162:
163: 164: 165: 166: 167: 168: 169: 170:
171: public function autoLinkEmails($text, $options = array()) {
172: $this->_linkOptions = $options;
173: $atom = '[a-z0-9!#$%&\'*+\/=?^_`{|}~-]';
174: return preg_replace_callback(
175: '/(' . $atom . '+(?:\.' . $atom . '+)*@[a-z0-9-]+(?:\.[a-z0-9-]+)+)/i',
176: array(&$this, '_linkEmails'),
177: $text
178: );
179: }
180:
181: 182: 183: 184: 185: 186: 187: 188:
189: public function autoLink($text, $options = array()) {
190: return $this->autoLinkEmails($this->autoLinkUrls($text, $options), $options);
191: }
192:
193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210:
211: public function truncate($text, $length = 100, $options = array()) {
212: $default = array(
213: 'ending' => '...', 'exact' => true, 'html' => false
214: );
215: $options = array_merge($default, $options);
216: extract($options);
217:
218: if (!function_exists('mb_strlen')) {
219: class_exists('Multibyte');
220: }
221:
222: if ($html) {
223: if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
224: return $text;
225: }
226: $totalLength = mb_strlen(strip_tags($ending));
227: $openTags = array();
228: $truncate = '';
229:
230: preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
231: foreach ($tags as $tag) {
232: if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
233: if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
234: array_unshift($openTags, $tag[2]);
235: } else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
236: $pos = array_search($closeTag[1], $openTags);
237: if ($pos !== false) {
238: array_splice($openTags, $pos, 1);
239: }
240: }
241: }
242: $truncate .= $tag[1];
243:
244: $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
245: if ($contentLength + $totalLength > $length) {
246: $left = $length - $totalLength;
247: $entitiesLength = 0;
248: 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)) {
249: foreach ($entities[0] as $entity) {
250: if ($entity[1] + 1 - $entitiesLength <= $left) {
251: $left--;
252: $entitiesLength += mb_strlen($entity[0]);
253: } else {
254: break;
255: }
256: }
257: }
258:
259: $truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength);
260: break;
261: } else {
262: $truncate .= $tag[3];
263: $totalLength += $contentLength;
264: }
265: if ($totalLength >= $length) {
266: break;
267: }
268: }
269: } else {
270: if (mb_strlen($text) <= $length) {
271: return $text;
272: } else {
273: $truncate = mb_substr($text, 0, $length - mb_strlen($ending));
274: }
275: }
276: if (!$exact) {
277: $spacepos = mb_strrpos($truncate, ' ');
278: if ($html) {
279: $truncateCheck = mb_substr($truncate, 0, $spacepos);
280: $lastOpenTag = mb_strrpos($truncateCheck, '<');
281: $lastCloseTag = mb_strrpos($truncateCheck, '>');
282: if ($lastOpenTag > $lastCloseTag) {
283: preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches);
284: $lastTag = array_pop($lastTagMatches[0]);
285: $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag);
286: }
287: $bits = mb_substr($truncate, $spacepos);
288: preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
289: if (!empty($droppedTags)) {
290: if (!empty($openTags)) {
291: foreach ($droppedTags as $closingTag) {
292: if (!in_array($closingTag[1], $openTags)) {
293: array_unshift($openTags, $closingTag[1]);
294: }
295: }
296: } else {
297: foreach ($droppedTags as $closingTag) {
298: array_push($openTags, $closingTag[1]);
299: }
300: }
301: }
302: }
303: $truncate = mb_substr($truncate, 0, $spacepos);
304: }
305: $truncate .= $ending;
306:
307: if ($html) {
308: foreach ($openTags as $tag) {
309: $truncate .= '</' . $tag . '>';
310: }
311: }
312:
313: return $truncate;
314: }
315:
316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326:
327: public function excerpt($text, $phrase, $radius = 100, $ending = '...') {
328: if (empty($text) or empty($phrase)) {
329: return $this->truncate($text, $radius * 2, array('ending' => $ending));
330: }
331:
332: $append = $prepend = $ending;
333:
334: $phraseLen = mb_strlen($phrase);
335: $textLen = mb_strlen($text);
336:
337: $pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase));
338: if ($pos === false) {
339: return mb_substr($text, 0, $radius) . $ending;
340: }
341:
342: $startPos = $pos - $radius;
343: if ($startPos <= 0) {
344: $startPos = 0;
345: $prepend = '';
346: }
347:
348: $endPos = $pos + $phraseLen + $radius;
349: if ($endPos >= $textLen) {
350: $endPos = $textLen;
351: $append = '';
352: }
353:
354: $excerpt = mb_substr($text, $startPos, $endPos - $startPos);
355: $excerpt = $prepend . $excerpt . $append;
356:
357: return $excerpt;
358: }
359:
360: 361: 362: 363: 364: 365: 366: 367: 368:
369: public function toList($list, $and = 'and', $separator = ', ') {
370: if (count($list) > 1) {
371: return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list);
372: } else {
373: return array_pop($list);
374: }
375: }
376: }
377: