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 }