2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
14 * @author Jose Salavert <salavert a.t akelos c.om>
15 * @author Bermi Ferrer <bermi a.t akelos c.om>
16 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
17 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
20 require_once(AK_VENDOR_DIR
.DS
.'phputf8'.DS
.'utf8.php');
22 defined('AK_VALID_URL_CHARS_REGEX') ?
null : define('AK_VALID_URL_CHARS_REGEX','A-Z-a-z0-9:=?&\/\.\-\\%~#_;,+');
23 define('AK_AUTO_LINK_REGEX','/
25 <\w+.*?>| # leading HTML tag, or
26 [^=!:\'"\/]| # leading punctuation, or
30 (?:https?:\/\/)| # protocol spec, or
34 [-\w]+ # subdomain or domain
35 (?:\.[-\w]+)* # remaining subdomains or domain
37 (?:\/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path
38 (?:\?[\w\+%&=.;-]+)? # query string
39 (?:\#[\w\-]*)? # trailing anchor
41 ([[:punct:]]|\s|<|$) # trailing text
45 * Provides a set of methods for working with text strings that can help unburden
46 * the level of inline AkelosFramework code in the templates. In the example
47 * below we iterate over a collection of posts provided to the template and print
48 * each title after making sure it doesn't run longer than 20 characters:
50 * Title: <?= $text_helper->truncate($post->title, 20) ?>
56 function setController(&$controller)
58 $this->_controller
=& $controller;
63 TextHelper
::cycle(array('reset'=>'all'));
68 * Truncates "$text" to the length of "length" and replaces the last three
69 * characters with the "$truncate_string" if the "$text" is longer than
70 * "$length" and the last characters will be replaced with the +truncate_string+.
71 * If +break+ is specified and if it's present in +text+ and if its position is
72 * lesser than +length+, then the truncated +text+ will be limited to +break+.
75 function truncate($text, $length = 30, $truncate_string = '...', $break = false)
77 if(utf8_strlen($text) <= $length){
81 if (false !== ($breakpoint = (empty($break) ?
$length : utf8_strpos($text, $break))) && ($breakpoint >= utf8_strlen($truncate_string)))
83 if ($breakpoint > $length)
85 $breakpoint = $length;
87 return utf8_substr($text, 0, $breakpoint - utf8_strlen($truncate_string)) . $truncate_string;
93 * Highlights the string or array of strings "$phrase" where it is found in
94 * the "$text" by surrounding it like
95 * <strong class="highlight">I'm a highlight phrase</strong>.
97 * The highlighter can be specialized by passing "$highlighter" as string
98 * with \1 where the phrase is supposed to be inserted.
100 * Note: The "$phrase" is sanitized with preg_quote before use.
104 * <?=$text_helper->highlight('I am highlighting the phrase','highlighting');?>
105 * //outputs: I am <strong class="highlight">highlighting</strong> the phrase
107 * <?=$text_helper->highlight('I am highlighting the phrase',
108 * array('highlighting','the')?>
109 * //outputs: I am <strong class="highlight">highlighting</strong> <strong class="highlight">the</strong> phrase
112 function highlight($text, $phrase, $highlighter = '<strong class="highlight">\1</strong>')
114 $phrase = is_array($phrase) ?
join('|',array_map('preg_quote',$phrase)) : preg_quote($phrase);
115 return !empty($phrase) ?
preg_replace('/('.$phrase.')/iu', $highlighter, $text) : $text;
119 * Extracts an excerpt from the "$text" surrounding the "$phrase" with a
120 * number of characters on each side determined by "$radius". If the phrase
121 * isn't found, '' is returned.
125 * <?=$text_helper->excerpt("hello my world", "my", 3);?>
126 * //outputs: ...lo my wo...
129 function excerpt($text, $phrase, $radius = 100, $excerpt_string = '...')
134 $phrase = preg_quote($phrase);
136 if(preg_match('/('.$phrase.')/iu',$text, $found)){
137 $found_pos = utf8_strpos($text, $found[0]);
138 $start_pos = max($found_pos - $radius, 0);
139 $end_pos = min($found_pos +
utf8_strlen($phrase) +
$radius, utf8_strlen($text));
141 $prefix = $start_pos > 0 ?
$excerpt_string : '';
142 $postfix = $end_pos < utf8_strlen($text) ?
$excerpt_string : '';
144 return $prefix.trim(utf8_substr($text,$start_pos,$end_pos-$start_pos)).$postfix;
150 * Attempts to pluralize the "$singular" word unless "$count" is 1.
152 function pluralize($count, $singular, $plural = null)
156 }elseif (!empty($plural)){
159 return AkInflector
::conditionalPlural($count, $singular);
164 * Word wrap long lines to line_width.
166 function word_wrap($text, $line_width = 80, $break = "\n")
168 // No need to use an UTF-8 wordwrap function as we are using the default cut character.
169 return trim(wordwrap($text, $line_width, $break));
173 * Like word wrap but allows defining text indenting and boby indenting
175 function format($text, $options = array())
177 $default_options = array(
184 $options = array_merge($default_options, $options);
186 $text = empty($text) && !empty($options['text']) ?
$options['text'] : $text;
190 $text = str_replace(array("\r","\t",' ',' '),array("\n",' ',' ',' '),$text);
192 foreach(explode("\n",$text."\n") as $paragraph) {
193 if(empty($paragraph)) {
196 $paragraph = ($options['first_indent'] > 0 ?
str_repeat(' ',$options['first_indent']) : '' ).$paragraph;
197 $paragraph = wordwrap($paragraph, $options['columns']-$options['body_indent'], "\n", $options['cut_words']);
199 if($options['body_indent'] > 0) {
200 $paragraph = preg_replace('!^!m',str_repeat(' ',$options['body_indent']),$paragraph);
202 $formated_text .= $paragraph . "\n\n";
204 return $formated_text;
209 * Returns the "$text" with all the Textile codes turned into HTML-tags.
211 function textilize($text, $options = array())
213 $default_options = array(
214 'extended_mode' => true,
215 'allow_images' => true,
218 $options = array_merge($default_options, $options);
219 require_once(AK_VENDOR_DIR
.DS
.'TextParsers'.DS
.'Textile.php');
221 $Textile = new Textile();
222 $text = trim($Textile->TextileThis($text, !$options['extended_mode'], '', !$options['allow_images'], true, $options['rel']));
228 * Returns the "$text" with all the Textile codes turned into HTML-tags, but
229 * without the regular bounding <p> tag.
231 function textilize_without_paragraph($text, $options = array())
233 return preg_replace('/^<p([A-Za-z0-9& ;\-=",\/:\.\']+)?>(.+)<\/p>$/u','\2', TextHelper
::textilize($text, $options));
237 * Returns "$text" transformed into HTML using very simple formatting rules
238 * Surrounds paragraphs with <tt><p></tt> tags, and converts line
239 * breaks into <tt><br /></tt> Two consecutive newlines(<tt>\n\n</tt>)
240 * are considered as a paragraph, one newline (<tt>\n</tt>) is considered a
241 * linebreak, three or more consecutive newlines are turned into two newlines
243 function simple_format($text)
246 '/(\\r\\n|\\r)/'=> "\n",
247 '/(\\n\\n+)/' => "\n\n",
248 '/(\\n\\n)/' => "</p><p>",
249 '/([^\\n])(\\n)([^\\n])/' => "\1\2<br />\3"
251 $text = preg_replace(array_keys($rules), array_values($rules), $text);
252 $text = TagHelper
::content_tag('p',$text);
253 return str_replace(array('<p></p>','</p><p>'),array('<br /><br />',"</p>\n<p>"),$text);
257 * Turns all urls and email addresses into clickable links. The "$link"
258 * parameter can limit what should be linked.
260 * Options are "all" (default), "email_addresses", and "urls".
264 * <?=$text_helper->auto_link("Go to http://www.akelos.org and say hello to bermi@example.com");?>
265 * //outputs: Go to <a href="http://www.akelos.org">http://www.akelos.org</a> and
266 * say hello to <a href="mailto:example.com">bermi@example.com</a>
269 function auto_link($text, $link = 'all', $href_options = array(), $email_link_options = array())
277 return TextHelper
::auto_link_urls(
278 TextHelper
::auto_link_email_addresses($text, $email_link_options),
282 case 'email_addresses':
283 return TextHelper
::auto_link_email_addresses($text, $email_link_options);
287 return TextHelper
::auto_link_urls($text, $href_options);
291 return TextHelper
::auto_link($text, 'all', $href_options);
298 * Strips all HTML tags from the input, including comments.
300 * Returns the tag free text.
302 function strip_tags($html)
304 return strip_tags($html);
308 * Turns all links into words, like "<a href="something">else</a>" to "else".
310 function strip_links($text)
312 return TextHelper
::strip_selected_tags($text, 'a');
316 * Turns all email addresses into clickable links. You can provide an options
317 * array in order to generate links using UrlHelper::mail_to()
320 * $text_helper->auto_link_email_addresses($post->body);
322 function auto_link_email_addresses($text, $email_options = array())
324 if(empty($email_options)){
325 return preg_replace('/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/',
326 "<a href='mailto:$1'>$1</a>",$text);
327 }elseif(preg_match_all('/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/',$text, $match)){
329 foreach ($emails as $email){
330 $encoded_emails[] = UrlHelper
::mail_to($email, null, $email_options);
332 $text = str_replace($emails,$encoded_emails,$text);
338 * Works like PHP function strip_tags, but it only removes selected tags.
340 * <?=$text_helper->strip_selected_tags(
341 * '<b>Person:</b> <strong>Salavert</strong>', 'strong');?>
342 * //outputs: <b>Person:</b> Salavert
344 function strip_selected_tags($text, $tags = array())
346 $args = func_get_args();
347 $text = array_shift($args);
348 $tags = func_num_args() > 2 ?
array_diff($args,array($text)) : (array)$tags;
349 foreach ($tags as $tag){
350 if(preg_match_all('/<'.$tag.'[^>]*>([^<]*)<\/'.$tag.'>/iU', $text, $found)){
351 $text = str_replace($found[0],$found[1],$text);
355 return preg_replace('/(<('.join('|',$tags).')(\\n|\\r|.)*\/>)/iU', '', $text);
360 * Turns all urls into clickable links.
363 * <?=$text_helper->auto_link_urls($post->body, array('all', 'target' => '_blank'));?>
365 function auto_link_urls($text, $href_options = array())
367 $extra_options = TagHelper
::_tag_options($href_options);
368 $extra_options_array = var_export($extra_options,true);
369 return preg_replace_callback(AK_AUTO_LINK_REGEX
, create_function(
371 'return TextHelper::_replace_url_with_link_callback($matched,'.$extra_options_array.');'
375 function _replace_url_with_link_callback($matched, $extra_options)
377 list($all, $a, $b, $c, $d) = $matched;
378 if (preg_match('/<a\s/i',$a)){ // don't replace URL's that are already linked
382 return $a.'<a href="'.($b=="www."?
"http://www.":$b).$c.'"'.$extra_options.'>'.$text.'</a>'.$d;
387 * Returns an array with all the urls found as key and their valid link url as value
390 * $text_helper->get_urls_from_text('www.akelos.com');
391 * //returns: array('www.akelos.com'=>'http://www.akelos.com');
393 function get_urls_from_text($text)
396 if(preg_match_all(AK_AUTO_LINK_REGEX
, $text, $found_urls)){
397 foreach ($found_urls[0] as $url){
398 $urls[$url] = (strtolower(substr($url,0,4)) == 'http' ?
$url : 'http://'.$url);
405 * Returns an array with the linked urls found on a text
408 * $text_helper->get_linked_urls_from_text('<a href="http://akelos.com">Akelos.com</a>');
409 * //returns: array('http://akelos.com');
411 function get_linked_urls_from_text($text)
413 $linked_urls = array();
414 if(preg_match_all('/<a [^>]*href[ ]?=[ \'"]?(['.AK_VALID_URL_CHARS_REGEX
.']+)[ \'"]?[^>]*>/i', $text, $linked_urls_pieces)){
415 $linked_urls = array_unique($linked_urls_pieces[1]);
422 * Returns an array with the image urls found on a text
425 * $text_helper->get_linked_urls_from_text('<a href="http://akelos.com">Akelos.com</a>');
426 * //returns: array('http://akelos.com/images/logo.gif');
428 function get_image_urls_from_html($text)
430 $linked_urls = array();
431 if(preg_match_all('/<img [^>]*src[ ]?=[ \'"]?(['.AK_VALID_URL_CHARS_REGEX
.']+)[ \'"]?[^>]*>/i', $text, $linked_urls_pieces)){
432 $linked_urls = array_unique($linked_urls_pieces[1]);
439 * Cycles through items of an array every time it is called.
440 * This can be used to alternate classes for table rows:
443 * <tr class="<?=$text_helper->cycle("even", "odd")?>">
448 * You can use named cycles to prevent clashes in nested loops. You'll
449 * have to reset the inner cycle, manually:
452 * <tr class="<?=$text_helper->cycle("even", "odd", array('name' => "row_class"))?>
455 * <span style="color:'<?=$text_helper->cycle("red", "green", "blue",array('name' => "colors"))?>'">
459 * <php $text_helper->reset_cycle("colors"); ?>
464 function cycle($first_value, $values = null)
466 static $cycle_position;
467 $params = func_get_args();
468 if(is_array($params[func_num_args()-1])){
469 $options = array_pop($params);
470 $name = isset($options['name']) ?
$options['name'] : 'default';
475 if(isset($options['reset'])){
476 $cycle_position[$options['reset']] = 0;
477 if($options['reset'] == 'all'){
478 $cycle_position = array();
482 $cycle_position[$name] = !isset($cycle_position[$name]) ?
0 : $cycle_position[$name];
483 $number_params = count($params);
484 $current_param = $cycle_position[$name] > $number_params-1 ?
0 : $cycle_position[$name];
485 $cycle_position[$name] = $current_param+
1;
486 return $params[$current_param];
489 function reset_cycle($name)
491 TextHelper
::cycle(array('reset'=>$name));
496 * Returns the text with all the Markdown codes turned into HTML-tags.
498 function markdown($text)
501 require_once(AK_VENDOR_DIR
.DS
.'TextParsers'.DS
.'markdown.php');
502 $text = trim(Markdown($text));
509 * Gets a localized setting (date format,...).
511 function locale($locale_setting, $locale = null)
513 return Ak
::locale($locale_setting, $locale);
517 * Translate strings to the current locale.
519 function translate($string, $args = null, $locale_namespace = null)
521 return Ak
::t($string, $args, empty($locale_namespace) ?
522 AkInflector
::underscore($this->_controller
->getControllerName()) : $locale_namespace);
526 * Alias for translate
528 function t($string, $args = null, $locale_namespace = null)
530 return TextHelper
::translate($string, $args, $locale_namespace);
534 function humanize($text)
536 return AkInflector
::humanize($text);
540 * Converts an underscored or CamelCase word into a English
543 * The titleize function converts text like "WelcomePage",
544 * "welcome_page" or "welcome page" to this "Welcome
546 * If second parameter is set to 'first' it will only
547 * capitalize the first character of the title.
551 * @param string $word Word to format as tile
552 * @param string $uppercase If set to 'first' it will only uppercase the
553 * first character. Otherwise it will uppercase all
554 * the words in the title.
555 * @return string Text formatted as title
557 function titleize($text, $uppercase = '')
559 return AkInflector
::titleize($text, $uppercase);
563 * Use this function to automatically handle flash messages.
567 * <?=$text_helper->flash();?>
568 * //will handle all flash messages automatically
570 * <?=$text_helper->flash(null,array('secconds_to_close'=>5));?>
571 * //will handle all flash messages automatically and will close in 5 secconds. NOTE. you need to include javascript dependencies for using interactive options
574 function flash($message = null, $options = array(), $html_options = array())
576 if(empty($message) && empty($this->_controller
->flash
)){
580 $options = empty($options) ?
(empty($this->_controller
->flash_options
) ?
array() : $this->_controller
->flash_options
) : $options;
582 $default_options = array(
583 'close_button' => false,
584 'seconds_to_close' => false,
590 $options = array_merge($default_options, $options);
591 if(empty($options['seconds_to_close']) && isset($options['close_in'])){
592 $options['seconds_to_close'] = strtotime($options['close_in'])-time();
595 $options['effects'] = empty($options['effects']) ?
array() : $options['effects'];
596 $effects = !empty($options['effect']) && is_string($options['effect']) ?
array_merge(array($options['effect']), $options['effects']) : $options['effects'];
598 $options['seconds_to_close'] = empty($options['seconds_to_close']) && !empty($options['seconds']) ?
$options['seconds'] : $options['seconds_to_close'];
600 $html_options = array_merge(array('id'=>'flash','class'=>'flash'), $html_options);
602 $close_button = !empty($options['close_button']) ?
$this->_controller
->asset_tag_helper
->image_tag($options['close_button']).' ' : '';
606 foreach ($this->_controller
->flash
as $k=>$v){
607 if(is_string($v) && !empty($v)){
608 $message .= TagHelper
::content_tag('div', $v, array('id'=>'flash_'.$k));
611 }elseif (is_array($message)){
613 foreach ($this->_controller
->flash
as $k=>$v){
614 if(is_string($v) && !empty($v)){
615 $message .= TagHelper
::content_tag('div', $v, array('id'=>'flash_'.$k));
623 $flash_message = TagHelper
::content_tag('div', $close_button.$message,$html_options);
625 if ($options['animate']) {
626 $animation_effects = '';
627 if(!empty($effects)){
628 foreach ($effects as $name=>$effect_options){
629 if(is_numeric($name)){
630 $animation_effects .= $this->_controller
->scriptaculous_helper
->visual_effect($effect_options, $html_options['id']);
632 $animation_effects .= $this->_controller
->scriptaculous_helper
->visual_effect($name, $html_options['id'], $effect_options);
636 if (!empty($options['seconds_to_close'])) {
637 $animation_effects .= 'setTimeout(\'new Effect.Fade($("'.$html_options['id'].'"));\', '.($options['seconds_to_close']*1000).');';
639 if(!empty($animation_effects)){
640 $flash_message .= $this->_controller
->javascript_helper
->javascript_tag($animation_effects);
642 }elseif (!empty($options['seconds_to_close'])) {
643 $flash_message .= $this->_controller
->javascript_helper
->javascript_tag('setTimeout(\'$("'.$html_options['id'].'").hide();\', '.($options['seconds_to_close']*1000).');');
646 return $flash_message;
650 * Recodes "$text" into utf-8 from "$input_string_encoding"
652 function utf8($text, $input_string_encoding = null)
654 return Ak
::utf8($text, $input_string_encoding);
659 * Will atempt to close unmatched tags. This is useful for truncating messages
660 * and not breaking the layout.
662 function close_unmatched_html($html)
664 preg_match_all('/<(\w+)[^>\/]*?(?!\/)>/', $html, $start_tags);
665 preg_match_all('/<\/(\w+)[^>\/]*?>/', $html, $end_tags);
667 $start_tags = (array)@$start_tags[1];
668 $end_tags = (array)@$end_tags[1];
670 if(count($start_tags) == count($end_tags)){
673 $missing_tags = array_reverse(array_diff($start_tags, $end_tags));
675 foreach ($missing_tags as $missing_tag){
676 if(!in_array($missing_tag,array('hr','br','img','input',''))){
677 $html .= "</{$missing_tag}>";
683 function html_escape($html)
687 $charset = Ak
::locale('charset');
689 return htmlentities($html, ENT_COMPAT
, $charset);
694 return TextHelper
::html_escape($html);