web/lib/Zend/Markup/Renderer/Html.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Markup
       
    17  * @subpackage Renderer
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: Html.php 22286 2010-05-25 14:26:45Z matthew $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @see Zend_Filter_HtmlEntities
       
    25  */
       
    26 require_once 'Zend/Filter/HtmlEntities.php';
       
    27 /**
       
    28  * @see Zend_Filter_PregReplace
       
    29  */
       
    30 require_once 'Zend/Filter/PregReplace.php';
       
    31 /**
       
    32  * @see Zend_Filter_Callback
       
    33  */
       
    34 require_once 'Zend/Filter/Callback.php';
       
    35 /**
       
    36  * @see Zend_Markup_Renderer_RendererAbstract
       
    37  */
       
    38 require_once 'Zend/Markup/Renderer/RendererAbstract.php';
       
    39 
       
    40 /**
       
    41  * HTML renderer
       
    42  *
       
    43  * @category   Zend
       
    44  * @package    Zend_Markup
       
    45  * @subpackage Renderer
       
    46  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    47  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    48  */
       
    49 class Zend_Markup_Renderer_Html extends Zend_Markup_Renderer_RendererAbstract
       
    50 {
       
    51 
       
    52     /**
       
    53      * Element groups
       
    54      *
       
    55      * @var array
       
    56      */
       
    57     protected $_groups = array(
       
    58         'block'        => array('block', 'inline', 'block-empty', 'inline-empty', 'list'),
       
    59         'inline'       => array('inline', 'inline-empty'),
       
    60         'list'         => array('list-item'),
       
    61         'list-item'    => array('inline', 'inline-empty', 'list'),
       
    62         'block-empty'  => array(),
       
    63         'inline-empty' => array(),
       
    64     );
       
    65 
       
    66     /**
       
    67      * The current group
       
    68      *
       
    69      * @var string
       
    70      */
       
    71     protected $_group = 'block';
       
    72 
       
    73     /**
       
    74      * Default attributes
       
    75      *
       
    76      * @var array
       
    77      */
       
    78     protected static $_defaultAttributes = array(
       
    79         'id'    => '',
       
    80         'class' => '',
       
    81         'style' => '',
       
    82         'lang'  => '',
       
    83         'title' => ''
       
    84     );
       
    85 
       
    86 
       
    87     /**
       
    88      * Constructor
       
    89      *
       
    90      * @param array|Zend_Config $options
       
    91      *
       
    92      * @return void
       
    93      */
       
    94     public function __construct($options = array())
       
    95     {
       
    96         if ($options instanceof Zend_Config) {
       
    97             $options = $options->toArray();
       
    98         }
       
    99 
       
   100         $this->_pluginLoader = new Zend_Loader_PluginLoader(array(
       
   101             'Zend_Markup_Renderer_Html' => 'Zend/Markup/Renderer/Html/'
       
   102         ));
       
   103 
       
   104         if (!isset($options['useDefaultMarkups']) && isset($options['useDefaultTags'])) {
       
   105             $options['useDefaultMarkups'] = $options['useDefaultTags'];
       
   106         }
       
   107         if (isset($options['useDefaultMarkups']) && ($options['useDefaultMarkups'] !== false)) {
       
   108             $this->_defineDefaultMarkups();
       
   109         } elseif (!isset($options['useDefaultMarkups'])) {
       
   110             $this->_defineDefaultMarkups();
       
   111         }
       
   112 
       
   113         parent::__construct($options);
       
   114     }
       
   115 
       
   116     /**
       
   117      * Define the default markups
       
   118      *
       
   119      * @return void
       
   120      */
       
   121     protected function _defineDefaultMarkups()
       
   122     {
       
   123         $this->_markups = array(
       
   124             'b' => array(
       
   125                 'type'   => 10, // self::TYPE_REPLACE | self::TAG_NORMAL
       
   126                 'tag'    => 'strong',
       
   127                 'group'  => 'inline',
       
   128                 'filter' => true,
       
   129             ),
       
   130             'u' => array(
       
   131                 'type'        => 10,
       
   132                 'tag'         => 'span',
       
   133                 'attributes'  => array(
       
   134                     'style' => 'text-decoration: underline;',
       
   135                 ),
       
   136                 'group'       => 'inline',
       
   137                 'filter'      => true,
       
   138             ),
       
   139             'i' => array(
       
   140                 'type'   => 10,
       
   141                 'tag'    => 'em',
       
   142                 'group'  => 'inline',
       
   143                 'filter' => true,
       
   144             ),
       
   145             'cite' => array(
       
   146                 'type'   => 10,
       
   147                 'tag'    => 'cite',
       
   148                 'group'  => 'inline',
       
   149                 'filter' => true,
       
   150             ),
       
   151             'del' => array(
       
   152                 'type'   => 10,
       
   153                 'tag'    => 'del',
       
   154                 'group'  => 'inline',
       
   155                 'filter' => true,
       
   156             ),
       
   157             'ins' => array(
       
   158                 'type'   => 10,
       
   159                 'tag'    => 'ins',
       
   160                 'group'  => 'inline',
       
   161                 'filter' => true,
       
   162             ),
       
   163             'sub' => array(
       
   164                 'type'   => 10,
       
   165                 'tag'    => 'sub',
       
   166                 'group'  => 'inline',
       
   167                 'filter' => true,
       
   168             ),
       
   169             'sup' => array(
       
   170                 'type'   => 10,
       
   171                 'tag'    => 'sup',
       
   172                 'group'  => 'inline',
       
   173                 'filter' => true,
       
   174             ),
       
   175             'span' => array(
       
   176                 'type'   => 10,
       
   177                 'tag'    => 'span',
       
   178                 'group'  => 'inline',
       
   179                 'filter' => true,
       
   180             ),
       
   181             'acronym'  => array(
       
   182                 'type'   => 10,
       
   183                 'tag'    => 'acronym',
       
   184                 'group'  => 'inline',
       
   185                 'filter' => true,
       
   186             ),
       
   187             // headings
       
   188             'h1' => array(
       
   189                 'type'   => 10,
       
   190                 'tag'    => 'h1',
       
   191                 'group'  => 'inline',
       
   192                 'filter' => true,
       
   193             ),
       
   194             'h2' => array(
       
   195                 'type'   => 10,
       
   196                 'tag'    => 'h2',
       
   197                 'group'  => 'inline',
       
   198                 'filter' => true,
       
   199             ),
       
   200             'h3' => array(
       
   201                 'type'   => 10,
       
   202                 'tag'    => 'h3',
       
   203                 'group'  => 'inline',
       
   204                 'filter' => true,
       
   205             ),
       
   206             'h4' => array(
       
   207                 'type'   => 10,
       
   208                 'tag'    => 'h4',
       
   209                 'group'  => 'inline',
       
   210                 'filter' => true,
       
   211             ),
       
   212             'h5' => array(
       
   213                 'type'   => 10,
       
   214                 'tag'    => 'h5',
       
   215                 'group'  => 'inline',
       
   216                 'filter' => true,
       
   217             ),
       
   218             'h6' => array(
       
   219                 'type'   => 10,
       
   220                 'tag'    => 'h6',
       
   221                 'group'  => 'inline',
       
   222                 'filter' => true,
       
   223             ),
       
   224             // callback tags
       
   225             'url' => array(
       
   226                 'type'     => 6, // self::TYPE_CALLBACK | self::TAG_NORMAL
       
   227                 'callback' => null,
       
   228                 'group'    => 'inline',
       
   229                 'filter'   => true,
       
   230             ),
       
   231             'img' => array(
       
   232                 'type'     => 6,
       
   233                 'callback' => null,
       
   234                 'group'    => 'inline-empty',
       
   235                 'filter'   => true,
       
   236             ),
       
   237             'code' => array(
       
   238                 'type'     => 6,
       
   239                 'callback' => null,
       
   240                 'group'    => 'block-empty',
       
   241                 'filter'   => false,
       
   242             ),
       
   243             'p' => array(
       
   244                 'type'   => 10,
       
   245                 'tag'    => 'p',
       
   246                 'group'  => 'block',
       
   247                 'filter' => true,
       
   248             ),
       
   249             'ignore' => array(
       
   250                 'type'   => 10,
       
   251                 'start'  => '',
       
   252                 'end'    => '',
       
   253                 'group'  => 'block-empty',
       
   254                 'filter' => true,
       
   255             ),
       
   256             'quote' => array(
       
   257                 'type'   => 10,
       
   258                 'tag'    => 'blockquote',
       
   259                 'group'  => 'block',
       
   260                 'filter' => true,
       
   261             ),
       
   262             'list' => array(
       
   263                 'type'     => 6,
       
   264                 'callback' => null,
       
   265                 'group'    => 'list',
       
   266                 'filter'   => new Zend_Filter_PregReplace('/.*/is', ''),
       
   267             ),
       
   268             '*' => array(
       
   269                 'type'   => 10,
       
   270                 'tag'    => 'li',
       
   271                 'group'  => 'list-item',
       
   272                 'filter' => true,
       
   273             ),
       
   274             'hr' => array(
       
   275                 'type'    => 9, // self::TYPE_REPLACE | self::TAG_SINGLE
       
   276                 'tag'     => 'hr',
       
   277                 'group'   => 'block',
       
   278                 'empty'   => true,
       
   279             ),
       
   280             // aliases
       
   281             'bold' => array(
       
   282                 'type' => 16,
       
   283                 'name' => 'b',
       
   284             ),
       
   285             'strong' => array(
       
   286                 'type' => 16,
       
   287                 'name' => 'b',
       
   288             ),
       
   289             'italic' => array(
       
   290                 'type' => 16,
       
   291                 'name' => 'i',
       
   292             ),
       
   293             'em' => array(
       
   294                 'type' => 16,
       
   295                 'name' => 'i',
       
   296             ),
       
   297             'emphasized' => array(
       
   298                 'type' => 16,
       
   299                 'name' => 'i',
       
   300             ),
       
   301             'underline' => array(
       
   302                 'type' => 16,
       
   303                 'name' => 'u',
       
   304             ),
       
   305             'citation' => array(
       
   306                 'type' => 16,
       
   307                 'name' => 'cite',
       
   308             ),
       
   309             'deleted' => array(
       
   310                 'type' => 16,
       
   311                 'name' => 'del',
       
   312             ),
       
   313             'insert' => array(
       
   314                 'type' => 16,
       
   315                 'name' => 'ins',
       
   316             ),
       
   317             'strike' => array(
       
   318                 'type' => 16,
       
   319                 'name' => 's',
       
   320             ),
       
   321             's' => array(
       
   322                 'type' => 16,
       
   323                 'name' => 'del',
       
   324             ),
       
   325             'subscript' => array(
       
   326                 'type' => 16,
       
   327                 'name' => 'sub',
       
   328             ),
       
   329             'superscript' => array(
       
   330                 'type' => 16,
       
   331                 'name' => 'sup',
       
   332             ),
       
   333             'a' => array(
       
   334                 'type' => 16,
       
   335                 'name' => 'url',
       
   336             ),
       
   337             'image' => array(
       
   338                 'type' => 16,
       
   339                 'name' => 'img',
       
   340             ),
       
   341             'li' => array(
       
   342                 'type' => 16,
       
   343                 'name' => '*',
       
   344             ),
       
   345             'color' => array(
       
   346                 'type' => 16,
       
   347                 'name' => 'span',
       
   348             ),
       
   349         );
       
   350     }
       
   351 
       
   352     /**
       
   353      * Add the default filters
       
   354      *
       
   355      * @return void
       
   356      */
       
   357     public function addDefaultFilters()
       
   358     {
       
   359         $this->_defaultFilter = new Zend_Filter();
       
   360 
       
   361         $this->_defaultFilter->addFilter(new Zend_Filter_HtmlEntities(array('encoding' => self::getEncoding())));
       
   362         $this->_defaultFilter->addFilter(new Zend_Filter_Callback('nl2br'));
       
   363     }
       
   364 
       
   365     /**
       
   366      * Execute a replace token
       
   367      *
       
   368      * @param  Zend_Markup_Token $token
       
   369      * @param  array $markup
       
   370      * @return string
       
   371      */
       
   372     protected function _executeReplace(Zend_Markup_Token $token, $markup)
       
   373     {
       
   374         if (isset($markup['tag'])) {
       
   375             if (!isset($markup['attributes'])) {
       
   376                 $markup['attributes'] = array();
       
   377             }
       
   378             $attrs = self::renderAttributes($token, $markup['attributes']);
       
   379             return "<{$markup['tag']}{$attrs}>{$this->_render($token)}</{$markup['tag']}>";
       
   380         }
       
   381 
       
   382         return parent::_executeReplace($token, $markup);
       
   383     }
       
   384 
       
   385     /**
       
   386      * Execute a single replace token
       
   387      *
       
   388      * @param  Zend_Markup_Token $token
       
   389      * @param  array $markup
       
   390      * @return string
       
   391      */
       
   392     protected function _executeSingleReplace(Zend_Markup_Token $token, $markup)
       
   393     {
       
   394         if (isset($markup['tag'])) {
       
   395             if (!isset($markup['attributes'])) {
       
   396                 $markup['attributes'] = array();
       
   397             }
       
   398             $attrs = self::renderAttributes($token, $markup['attributes']);
       
   399             return "<{$markup['tag']}{$attrs} />";
       
   400         }
       
   401         return parent::_executeSingleReplace($token, $markup);
       
   402     }
       
   403 
       
   404     /**
       
   405      * Render some attributes
       
   406      *
       
   407      * @param  Zend_Markup_Token $token
       
   408      * @param  array $attributes
       
   409      * @return string
       
   410      */
       
   411     public static function renderAttributes(Zend_Markup_Token $token, array $attributes = array())
       
   412     {
       
   413         $attributes = array_merge(self::$_defaultAttributes, $attributes);
       
   414 
       
   415         $return = '';
       
   416 
       
   417         $tokenAttributes = $token->getAttributes();
       
   418 
       
   419         // correct style attribute
       
   420         if (isset($tokenAttributes['style'])) {
       
   421             $tokenAttributes['style'] = trim($tokenAttributes['style']);
       
   422 
       
   423             if ($tokenAttributes['style'][strlen($tokenAttributes['style']) - 1] != ';') {
       
   424                 $tokenAttributes['style'] .= ';';
       
   425             }
       
   426         } else {
       
   427             $tokenAttributes['style'] = '';
       
   428         }
       
   429 
       
   430         // special treathment for 'align' and 'color' attribute
       
   431         if (isset($tokenAttributes['align'])) {
       
   432             $tokenAttributes['style'] .= 'text-align: ' . $tokenAttributes['align'] . ';';
       
   433             unset($tokenAttributes['align']);
       
   434         }
       
   435         if (isset($tokenAttributes['color']) && self::checkColor($tokenAttributes['color'])) {
       
   436             $tokenAttributes['style'] .= 'color: ' . $tokenAttributes['color'] . ';';
       
   437             unset($tokenAttributes['color']);
       
   438         }
       
   439 
       
   440         /*
       
   441          * loop through all the available attributes, and check if there is
       
   442          * a value defined by the token
       
   443          * if there is no value defined by the token, use the default value or
       
   444          * don't set the attribute
       
   445          */
       
   446         foreach ($attributes as $attribute => $value) {
       
   447             if (isset($tokenAttributes[$attribute]) && !empty($tokenAttributes[$attribute])) {
       
   448                 $return .= ' ' . $attribute . '="' . htmlentities($tokenAttributes[$attribute],
       
   449                                                                   ENT_QUOTES,
       
   450                                                                   self::getEncoding()) . '"';
       
   451             } elseif (!empty($value)) {
       
   452                 $return .= ' ' . $attribute . '="' . htmlentities($value, ENT_QUOTES, self::getEncoding()) . '"';
       
   453             }
       
   454         }
       
   455 
       
   456         return $return;
       
   457     }
       
   458 
       
   459     /**
       
   460      * Check if a color is a valid HTML color
       
   461      *
       
   462      * @param string $color
       
   463      *
       
   464      * @return bool
       
   465      */
       
   466     public static function checkColor($color)
       
   467     {
       
   468         /*
       
   469          * aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive,
       
   470          * purple, red, silver, teal, white, and yellow.
       
   471          */
       
   472         $colors = array(
       
   473             'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime',
       
   474             'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal',
       
   475             'white', 'yellow'
       
   476         );
       
   477 
       
   478         if (in_array($color, $colors)) {
       
   479             return true;
       
   480         }
       
   481 
       
   482         if (preg_match('/\#[0-9a-f]{6}/i', $color)) {
       
   483             return true;
       
   484         }
       
   485 
       
   486         return false;
       
   487     }
       
   488 
       
   489     /**
       
   490      * Check if the URI is valid
       
   491      *
       
   492      * @param string $uri
       
   493      *
       
   494      * @return bool
       
   495      */
       
   496     public static function isValidUri($uri)
       
   497     {
       
   498         if (!preg_match('/^([a-z][a-z+\-.]*):/i', $uri, $matches)) {
       
   499             return false;
       
   500         }
       
   501 
       
   502         $scheme = strtolower($matches[1]);
       
   503 
       
   504         switch ($scheme) {
       
   505             case 'javascript':
       
   506                 // JavaScript scheme is not allowed for security reason
       
   507                 return false;
       
   508 
       
   509             case 'http':
       
   510             case 'https':
       
   511             case 'ftp':
       
   512                 $components = @parse_url($uri);
       
   513 
       
   514                 if ($components === false) {
       
   515                     return false;
       
   516                 }
       
   517 
       
   518                 if (!isset($components['host'])) {
       
   519                     return false;
       
   520                 }
       
   521 
       
   522                 return true;
       
   523 
       
   524             default:
       
   525                 return true;
       
   526         }
       
   527     }
       
   528 }