|
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 } |