File indexing completed on 2025-03-02 05:29:35

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Markup
0017  * @subpackage Renderer
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Filter_HtmlEntities
0025  */
0026 // require_once 'Zend/Filter/HtmlEntities.php';
0027 /**
0028  * @see Zend_Filter_PregReplace
0029  */
0030 // require_once 'Zend/Filter/PregReplace.php';
0031 /**
0032  * @see Zend_Filter_Callback
0033  */
0034 // require_once 'Zend/Filter/Callback.php';
0035 /**
0036  * @see Zend_Markup_Renderer_RendererAbstract
0037  */
0038 // require_once 'Zend/Markup/Renderer/RendererAbstract.php';
0039 
0040 /**
0041  * HTML renderer
0042  *
0043  * @category   Zend
0044  * @package    Zend_Markup
0045  * @subpackage Renderer
0046  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0047  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0048  */
0049 class Zend_Markup_Renderer_Html extends Zend_Markup_Renderer_RendererAbstract
0050 {
0051 
0052     /**
0053      * Element groups
0054      *
0055      * @var array
0056      */
0057     protected $_groups = array(
0058         'block'        => array('block', 'inline', 'block-empty', 'inline-empty', 'list'),
0059         'inline'       => array('inline', 'inline-empty'),
0060         'list'         => array('list-item'),
0061         'list-item'    => array('inline', 'inline-empty', 'list'),
0062         'block-empty'  => array(),
0063         'inline-empty' => array(),
0064     );
0065 
0066     /**
0067      * The current group
0068      *
0069      * @var string
0070      */
0071     protected $_group = 'block';
0072 
0073     /**
0074      * Default attributes
0075      *
0076      * @var array
0077      */
0078     protected static $_defaultAttributes = array(
0079         'id'    => '',
0080         'class' => '',
0081         'style' => '',
0082         'lang'  => '',
0083         'title' => ''
0084     );
0085 
0086 
0087     /**
0088      * Constructor
0089      *
0090      * @param array|Zend_Config $options
0091      *
0092      * @return void
0093      */
0094     public function __construct($options = array())
0095     {
0096         if ($options instanceof Zend_Config) {
0097             $options = $options->toArray();
0098         }
0099 
0100         $this->_pluginLoader = new Zend_Loader_PluginLoader(array(
0101             'Zend_Markup_Renderer_Html' => 'Zend/Markup/Renderer/Html/'
0102         ));
0103 
0104         if (!isset($options['useDefaultMarkups']) && isset($options['useDefaultTags'])) {
0105             $options['useDefaultMarkups'] = $options['useDefaultTags'];
0106         }
0107         if (isset($options['useDefaultMarkups']) && ($options['useDefaultMarkups'] !== false)) {
0108             $this->_defineDefaultMarkups();
0109         } elseif (!isset($options['useDefaultMarkups'])) {
0110             $this->_defineDefaultMarkups();
0111         }
0112 
0113         parent::__construct($options);
0114     }
0115 
0116     /**
0117      * Define the default markups
0118      *
0119      * @return void
0120      */
0121     protected function _defineDefaultMarkups()
0122     {
0123         $this->_markups = array(
0124             'b' => array(
0125                 'type'   => 10, // self::TYPE_REPLACE | self::TAG_NORMAL
0126                 'tag'    => 'strong',
0127                 'group'  => 'inline',
0128                 'filter' => true,
0129             ),
0130             'u' => array(
0131                 'type'        => 10,
0132                 'tag'         => 'span',
0133                 'attributes'  => array(
0134                     'style' => 'text-decoration: underline;',
0135                 ),
0136                 'group'       => 'inline',
0137                 'filter'      => true,
0138             ),
0139             'i' => array(
0140                 'type'   => 10,
0141                 'tag'    => 'em',
0142                 'group'  => 'inline',
0143                 'filter' => true,
0144             ),
0145             'cite' => array(
0146                 'type'   => 10,
0147                 'tag'    => 'cite',
0148                 'group'  => 'inline',
0149                 'filter' => true,
0150             ),
0151             'del' => array(
0152                 'type'   => 10,
0153                 'tag'    => 'del',
0154                 'group'  => 'inline',
0155                 'filter' => true,
0156             ),
0157             'ins' => array(
0158                 'type'   => 10,
0159                 'tag'    => 'ins',
0160                 'group'  => 'inline',
0161                 'filter' => true,
0162             ),
0163             'sub' => array(
0164                 'type'   => 10,
0165                 'tag'    => 'sub',
0166                 'group'  => 'inline',
0167                 'filter' => true,
0168             ),
0169             'sup' => array(
0170                 'type'   => 10,
0171                 'tag'    => 'sup',
0172                 'group'  => 'inline',
0173                 'filter' => true,
0174             ),
0175             'span' => array(
0176                 'type'   => 10,
0177                 'tag'    => 'span',
0178                 'group'  => 'inline',
0179                 'filter' => true,
0180             ),
0181             'acronym'  => array(
0182                 'type'   => 10,
0183                 'tag'    => 'acronym',
0184                 'group'  => 'inline',
0185                 'filter' => true,
0186             ),
0187             // headings
0188             'h1' => array(
0189                 'type'   => 10,
0190                 'tag'    => 'h1',
0191                 'group'  => 'inline',
0192                 'filter' => true,
0193             ),
0194             'h2' => array(
0195                 'type'   => 10,
0196                 'tag'    => 'h2',
0197                 'group'  => 'inline',
0198                 'filter' => true,
0199             ),
0200             'h3' => array(
0201                 'type'   => 10,
0202                 'tag'    => 'h3',
0203                 'group'  => 'inline',
0204                 'filter' => true,
0205             ),
0206             'h4' => array(
0207                 'type'   => 10,
0208                 'tag'    => 'h4',
0209                 'group'  => 'inline',
0210                 'filter' => true,
0211             ),
0212             'h5' => array(
0213                 'type'   => 10,
0214                 'tag'    => 'h5',
0215                 'group'  => 'inline',
0216                 'filter' => true,
0217             ),
0218             'h6' => array(
0219                 'type'   => 10,
0220                 'tag'    => 'h6',
0221                 'group'  => 'inline',
0222                 'filter' => true,
0223             ),
0224             // callback tags
0225             'url' => array(
0226                 'type'     => 6, // self::TYPE_CALLBACK | self::TAG_NORMAL
0227                 'callback' => null,
0228                 'group'    => 'inline',
0229                 'filter'   => true,
0230             ),
0231             'img' => array(
0232                 'type'     => 6,
0233                 'callback' => null,
0234                 'group'    => 'inline-empty',
0235                 'filter'   => true,
0236             ),
0237             'code' => array(
0238                 'type'     => 6,
0239                 'callback' => null,
0240                 'group'    => 'block-empty',
0241                 'filter'   => false,
0242             ),
0243             'p' => array(
0244                 'type'   => 10,
0245                 'tag'    => 'p',
0246                 'group'  => 'block',
0247                 'filter' => true,
0248             ),
0249             'ignore' => array(
0250                 'type'   => 10,
0251                 'start'  => '',
0252                 'end'    => '',
0253                 'group'  => 'block-empty',
0254                 'filter' => true,
0255             ),
0256             'quote' => array(
0257                 'type'   => 10,
0258                 'tag'    => 'blockquote',
0259                 'group'  => 'block',
0260                 'filter' => true,
0261             ),
0262             'list' => array(
0263                 'type'     => 6,
0264                 'callback' => null,
0265                 'group'    => 'list',
0266                 'filter'   => new Zend_Filter_PregReplace('/.*/is', ''),
0267             ),
0268             '*' => array(
0269                 'type'   => 10,
0270                 'tag'    => 'li',
0271                 'group'  => 'list-item',
0272                 'filter' => true,
0273             ),
0274             'hr' => array(
0275                 'type'    => 9, // self::TYPE_REPLACE | self::TAG_SINGLE
0276                 'tag'     => 'hr',
0277                 'group'   => 'block',
0278                 'empty'   => true,
0279             ),
0280             // aliases
0281             'bold' => array(
0282                 'type' => 16,
0283                 'name' => 'b',
0284             ),
0285             'strong' => array(
0286                 'type' => 16,
0287                 'name' => 'b',
0288             ),
0289             'italic' => array(
0290                 'type' => 16,
0291                 'name' => 'i',
0292             ),
0293             'em' => array(
0294                 'type' => 16,
0295                 'name' => 'i',
0296             ),
0297             'emphasized' => array(
0298                 'type' => 16,
0299                 'name' => 'i',
0300             ),
0301             'underline' => array(
0302                 'type' => 16,
0303                 'name' => 'u',
0304             ),
0305             'citation' => array(
0306                 'type' => 16,
0307                 'name' => 'cite',
0308             ),
0309             'deleted' => array(
0310                 'type' => 16,
0311                 'name' => 'del',
0312             ),
0313             'insert' => array(
0314                 'type' => 16,
0315                 'name' => 'ins',
0316             ),
0317             'strike' => array(
0318                 'type' => 16,
0319                 'name' => 's',
0320             ),
0321             's' => array(
0322                 'type' => 16,
0323                 'name' => 'del',
0324             ),
0325             'subscript' => array(
0326                 'type' => 16,
0327                 'name' => 'sub',
0328             ),
0329             'superscript' => array(
0330                 'type' => 16,
0331                 'name' => 'sup',
0332             ),
0333             'a' => array(
0334                 'type' => 16,
0335                 'name' => 'url',
0336             ),
0337             'image' => array(
0338                 'type' => 16,
0339                 'name' => 'img',
0340             ),
0341             'li' => array(
0342                 'type' => 16,
0343                 'name' => '*',
0344             ),
0345             'color' => array(
0346                 'type' => 16,
0347                 'name' => 'span',
0348             ),
0349         );
0350     }
0351 
0352     /**
0353      * Add the default filters
0354      *
0355      * @return void
0356      */
0357     public function addDefaultFilters()
0358     {
0359         $this->_defaultFilter = new Zend_Filter();
0360 
0361         $this->_defaultFilter->addFilter(new Zend_Filter_HtmlEntities(array('encoding' => self::getEncoding())));
0362         $this->_defaultFilter->addFilter(new Zend_Filter_Callback('nl2br'));
0363     }
0364 
0365     /**
0366      * Execute a replace token
0367      *
0368      * @param  Zend_Markup_Token $token
0369      * @param  array $markup
0370      * @return string
0371      */
0372     protected function _executeReplace(Zend_Markup_Token $token, $markup)
0373     {
0374         if (isset($markup['tag'])) {
0375             if (!isset($markup['attributes'])) {
0376                 $markup['attributes'] = array();
0377             }
0378             $attrs = self::renderAttributes($token, $markup['attributes']);
0379             return "<{$markup['tag']}{$attrs}>{$this->_render($token)}</{$markup['tag']}>";
0380         }
0381 
0382         return parent::_executeReplace($token, $markup);
0383     }
0384 
0385     /**
0386      * Execute a single replace token
0387      *
0388      * @param  Zend_Markup_Token $token
0389      * @param  array $markup
0390      * @return string
0391      */
0392     protected function _executeSingleReplace(Zend_Markup_Token $token, $markup)
0393     {
0394         if (isset($markup['tag'])) {
0395             if (!isset($markup['attributes'])) {
0396                 $markup['attributes'] = array();
0397             }
0398             $attrs = self::renderAttributes($token, $markup['attributes']);
0399             return "<{$markup['tag']}{$attrs} />";
0400         }
0401         return parent::_executeSingleReplace($token, $markup);
0402     }
0403 
0404     /**
0405      * Render some attributes
0406      *
0407      * @param  Zend_Markup_Token $token
0408      * @param  array $attributes
0409      * @return string
0410      */
0411     public static function renderAttributes(Zend_Markup_Token $token, array $attributes = array())
0412     {
0413         $attributes = array_merge(self::$_defaultAttributes, $attributes);
0414 
0415         $return = '';
0416 
0417         $tokenAttributes = $token->getAttributes();
0418 
0419         // correct style attribute
0420         if (isset($tokenAttributes['style'])) {
0421             $tokenAttributes['style'] = trim($tokenAttributes['style']);
0422 
0423             if ($tokenAttributes['style'][strlen($tokenAttributes['style']) - 1] != ';') {
0424                 $tokenAttributes['style'] .= ';';
0425             }
0426         } else {
0427             $tokenAttributes['style'] = '';
0428         }
0429 
0430         // special treathment for 'align' and 'color' attribute
0431         if (isset($tokenAttributes['align'])) {
0432             $tokenAttributes['style'] .= 'text-align: ' . $tokenAttributes['align'] . ';';
0433             unset($tokenAttributes['align']);
0434         }
0435         if (isset($tokenAttributes['color']) && self::checkColor($tokenAttributes['color'])) {
0436             $tokenAttributes['style'] .= 'color: ' . $tokenAttributes['color'] . ';';
0437             unset($tokenAttributes['color']);
0438         }
0439 
0440         /*
0441          * loop through all the available attributes, and check if there is
0442          * a value defined by the token
0443          * if there is no value defined by the token, use the default value or
0444          * don't set the attribute
0445          */
0446         foreach ($attributes as $attribute => $value) {
0447             if (isset($tokenAttributes[$attribute]) && !empty($tokenAttributes[$attribute])) {
0448                 $return .= ' ' . $attribute . '="' . htmlentities($tokenAttributes[$attribute],
0449                                                                   ENT_QUOTES,
0450                                                                   self::getEncoding()) . '"';
0451             } elseif (!empty($value)) {
0452                 $return .= ' ' . $attribute . '="' . htmlentities($value, ENT_QUOTES, self::getEncoding()) . '"';
0453             }
0454         }
0455 
0456         return $return;
0457     }
0458 
0459     /**
0460      * Check if a color is a valid HTML color
0461      *
0462      * @param string $color
0463      *
0464      * @return bool
0465      */
0466     public static function checkColor($color)
0467     {
0468         /*
0469          * aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive,
0470          * purple, red, silver, teal, white, and yellow.
0471          */
0472         $colors = array(
0473             'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime',
0474             'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal',
0475             'white', 'yellow'
0476         );
0477 
0478         if (in_array($color, $colors)) {
0479             return true;
0480         }
0481 
0482         if (preg_match('/\#[0-9a-f]{6}/i', $color)) {
0483             return true;
0484         }
0485 
0486         return false;
0487     }
0488 
0489     /**
0490      * Check if the URI is valid
0491      *
0492      * @param string $uri
0493      *
0494      * @return bool
0495      */
0496     public static function isValidUri($uri)
0497     {
0498         if (!preg_match('/^([a-z][a-z+\-.]*):/i', $uri, $matches)) {
0499             return false;
0500         }
0501 
0502         $scheme = strtolower($matches[1]);
0503 
0504         switch ($scheme) {
0505             case 'javascript':
0506                 // JavaScript scheme is not allowed for security reason
0507                 return false;
0508 
0509             case 'http':
0510             case 'https':
0511             case 'ftp':
0512                 $components = @parse_url($uri);
0513 
0514                 if ($components === false) {
0515                     return false;
0516                 }
0517 
0518                 if (!isset($components['host'])) {
0519                     return false;
0520                 }
0521 
0522                 return true;
0523 
0524             default:
0525                 return true;
0526         }
0527     }
0528 }