PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Markup/Renderer/Html.php

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