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

0001 <?php
0002 
0003 /**
0004  * Abstract class for a set of proprietary modules that clean up (tidy)
0005  * poorly written HTML.
0006  * @todo Figure out how to protect some of these methods/properties
0007  */
0008 class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
0009 {
0010     /**
0011      * List of supported levels.
0012      * Index zero is a special case "no fixes" level.
0013      * @type array
0014      */
0015     public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
0016 
0017     /**
0018      * Default level to place all fixes in.
0019      * Disabled by default.
0020      * @type string
0021      */
0022     public $defaultLevel = null;
0023 
0024     /**
0025      * Lists of fixes used by getFixesForLevel().
0026      * Format is:
0027      *      HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
0028      * @type array
0029      */
0030     public $fixesForLevel = array(
0031         'light' => array(),
0032         'medium' => array(),
0033         'heavy' => array()
0034     );
0035 
0036     /**
0037      * Lazy load constructs the module by determining the necessary
0038      * fixes to create and then delegating to the populate() function.
0039      * @param HTMLPurifier_Config $config
0040      * @todo Wildcard matching and error reporting when an added or
0041      *       subtracted fix has no effect.
0042      */
0043     public function setup($config)
0044     {
0045         // create fixes, initialize fixesForLevel
0046         $fixes = $this->makeFixes();
0047         $this->makeFixesForLevel($fixes);
0048 
0049         // figure out which fixes to use
0050         $level = $config->get('HTML.TidyLevel');
0051         $fixes_lookup = $this->getFixesForLevel($level);
0052 
0053         // get custom fix declarations: these need namespace processing
0054         $add_fixes = $config->get('HTML.TidyAdd');
0055         $remove_fixes = $config->get('HTML.TidyRemove');
0056 
0057         foreach ($fixes as $name => $fix) {
0058             // needs to be refactored a little to implement globbing
0059             if (isset($remove_fixes[$name]) ||
0060                 (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
0061                 unset($fixes[$name]);
0062             }
0063         }
0064 
0065         // populate this module with necessary fixes
0066         $this->populate($fixes);
0067     }
0068 
0069     /**
0070      * Retrieves all fixes per a level, returning fixes for that specific
0071      * level as well as all levels below it.
0072      * @param string $level level identifier, see $levels for valid values
0073      * @return array Lookup up table of fixes
0074      */
0075     public function getFixesForLevel($level)
0076     {
0077         if ($level == $this->levels[0]) {
0078             return array();
0079         }
0080         $activated_levels = array();
0081         for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
0082             $activated_levels[] = $this->levels[$i];
0083             if ($this->levels[$i] == $level) {
0084                 break;
0085             }
0086         }
0087         if ($i == $c) {
0088             trigger_error(
0089                 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
0090                 E_USER_WARNING
0091             );
0092             return array();
0093         }
0094         $ret = array();
0095         foreach ($activated_levels as $level) {
0096             foreach ($this->fixesForLevel[$level] as $fix) {
0097                 $ret[$fix] = true;
0098             }
0099         }
0100         return $ret;
0101     }
0102 
0103     /**
0104      * Dynamically populates the $fixesForLevel member variable using
0105      * the fixes array. It may be custom overloaded, used in conjunction
0106      * with $defaultLevel, or not used at all.
0107      * @param array $fixes
0108      */
0109     public function makeFixesForLevel($fixes)
0110     {
0111         if (!isset($this->defaultLevel)) {
0112             return;
0113         }
0114         if (!isset($this->fixesForLevel[$this->defaultLevel])) {
0115             trigger_error(
0116                 'Default level ' . $this->defaultLevel . ' does not exist',
0117                 E_USER_ERROR
0118             );
0119             return;
0120         }
0121         $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
0122     }
0123 
0124     /**
0125      * Populates the module with transforms and other special-case code
0126      * based on a list of fixes passed to it
0127      * @param array $fixes Lookup table of fixes to activate
0128      */
0129     public function populate($fixes)
0130     {
0131         foreach ($fixes as $name => $fix) {
0132             // determine what the fix is for
0133             list($type, $params) = $this->getFixType($name);
0134             switch ($type) {
0135                 case 'attr_transform_pre':
0136                 case 'attr_transform_post':
0137                     $attr = $params['attr'];
0138                     if (isset($params['element'])) {
0139                         $element = $params['element'];
0140                         if (empty($this->info[$element])) {
0141                             $e = $this->addBlankElement($element);
0142                         } else {
0143                             $e = $this->info[$element];
0144                         }
0145                     } else {
0146                         $type = "info_$type";
0147                         $e = $this;
0148                     }
0149                     // PHP does some weird parsing when I do
0150                     // $e->$type[$attr], so I have to assign a ref.
0151                     $f =& $e->$type;
0152                     $f[$attr] = $fix;
0153                     break;
0154                 case 'tag_transform':
0155                     $this->info_tag_transform[$params['element']] = $fix;
0156                     break;
0157                 case 'child':
0158                 case 'content_model_type':
0159                     $element = $params['element'];
0160                     if (empty($this->info[$element])) {
0161                         $e = $this->addBlankElement($element);
0162                     } else {
0163                         $e = $this->info[$element];
0164                     }
0165                     $e->$type = $fix;
0166                     break;
0167                 default:
0168                     trigger_error("Fix type $type not supported", E_USER_ERROR);
0169                     break;
0170             }
0171         }
0172     }
0173 
0174     /**
0175      * Parses a fix name and determines what kind of fix it is, as well
0176      * as other information defined by the fix
0177      * @param $name String name of fix
0178      * @return array(string $fix_type, array $fix_parameters)
0179      * @note $fix_parameters is type dependant, see populate() for usage
0180      *       of these parameters
0181      */
0182     public function getFixType($name)
0183     {
0184         // parse it
0185         $property = $attr = null;
0186         if (strpos($name, '#') !== false) {
0187             list($name, $property) = explode('#', $name);
0188         }
0189         if (strpos($name, '@') !== false) {
0190             list($name, $attr) = explode('@', $name);
0191         }
0192 
0193         // figure out the parameters
0194         $params = array();
0195         if ($name !== '') {
0196             $params['element'] = $name;
0197         }
0198         if (!is_null($attr)) {
0199             $params['attr'] = $attr;
0200         }
0201 
0202         // special case: attribute transform
0203         if (!is_null($attr)) {
0204             if (is_null($property)) {
0205                 $property = 'pre';
0206             }
0207             $type = 'attr_transform_' . $property;
0208             return array($type, $params);
0209         }
0210 
0211         // special case: tag transform
0212         if (is_null($property)) {
0213             return array('tag_transform', $params);
0214         }
0215 
0216         return array($property, $params);
0217 
0218     }
0219 
0220     /**
0221      * Defines all fixes the module will perform in a compact
0222      * associative array of fix name to fix implementation.
0223      * @return array
0224      */
0225     public function makeFixes()
0226     {
0227     }
0228 }
0229 
0230 // vim: et sw=4 sts=4