• Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • Examples
  • File List

system/classes/format.php

00001 <?php
00007 namespace Habari;
00008 
00015 class Format
00016 {
00017   private static $formatters = null;
00018 
00024   public static function apply( $format, $onwhat )
00025   {
00026     if ( self::$formatters == null ) {
00027       self::load_all();
00028     }
00029 
00030     $priority = 8;
00031     if(preg_match('#^(.+)_(\d+)$#', $onwhat, $matches)) {
00032       $priority = intval($matches[2]);
00033       $onwhat = $matches[1];
00034     }
00035 
00036     $method = false;
00037     if (is_callable($format)) {
00038       $method = $format;
00039     }
00040     else {
00041       foreach ( self::$formatters as $formatobj ) {
00042         if ( method_exists( $formatobj, $format ) ) {
00043           $method = array($formatobj, $format);
00044           break;
00045         }
00046       }
00047     }
00048 
00049     if($method) {
00050       $args = func_get_args();
00051       $args = array_slice($args, 2);
00052       $lambda = function() use ($args, $method) {
00053         $filterargs = func_get_args();
00054         $filterargs = array_slice($filterargs, 0, 1);
00055         foreach($args as $arg) {
00056           $filterargs[] = $arg;
00057         }
00058         return call_user_func_array($method, $filterargs);
00059       };
00060       Plugins::register( $lambda, 'filter', $onwhat, $priority );
00061     }
00062   }
00063 
00069   public static function apply_with_hook_params( $format, $onwhat )
00070   {
00071     if ( self::$formatters == null ) {
00072       self::load_all();
00073     }
00074 
00075     $priority = 8;
00076     if(preg_match('#^(.+)_(\d+)$#', $onwhat, $matches)) {
00077       $priority = intval($matches[2]);
00078       $onwhat = $matches[1];
00079     }
00080 
00081     $method = false;
00082     if (is_callable($format)) {
00083       $method = $format;
00084     }
00085     else {
00086       foreach ( self::$formatters as $formatobj ) {
00087         if ( method_exists( $formatobj, $format ) ) {
00088           $method = array($formatobj, $format);
00089           break;
00090         }
00091       }
00092     }
00093 
00094     if($method) {
00095       $args = func_get_args();
00096       $args = array_slice($args, 2);
00097       $lambda = function() use ($args, $method) {
00098         $filterargs = func_get_args();
00099         //$filterargs = array_slice($filterargs, 0, 1);
00100         foreach($args as $arg) {
00101           $filterargs[] = $arg;
00102         }
00103         return call_user_func_array($method, $filterargs);
00104       };
00105       Plugins::register( $lambda, 'filter', $onwhat );
00106     }
00107   }
00108 
00116   public static function by_index( $index )
00117   {
00118     return self::$formatters[$index];
00119   }
00120 
00125   public static function load_all()
00126   {
00127     self::$formatters = array();
00128     $classes = get_declared_classes();
00129     foreach ( $classes as $class ) {
00130       if ( $class == __CLASS__ || get_parent_class( $class ) == __CLASS__ ) {
00131         self::$formatters[] = new $class();
00132       }
00133     }
00134     self::$formatters = array_merge( self::$formatters, Plugins::get_by_interface( 'FormatPlugin' ) );
00135     self::$formatters = array_reverse( self::$formatters, true );
00136   }
00137 
00153   public static function autop( $value )
00154   {
00155     $value = str_replace( "\r\n", "\n", $value );
00156     $value = trim( $value );
00157     $ht = new HtmlTokenizer( $value, false );
00158     $set = $ht->parse();
00159     $value = '';
00160 
00161     // should never autop ANY content in these items
00162     $no_auto_p = array(
00163       'pre','code','ul','h1','h2','h3','h4','h5','h6',
00164       'object','applet','embed',
00165       'table','ul','ol','li','i','b','em','strong','script', 'dl', 'dt', 'dd'
00166     );
00167 
00168     $block_elements = array(
00169       'address','blockquote','center','dir','div','dl','fieldset','form',
00170       'h1','h2','h3','h4','h5','h6','hr','isindex','menu','noframes',
00171       'object','applet','embed',
00172       'noscript','ol','p','pre','table','ul','figure','figcaption'
00173     );
00174 
00175     $token = $set->current();
00176 
00177     // There are no tokens in the text being formatted
00178     if ( $token === false ) {
00179       return $value;
00180     }
00181 
00182     $open_p = false;
00183     do {
00184 
00185       if ( $open_p ) {
00186         if ( ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) && in_array( strtolower( $token['name'] ), $block_elements ) ) {
00187           if ( strtolower( $token['name'] ) != 'p' || $token['type'] != HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) {
00188             $value .= '</p>';
00189           }
00190           $open_p = false;
00191         }
00192       }
00193 
00194       if ( ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY ) && !in_array( strtolower( $token['name'] ), $block_elements ) && !$open_p ) {
00195         // first element, is not a block element
00196         $value .= '<p>';
00197         $open_p = true;
00198       }
00199 
00200       // no-autop, pass them through verbatim
00201       if ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN && in_array( strtolower( $token['name'] ), $no_auto_p ) ) {
00202         $nested_token = $token;
00203         do {
00204           $value .= HtmlTokenSet::token_to_string( $nested_token, false );
00205           if (
00206             ( $nested_token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE
00207               && strtolower( $nested_token['name'] ) == strtolower( $token['name'] ) ) // found closing element
00208           ) {
00209             break;
00210           }
00211         } while ( $nested_token = $set->next() );
00212         continue;
00213       }
00214 
00215       // anything that's not a text node should get passed through
00216       if ( $token['type'] != HTMLTokenizer::NODE_TYPE_TEXT ) {
00217         $value .= HtmlTokenSet::token_to_string( $token, true );
00218         // If the token itself is p, we need to set $open_p
00219         if ( strtolower( $token['name'] ) == 'p' && $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
00220           $open_p = true;
00221         }
00222         continue;
00223       }
00224 
00225       // if we get this far, token type is text
00226       $local_value = $token['value'];
00227       if ( MultiByte::strlen( $local_value ) ) {
00228         if ( !$open_p ) {
00229           $local_value = '<p>' . ltrim( $local_value );
00230           $open_p = true;
00231         }
00232 
00233         $local_value = preg_replace( '/\s*(\n\s*){2,}/u', "</p><p>", $local_value ); // at least two \n in a row (allow whitespace in between)
00234         $local_value = str_replace( "\n", "<br>", $local_value ); // nl2br
00235       }
00236       $value .= $local_value;
00237     } while ( $token = $set->next() );
00238 
00239     $value = preg_replace( '#\s*<p></p>\s*#u', '', $value ); // replace <p></p>
00240     $value = preg_replace( '/<p><!--(.*?)--><\/p>/', "<!--\\1-->", $value ); // replace <p></p> around comments
00241     if ( $open_p ) {
00242       $value .= '</p>';
00243     }
00244 
00245     return $value;
00246   }
00247 
00256   public static function and_list( $array, $between = ', ', $between_last = null )
00257   {
00258     if ( ! is_array( $array ) ) {
00259       $array = array( $array );
00260     }
00261 
00262     if ( $between_last === null ) {
00263       // @locale The default string used between the last two items in a series (one, two, three *and* four).
00264       $between_last = _t( ' and ' );
00265     }
00266 
00267     $last = array_pop( $array );
00268     $out = implode( $between, $array );
00269     $out .= ($out == '') ? $last : $between_last . $last;
00270     return $out;
00271   }
00272 
00283   public static function tag_and_list( $terms, $between = ', ', $between_last = null, $sort_alphabetical = false )
00284   {
00285     $array = array();
00286     if ( !$terms instanceof Terms ) {
00287       $terms = new Terms( $terms );
00288     }
00289 
00290     foreach ( $terms as $term ) {
00291       $array[$term->term] = $term->term_display;
00292     }
00293 
00294     if ( $sort_alphabetical ) {
00295       ksort( $array );
00296     }
00297 
00298     if ( $between_last === null ) {
00299       // @locale The default string used between the last two items in a series of tags (one, two, three *and* four).
00300       $between_last = _t( ' and ' );
00301     }
00302 
00303     $fn = function($a, $b) {
00304       return "<a href=\"" . URL::get("display_entries_by_tag", array( "tag" => $b) ) . "\" rel=\"tag\">" . $a . "</a>";
00305     };
00306     $array = array_map( $fn, $array, array_keys( $array ) );
00307     $last = array_pop( $array );
00308     $out = implode( $between, $array );
00309     $out .= ( $out == '' ) ? $last : $between_last . $last;
00310     return $out;
00311 
00312   }
00313 
00326   public static function format_date( $date, $format )
00327   {
00328     if ( !( $date instanceOf DateTime ) ) {
00329       $date = DateTime::create( $date );
00330     }
00331     return $date->text_format( $format );
00332   }
00333 
00341   public static function nice_date( $date, $dateformat = 'F j, Y' )
00342   {
00343     if ( !( $date instanceOf DateTime ) ) {
00344       $date = DateTime::create( $date );
00345     }
00346     return $date->format( $dateformat );
00347   }
00348 
00356   public static function nice_time( $date, $dateformat = 'H:i:s' )
00357   {
00358     if ( !( $date instanceOf DateTime ) ) {
00359       $date = DateTime::create( $date );
00360     }
00361     return $date->format( $dateformat );
00362   }
00363 
00371   public static function summarize( $text, $count = 100, $max_paragraphs = 1 )
00372   {
00373     $ellipsis = '&hellip;';
00374 
00375     $showmore = false;
00376 
00377     $ht = new HtmlTokenizer($text, false);
00378     $set = $ht->parse();
00379 
00380     $stack = array();
00381     $para = 0;
00382     $token = $set->current();
00383     $summary = new HTMLTokenSet();
00384     $set->rewind();
00385     $remaining_words = $count;
00386     // $bail lets the loop end naturally and close all open elements without adding new ones.
00387     $bail = false;
00388     for ( $token = $set->current(); $set->valid(); $token = $set->next() ) {
00389       if ( !$bail && $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
00390         $stack[] = $token;
00391       }
00392       if ( !$bail ) {
00393         switch ( $token['type'] ) {
00394           case HTMLTokenizer::NODE_TYPE_TEXT:
00395             $words = preg_split( '/(\\s+)/u', $token['value'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
00396             // word count is doubled because spaces between words are captured as their own array elements via PREG_SPLIT_DELIM_CAPTURE
00397             $words = array_slice( $words, 0, $remaining_words * 2 );
00398             $remaining_words -= count( $words ) / 2;
00399             $token['value'] = implode( '', $words );
00400             if ( $remaining_words <= 0 ) {
00401               $token['value'] .= $ellipsis;
00402               $summary[] = $token;
00403               $bail = true;
00404             }
00405             else {
00406               $summary[] = $token;
00407             }
00408             break;
00409           case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE;
00410             // don't handle this case here
00411             break;
00412           default:
00413             $summary[] = $token;
00414             break;
00415         }
00416       }
00417       if ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) {
00418         if(count($stack) > 0 && in_array($token['name'], Utils::array_map_field($stack, 'name'))) {
00419           do {
00420             $end = array_pop( $stack );
00421             $end['type'] = HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE;
00422             $end['attrs'] = null;
00423             $end['value'] = null;
00424             $summary[] = $end;
00425           } while ( ( $bail || $end['name'] != $token['name'] ) && count( $stack ) > 0 );
00426         }
00427         else {
00428           $end['name'] = $token['name'];
00429           $end['type'] = HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE;
00430           $end['attrs'] = null;
00431           $end['value'] = null;
00432         }
00433         if ( count( $stack ) == 0 ) {
00434           $para++;
00435         }
00436         if ( $bail || $para >= $max_paragraphs ) {
00437           break;
00438         }
00439       }
00440     }
00441 
00442     return (string) $summary;
00443   }
00444 
00459   public static function more( $content, $post, $properties = array() )
00460   {
00461     // If the post requested is the post under consideration, always return the full post
00462     if ( $post->slug == Controller::get_var( 'slug' ) ) {
00463       return $content;
00464     }
00465     elseif ( is_string( $properties ) ) {
00466       $args = func_get_args();
00467       $more_text = $properties;
00468       $max_words = ( isset( $args[3] ) ? $args[3] : null );
00469       $max_paragraphs = ( isset( $args[4] ) ? $args[4] : null );
00470       $inside_last = ( isset( $args[5] ) ? $args[5] : true );
00471       $paramstring = "";
00472     }
00473     else {
00474       $paramstring = "";
00475       $paramarray = Utils::get_params( $properties );
00476 
00477       $more_text = ( isset( $paramarray['more_text'] ) ? $paramarray['more_text'] : 'Read More' );
00478       $max_words = ( isset( $paramarray['max_words'] ) ? $paramarray['max_words'] : null );
00479       $max_paragraphs = ( isset( $paramarray['max_paragraphs'] ) ? $paramarray['max_paragraphs'] : null );
00480       $inside_last = ( isset( $paramarray['inside_last'] ) ? $paramarray['inside_last'] : true );
00481 
00482       if ( isset( $paramarray['title:before'] ) || isset( $paramarray['title'] ) || isset( $paramarray['title:after'] ) ) {
00483         $paramstring .= 'title="';
00484 
00485         if ( isset( $paramarray['title:before'] ) ) {
00486           $paramstring .= $paramarray['title:before'];
00487         }
00488         if ( isset( $paramarray['title'] ) ) {
00489           $paramstring .= $post->title;
00490         }
00491         if ( isset( $paramarray['title:after'] ) ) {
00492           $paramstring .= $paramarray['title:after'];
00493         }
00494         $paramstring .= '" ';
00495       }
00496       if ( isset( $paramarray['class'] ) ) {
00497         $paramstring .= 'class="' . $paramarray['class'] . '" ';
00498       }
00499 
00500     }
00501 
00502     $link_text = '<a ' . $paramstring . ' href="' . $post->permalink . '">' . $more_text . '</a>';
00503 
00504     // if we want it inside the last element, make sure there's a space before the link
00505     if ( $inside_last ) {
00506       $link_text = ' ' . $link_text;
00507     }
00508 
00509     // check for a <!--more--> link, which sets exactly where we should split
00510     $matches = preg_split( '/<!--\s*more\s*-->/isu', $content, 2, PREG_SPLIT_NO_EMPTY );
00511     if ( count( $matches ) > 1 ) {
00512       $summary = reset( $matches );
00513     }
00514     else {
00515       // otherwise, we need to summarize it automagically
00516       $max_words = empty( $max_words ) ? 9999999 : intval( $max_words );
00517       $max_paragraphs = empty( $max_paragraphs ) ? 9999999 : intval( $max_paragraphs );
00518       $summary = Format::summarize( $content, $max_words, $max_paragraphs );
00519     }
00520 
00521     // if the summary is equal to the length of the content (or somehow greater??), there's no need to add a link, just return the content
00522     if ( MultiByte::strlen( $summary ) >= MultiByte::strlen( $content ) ) {
00523       return $content;
00524     }
00525     else {
00526       // make sure there's actually text to append before we waste our time
00527       if ( strlen( $more_text ) ) {
00528         // parse out the summary and stick in our linky goodness
00529 
00530         // tokenize the summary
00531         $ht = new HTMLTokenizer( $summary );
00532         $summary_set = $ht->parse();
00533 
00534         // tokenize the link we're adding
00535         $ht = new HTMLTokenizer( $link_text );
00536         $link_set = $ht->parse();
00537 
00538         // find out where to put the link by bumping the iterator to the last element
00539         $end = $summary_set->end();
00540         // and what index is that?
00541         $key = $summary_set->key();
00542 
00543         // if we want it inside the last element, we're good to go - if we want it outside, we need to add it as the *next* element
00544         if ( $inside_last == false ) {
00545           $key++;
00546         }
00547 
00548         // inject it, whereever we decided it should go
00549         $summary_set->insert( $link_set, $key );
00550 
00551         // and return a stringified version
00552         return (string)$summary_set;
00553       }
00554       else {
00555         // no text to append? just return the summary
00556         return $summary;
00557       }
00558 
00559     }
00560 
00561     return $content;
00562 
00563   }
00564 
00572   public static function html_messages( $notices, $errors )
00573   {
00574     $output = '';
00575     if ( count( $errors ) ) {
00576       $output.= '<ul class="messages error">';
00577       foreach ( $errors as $error ) {
00578         $output.= '<li>' . $error . '</li>';
00579       }
00580       $output.= '</ul>';
00581     }
00582     if ( count( $notices ) ) {
00583       $output.= '<ul class="messages success">';
00584       foreach ( $notices as $notice ) {
00585         $output.= '<li>' . $notice . '</li>';
00586       }
00587       $output.= '</ul>';
00588     }
00589 
00590     return $output;
00591   }
00592 
00600   public static function humane_messages( $notices, $errors )
00601   {
00602     $output = '';
00603     if ( count( $errors ) ) {
00604       foreach ( $errors as $error ) {
00605         $error = addslashes( $error );
00606         $output .= "human_msg.display_msg(\"{$error}\");";
00607       }
00608     }
00609     if ( count( $notices ) ) {
00610       foreach ( $notices as $notice ) {
00611         $notice = addslashes( $notice );
00612         $output .= "human_msg.display_msg(\"{$notice}\");";
00613       }
00614     }
00615 
00616     return $output;
00617   }
00618 
00626   public static function json_messages( $notices, $errors )
00627   {
00628     $messages = array_merge( $errors, $notices );
00629     return json_encode( $messages );
00630   }
00631 
00642   public static function term_tree( $terms, $tree_name, $config = array() )
00643   {
00644     $defaults = array(
00645       'treestart' => '<ol %s>',
00646       'treeattr' => array('class' => 'tree', 'id' => Utils::slugify('tree_' . $tree_name)),
00647       'treeend' => '</ol>',
00648       'liststart' => '<ol %s>',
00649       'listattr' => array(),
00650       'listend' => '</ol>',
00651       'itemstart' => '<li %s>',
00652       'itemattr' => array('class' => 'treeitem'),
00653       'itemend' => '</li>',
00654       'wrapper' => '<div>%s</div>',
00655       'linkcallback' => null,
00656       'itemcallback' => null,
00657       'listcallback' => null,
00658     );
00659     $config = array_merge($defaults, $config);
00660 
00661     $out = sprintf($config['treestart'], Utils::html_attr($config['treeattr']));
00662     $stack = array();
00663     $tree_name = Utils::slugify($tree_name);
00664 
00665     if ( !$terms instanceof Terms ) {
00666       $terms = new Terms( $terms );
00667     }
00668 
00669     foreach ( $terms as $term ) {
00670       if(count($stack)) {
00671         if($term->mptt_left - end($stack)->mptt_left == 1) {
00672           if(isset($config['listcallback'])) {
00673             $config = call_user_func($config['listcallback'], $term, $config);
00674           }
00675           $out .= sprintf($config['liststart'], Utils::html_attr($config['listattr']));
00676         }
00677         while(count($stack) && $term->mptt_left > end($stack)->mptt_right) {
00678           $out .= $config['listend'] . $config['itemend'] . "\n";
00679           array_pop($stack);
00680         }
00681       }
00682 
00683       $config['itemattr']['id'] = $tree_name . '_' . $term->id;
00684       if(isset($config['itemcallback'])) {
00685         $config = call_user_func($config['itemcallback'], $term, $config);
00686       }
00687       $out .= sprintf($config['itemstart'], Utils::html_attr($config['itemattr']));
00688       if(isset($config['linkcallback'])) {
00689         $display = call_user_func($config['linkcallback'], $term, $config);
00690       }
00691       else {
00692         $display = $term->term_display;
00693       }
00694       $out .= sprintf( $config['wrapper'], $display );
00695       if($term->mptt_right - $term->mptt_left > 1) {
00696         $stack[] = $term;
00697       }
00698       else {
00699         $out .= $config['itemend'] ."\n";
00700       }
00701     }
00702     while(count($stack)) {
00703       $out .= $config['listend'] . $config['itemend'] . "\n";
00704       array_pop($stack);
00705     }
00706 
00707     $out .= $config['treeend'];
00708     return $out;
00709   }
00710 }
00711 ?>

Generated on Sun Aug 4 2013 12:51:43 for Habari by  doxygen 1.7.1