PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Filter/StripTags.php

https://github.com/sidealice/zf2
PHP | 306 lines | 142 code | 35 blank | 129 comment | 33 complexity | 1964c729d1038a8b09d1c6096a46232f 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_Filter
  17. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * @namespace
  22. */
  23. namespace Zend\Filter;
  24. /**
  25. * @uses Zend\Filter\AbstractFilter
  26. * @category Zend
  27. * @package Zend_Filter
  28. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  29. * @license http://framework.zend.com/license/new-bsd New BSD License
  30. */
  31. class StripTags extends AbstractFilter
  32. {
  33. /**
  34. * Unique ID prefix used for allowing comments
  35. */
  36. const UNIQUE_ID_PREFIX = '__Zend_Filter_StripTags__';
  37. /**
  38. * Array of allowed tags and allowed attributes for each allowed tag
  39. *
  40. * Tags are stored in the array keys, and the array values are themselves
  41. * arrays of the attributes allowed for the corresponding tag.
  42. *
  43. * @var array
  44. */
  45. protected $tagsAllowed = array();
  46. /**
  47. * Array of allowed attributes for all allowed tags
  48. *
  49. * Attributes stored here are allowed for all of the allowed tags.
  50. *
  51. * @var array
  52. */
  53. protected $attributesAllowed = array();
  54. /**
  55. * Sets the filter options
  56. * Allowed options are
  57. * 'allowTags' => Tags which are allowed
  58. * 'allowAttribs' => Attributes which are allowed
  59. * 'allowComments' => Are comments allowed ?
  60. *
  61. * @param string|array|\Zend\Config\Config $options
  62. * @return void
  63. */
  64. public function __construct($options = null)
  65. {
  66. if ($options instanceof \Zend\Config\Config) {
  67. $options = $options->toArray();
  68. } else if ((!is_array($options)) || (is_array($options) && !array_key_exists('allowTags', $options) &&
  69. !array_key_exists('allowAttribs', $options) && !array_key_exists('allowComments', $options))) {
  70. $options = func_get_args();
  71. $temp['allowTags'] = array_shift($options);
  72. if (!empty($options)) {
  73. $temp['allowAttribs'] = array_shift($options);
  74. }
  75. if (!empty($options)) {
  76. $temp['allowComments'] = array_shift($options);
  77. }
  78. $options = $temp;
  79. }
  80. if (array_key_exists('allowTags', $options)) {
  81. $this->setTagsAllowed($options['allowTags']);
  82. }
  83. if (array_key_exists('allowAttribs', $options)) {
  84. $this->setAttributesAllowed($options['allowAttribs']);
  85. }
  86. }
  87. /**
  88. * Returns the tagsAllowed option
  89. *
  90. * @return array
  91. */
  92. public function getTagsAllowed()
  93. {
  94. return $this->tagsAllowed;
  95. }
  96. /**
  97. * Sets the tagsAllowed option
  98. *
  99. * @param array|string $tagsAllowed
  100. * @return \Zend\Filter\StripTags Provides a fluent interface
  101. */
  102. public function setTagsAllowed($tagsAllowed)
  103. {
  104. if (!is_array($tagsAllowed)) {
  105. $tagsAllowed = array($tagsAllowed);
  106. }
  107. foreach ($tagsAllowed as $index => $element) {
  108. // If the tag was provided without attributes
  109. if (is_int($index) && is_string($element)) {
  110. // Canonicalize the tag name
  111. $tagName = strtolower($element);
  112. // Store the tag as allowed with no attributes
  113. $this->tagsAllowed[$tagName] = array();
  114. }
  115. // Otherwise, if a tag was provided with attributes
  116. else if (is_string($index) && (is_array($element) || is_string($element))) {
  117. // Canonicalize the tag name
  118. $tagName = strtolower($index);
  119. // Canonicalize the attributes
  120. if (is_string($element)) {
  121. $element = array($element);
  122. }
  123. // Store the tag as allowed with the provided attributes
  124. $this->tagsAllowed[$tagName] = array();
  125. foreach ($element as $attribute) {
  126. if (is_string($attribute)) {
  127. // Canonicalize the attribute name
  128. $attributeName = strtolower($attribute);
  129. $this->tagsAllowed[$tagName][$attributeName] = null;
  130. }
  131. }
  132. }
  133. }
  134. return $this;
  135. }
  136. /**
  137. * Returns the attributesAllowed option
  138. *
  139. * @return array
  140. */
  141. public function getAttributesAllowed()
  142. {
  143. return $this->attributesAllowed;
  144. }
  145. /**
  146. * Sets the attributesAllowed option
  147. *
  148. * @param array|string $attributesAllowed
  149. * @return \Zend\Filter\StripTags Provides a fluent interface
  150. */
  151. public function setAttributesAllowed($attributesAllowed)
  152. {
  153. if (!is_array($attributesAllowed)) {
  154. $attributesAllowed = array($attributesAllowed);
  155. }
  156. // Store each attribute as allowed
  157. foreach ($attributesAllowed as $attribute) {
  158. if (is_string($attribute)) {
  159. // Canonicalize the attribute name
  160. $attributeName = strtolower($attribute);
  161. $this->attributesAllowed[$attributeName] = null;
  162. }
  163. }
  164. return $this;
  165. }
  166. /**
  167. * Defined by Zend_Filter_Interface
  168. *
  169. * @todo improve docblock descriptions
  170. *
  171. * @param string $value
  172. * @return string
  173. */
  174. public function filter($value)
  175. {
  176. $value = (string) $value;
  177. // Strip HTML comments first
  178. while (strpos($value, '<!--') !== false) {
  179. $pos = strrpos($value, '<!--');
  180. $start = substr($value, 0, $pos);
  181. $value = substr($value, $pos);
  182. // If there is no comment closing tag, strip whole text
  183. if (!preg_match('/--\s*>/s', $value)) {
  184. $value = '';
  185. } else {
  186. $value = preg_replace('/<(?:!(?:--[\s\S]*?--\s*)?(>))/s', '', $value);
  187. }
  188. $value = $start . $value;
  189. }
  190. // Initialize accumulator for filtered data
  191. $dataFiltered = '';
  192. // Parse the input data iteratively as regular pre-tag text followed by a
  193. // tag; either may be empty strings
  194. preg_match_all('/([^<]*)(<?[^>]*>?)/', (string) $value, $matches);
  195. // Iterate over each set of matches
  196. foreach ($matches[1] as $index => $preTag) {
  197. // If the pre-tag text is non-empty, strip any ">" characters from it
  198. if (strlen($preTag)) {
  199. $preTag = str_replace('>', '', $preTag);
  200. }
  201. // If a tag exists in this match, then filter the tag
  202. $tag = $matches[2][$index];
  203. if (strlen($tag)) {
  204. $tagFiltered = $this->_filterTag($tag);
  205. } else {
  206. $tagFiltered = '';
  207. }
  208. // Add the filtered pre-tag text and filtered tag to the data buffer
  209. $dataFiltered .= $preTag . $tagFiltered;
  210. }
  211. // Return the filtered data
  212. return $dataFiltered;
  213. }
  214. /**
  215. * Filters a single tag against the current option settings
  216. *
  217. * @param string $tag
  218. * @return string
  219. */
  220. protected function _filterTag($tag)
  221. {
  222. // Parse the tag into:
  223. // 1. a starting delimiter (mandatory)
  224. // 2. a tag name (if available)
  225. // 3. a string of attributes (if available)
  226. // 4. an ending delimiter (if available)
  227. $isMatch = preg_match('~(</?)(\w*)((/(?!>)|[^/>])*)(/?>)~', $tag, $matches);
  228. // If the tag does not match, then strip the tag entirely
  229. if (!$isMatch) {
  230. return '';
  231. }
  232. // Save the matches to more meaningfully named variables
  233. $tagStart = $matches[1];
  234. $tagName = strtolower($matches[2]);
  235. $tagAttributes = $matches[3];
  236. $tagEnd = $matches[5];
  237. // If the tag is not an allowed tag, then remove the tag entirely
  238. if (!isset($this->tagsAllowed[$tagName])) {
  239. return '';
  240. }
  241. // Trim the attribute string of whitespace at the ends
  242. $tagAttributes = trim($tagAttributes);
  243. // If there are non-whitespace characters in the attribute string
  244. if (strlen($tagAttributes)) {
  245. // Parse iteratively for well-formed attributes
  246. preg_match_all('/([\w-]+)\s*=\s*(?:(")(.*?)"|(\')(.*?)\')/s', $tagAttributes, $matches);
  247. // Initialize valid attribute accumulator
  248. $tagAttributes = '';
  249. // Iterate over each matched attribute
  250. foreach ($matches[1] as $index => $attributeName) {
  251. $attributeName = strtolower($attributeName);
  252. $attributeDelimiter = empty($matches[2][$index]) ? $matches[4][$index] : $matches[2][$index];
  253. $attributeValue = empty($matches[3][$index]) ? $matches[5][$index] : $matches[3][$index];
  254. // If the attribute is not allowed, then remove it entirely
  255. if (!array_key_exists($attributeName, $this->tagsAllowed[$tagName])
  256. && !array_key_exists($attributeName, $this->attributesAllowed)) {
  257. continue;
  258. }
  259. // Add the attribute to the accumulator
  260. $tagAttributes .= " $attributeName=" . $attributeDelimiter
  261. . $attributeValue . $attributeDelimiter;
  262. }
  263. }
  264. // Reconstruct tags ending with "/>" as backwards-compatible XHTML tag
  265. if (strpos($tagEnd, '/') !== false) {
  266. $tagEnd = " $tagEnd";
  267. }
  268. // Return the filtered tag
  269. return $tagStart . $tagName . $tagAttributes . $tagEnd;
  270. }
  271. }