File indexing completed on 2024-05-12 06:02:33

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_Filter
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 
0023 /**
0024  * @see Zend_Filter_Interface
0025  */
0026 // require_once 'Zend/Filter/Interface.php';
0027 
0028 
0029 /**
0030  * @category   Zend
0031  * @package    Zend_Filter
0032  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0033  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0034  */
0035 class Zend_Filter_StripTags implements Zend_Filter_Interface
0036 {
0037     /**
0038      * Unique ID prefix used for allowing comments
0039      */
0040     const UNIQUE_ID_PREFIX = '__Zend_Filter_StripTags__';
0041 
0042     /**
0043      * Whether comments are allowed
0044      *
0045      * If false (the default), then comments are removed from the input string.
0046      *
0047      * This setting is now deprecated, and ignored internally.
0048      *
0049      * @deprecated
0050      * @var boolean
0051      */
0052     public $commentsAllowed = false;
0053 
0054     /**
0055      * Array of allowed tags and allowed attributes for each allowed tag
0056      *
0057      * Tags are stored in the array keys, and the array values are themselves
0058      * arrays of the attributes allowed for the corresponding tag.
0059      *
0060      * @var array
0061      */
0062     protected $_tagsAllowed = array();
0063 
0064     /**
0065      * Array of allowed attributes for all allowed tags
0066      *
0067      * Attributes stored here are allowed for all of the allowed tags.
0068      *
0069      * @var array
0070      */
0071     protected $_attributesAllowed = array();
0072 
0073     /**
0074      * Sets the filter options
0075      * Allowed options are
0076      *     'allowTags'     => Tags which are allowed
0077      *     'allowAttribs'  => Attributes which are allowed
0078      *     'allowComments' => Are comments allowed ?
0079      *
0080      * @param  string|array|Zend_Config $options
0081      * @return void
0082      */
0083     public function __construct($options = null)
0084     {
0085         if ($options instanceof Zend_Config) {
0086             $options = $options->toArray();
0087         } else if ((!is_array($options)) || (is_array($options) && !array_key_exists('allowTags', $options) &&
0088             !array_key_exists('allowAttribs', $options) && !array_key_exists('allowComments', $options))) {
0089             $options = func_get_args();
0090             $temp['allowTags'] = array_shift($options);
0091             if (!empty($options)) {
0092                 $temp['allowAttribs'] = array_shift($options);
0093             }
0094 
0095             if (!empty($options)) {
0096                 $temp['allowComments'] = array_shift($options);
0097             }
0098 
0099             $options = $temp;
0100         }
0101 
0102         if (array_key_exists('allowTags', $options)) {
0103             $this->setTagsAllowed($options['allowTags']);
0104         }
0105 
0106         if (array_key_exists('allowAttribs', $options)) {
0107             $this->setAttributesAllowed($options['allowAttribs']);
0108         }
0109 
0110         if (array_key_exists('allowComments', $options)) {
0111             $this->setCommentsAllowed($options['allowComments']);
0112         }
0113     }
0114 
0115     /**
0116      * Returns the commentsAllowed option
0117      *
0118      * This setting is now deprecated and ignored internally.
0119      *
0120      * @deprecated
0121      * @return bool
0122      */
0123     public function getCommentsAllowed()
0124     {
0125         return $this->commentsAllowed;
0126     }
0127 
0128     /**
0129      * Sets the commentsAllowed option
0130      *
0131      * This setting is now deprecated and ignored internally.
0132      *
0133      * @deprecated
0134      * @param  boolean $commentsAllowed
0135      * @return Zend_Filter_StripTags Provides a fluent interface
0136      */
0137     public function setCommentsAllowed($commentsAllowed)
0138     {
0139        $this->commentsAllowed = (boolean) $commentsAllowed;
0140        return $this;
0141     }
0142 
0143     /**
0144      * Returns the tagsAllowed option
0145      *
0146      * @return array
0147      */
0148     public function getTagsAllowed()
0149     {
0150         return $this->_tagsAllowed;
0151     }
0152 
0153     /**
0154      * Sets the tagsAllowed option
0155      *
0156      * @param  array|string $tagsAllowed
0157      * @return Zend_Filter_StripTags Provides a fluent interface
0158      */
0159     public function setTagsAllowed($tagsAllowed)
0160     {
0161         if (!is_array($tagsAllowed)) {
0162             $tagsAllowed = array($tagsAllowed);
0163         }
0164 
0165         foreach ($tagsAllowed as $index => $element) {
0166             // If the tag was provided without attributes
0167             if (is_int($index) && is_string($element)) {
0168                 // Canonicalize the tag name
0169                 $tagName = strtolower($element);
0170                 // Store the tag as allowed with no attributes
0171                 $this->_tagsAllowed[$tagName] = array();
0172             }
0173             // Otherwise, if a tag was provided with attributes
0174             else if (is_string($index) && (is_array($element) || is_string($element))) {
0175                 // Canonicalize the tag name
0176                 $tagName = strtolower($index);
0177                 // Canonicalize the attributes
0178                 if (is_string($element)) {
0179                     $element = array($element);
0180                 }
0181                 // Store the tag as allowed with the provided attributes
0182                 $this->_tagsAllowed[$tagName] = array();
0183                 foreach ($element as $attribute) {
0184                     if (is_string($attribute)) {
0185                         // Canonicalize the attribute name
0186                         $attributeName = strtolower($attribute);
0187                         $this->_tagsAllowed[$tagName][$attributeName] = null;
0188                     }
0189                 }
0190             }
0191         }
0192 
0193         return $this;
0194     }
0195 
0196     /**
0197      * Returns the attributesAllowed option
0198      *
0199      * @return array
0200      */
0201     public function getAttributesAllowed()
0202     {
0203         return $this->_attributesAllowed;
0204     }
0205 
0206     /**
0207      * Sets the attributesAllowed option
0208      *
0209      * @param  array|string $attributesAllowed
0210      * @return Zend_Filter_StripTags Provides a fluent interface
0211      */
0212     public function setAttributesAllowed($attributesAllowed)
0213     {
0214         if (!is_array($attributesAllowed)) {
0215             $attributesAllowed = array($attributesAllowed);
0216         }
0217 
0218         // Store each attribute as allowed
0219         foreach ($attributesAllowed as $attribute) {
0220             if (is_string($attribute)) {
0221                 // Canonicalize the attribute name
0222                 $attributeName = strtolower($attribute);
0223                 $this->_attributesAllowed[$attributeName] = null;
0224             }
0225         }
0226 
0227         return $this;
0228     }
0229 
0230     /**
0231      * Defined by Zend_Filter_Interface
0232      *
0233      * @todo improve docblock descriptions
0234      *
0235      * @param  string $value
0236      * @return string
0237      */
0238     public function filter($value)
0239     {
0240         $value = (string) $value;
0241 
0242         // Strip HTML comments first
0243         while (strpos($value, '<!--') !== false) {
0244             $pos   = strrpos($value, '<!--');
0245             $start = substr($value, 0, $pos);
0246             $value = substr($value, $pos);
0247 
0248             // If there is no comment closing tag, strip whole text
0249             if (!preg_match('/--\s*>/s', $value)) {
0250                 $value = '';
0251             } else {
0252                 $value = preg_replace('/<(?:!(?:--[\s\S]*?--\s*)?(>))/s', '',  $value);
0253             }
0254 
0255             $value = $start . $value;
0256         }
0257 
0258         // Initialize accumulator for filtered data
0259         $dataFiltered = '';
0260         // Parse the input data iteratively as regular pre-tag text followed by a
0261         // tag; either may be empty strings
0262         preg_match_all('/([^<]*)(<?[^>]*>?)/', (string) $value, $matches);
0263 
0264         // Iterate over each set of matches
0265         foreach ($matches[1] as $index => $preTag) {
0266             // If the pre-tag text is non-empty, strip any ">" characters from it
0267             if (strlen($preTag)) {
0268                 $preTag = str_replace('>', '', $preTag);
0269             }
0270             // If a tag exists in this match, then filter the tag
0271             $tag = $matches[2][$index];
0272             if (strlen($tag)) {
0273                 $tagFiltered = $this->_filterTag($tag);
0274             } else {
0275                 $tagFiltered = '';
0276             }
0277             // Add the filtered pre-tag text and filtered tag to the data buffer
0278             $dataFiltered .= $preTag . $tagFiltered;
0279         }
0280 
0281         // Return the filtered data
0282         return $dataFiltered;
0283     }
0284 
0285     /**
0286      * Filters a single tag against the current option settings
0287      *
0288      * @param  string $tag
0289      * @return string
0290      */
0291     protected function _filterTag($tag)
0292     {
0293         // Parse the tag into:
0294         // 1. a starting delimiter (mandatory)
0295         // 2. a tag name (if available)
0296         // 3. a string of attributes (if available)
0297         // 4. an ending delimiter (if available)
0298         $isMatch = preg_match('~(</?)(\w*)((/(?!>)|[^/>])*)(/?>)~', $tag, $matches);
0299 
0300         // If the tag does not match, then strip the tag entirely
0301         if (!$isMatch) {
0302             return '';
0303         }
0304 
0305         // Save the matches to more meaningfully named variables
0306         $tagStart      = $matches[1];
0307         $tagName       = strtolower($matches[2]);
0308         $tagAttributes = $matches[3];
0309         $tagEnd        = $matches[5];
0310 
0311         // If the tag is not an allowed tag, then remove the tag entirely
0312         if (!isset($this->_tagsAllowed[$tagName])) {
0313             return '';
0314         }
0315 
0316         // Trim the attribute string of whitespace at the ends
0317         $tagAttributes = trim($tagAttributes);
0318 
0319         // If there are non-whitespace characters in the attribute string
0320         if (strlen($tagAttributes)) {
0321             // Parse iteratively for well-formed attributes
0322             preg_match_all('/([\w-]+)\s*=\s*(?:(")(.*?)"|(\')(.*?)\')/s', $tagAttributes, $matches);
0323 
0324             // Initialize valid attribute accumulator
0325             $tagAttributes = '';
0326 
0327             // Iterate over each matched attribute
0328             foreach ($matches[1] as $index => $attributeName) {
0329                 $attributeName      = strtolower($attributeName);
0330                 $attributeDelimiter = empty($matches[2][$index]) ? $matches[4][$index] : $matches[2][$index];
0331                 $attributeValue     = empty($matches[3][$index]) ? $matches[5][$index] : $matches[3][$index];
0332 
0333                 // If the attribute is not allowed, then remove it entirely
0334                 if (!array_key_exists($attributeName, $this->_tagsAllowed[$tagName])
0335                     && !array_key_exists($attributeName, $this->_attributesAllowed)) {
0336                     continue;
0337                 }
0338                 // Add the attribute to the accumulator
0339                 $tagAttributes .= " $attributeName=" . $attributeDelimiter
0340                                 . $attributeValue . $attributeDelimiter;
0341             }
0342         }
0343 
0344         // Reconstruct tags ending with "/>" as backwards-compatible XHTML tag
0345         if (strpos($tagEnd, '/') !== false) {
0346             $tagEnd = " $tagEnd";
0347         }
0348 
0349         // Return the filtered tag
0350         return $tagStart . $tagName . $tagAttributes . $tagEnd;
0351     }
0352 }