]>
Commit | Line | Data |
---|---|---|
d4949327 NL |
1 | <?php\r |
2 | \r | |
3 | /**\r | |
4 | * Abstract class for a set of proprietary modules that clean up (tidy)\r | |
5 | * poorly written HTML.\r | |
6 | * @todo Figure out how to protect some of these methods/properties\r | |
7 | */\r | |
8 | class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule\r | |
9 | {\r | |
10 | /**\r | |
11 | * List of supported levels.\r | |
12 | * Index zero is a special case "no fixes" level.\r | |
13 | * @type array\r | |
14 | */\r | |
15 | public $levels = array(0 => 'none', 'light', 'medium', 'heavy');\r | |
16 | \r | |
17 | /**\r | |
18 | * Default level to place all fixes in.\r | |
19 | * Disabled by default.\r | |
20 | * @type string\r | |
21 | */\r | |
22 | public $defaultLevel = null;\r | |
23 | \r | |
24 | /**\r | |
25 | * Lists of fixes used by getFixesForLevel().\r | |
26 | * Format is:\r | |
27 | * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');\r | |
28 | * @type array\r | |
29 | */\r | |
30 | public $fixesForLevel = array(\r | |
31 | 'light' => array(),\r | |
32 | 'medium' => array(),\r | |
33 | 'heavy' => array()\r | |
34 | );\r | |
35 | \r | |
36 | /**\r | |
37 | * Lazy load constructs the module by determining the necessary\r | |
38 | * fixes to create and then delegating to the populate() function.\r | |
39 | * @param HTMLPurifier_Config $config\r | |
40 | * @todo Wildcard matching and error reporting when an added or\r | |
41 | * subtracted fix has no effect.\r | |
42 | */\r | |
43 | public function setup($config)\r | |
44 | {\r | |
45 | // create fixes, initialize fixesForLevel\r | |
46 | $fixes = $this->makeFixes();\r | |
47 | $this->makeFixesForLevel($fixes);\r | |
48 | \r | |
49 | // figure out which fixes to use\r | |
50 | $level = $config->get('HTML.TidyLevel');\r | |
51 | $fixes_lookup = $this->getFixesForLevel($level);\r | |
52 | \r | |
53 | // get custom fix declarations: these need namespace processing\r | |
54 | $add_fixes = $config->get('HTML.TidyAdd');\r | |
55 | $remove_fixes = $config->get('HTML.TidyRemove');\r | |
56 | \r | |
57 | foreach ($fixes as $name => $fix) {\r | |
58 | // needs to be refactored a little to implement globbing\r | |
59 | if (isset($remove_fixes[$name]) ||\r | |
60 | (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {\r | |
61 | unset($fixes[$name]);\r | |
62 | }\r | |
63 | }\r | |
64 | \r | |
65 | // populate this module with necessary fixes\r | |
66 | $this->populate($fixes);\r | |
67 | }\r | |
68 | \r | |
69 | /**\r | |
70 | * Retrieves all fixes per a level, returning fixes for that specific\r | |
71 | * level as well as all levels below it.\r | |
72 | * @param string $level level identifier, see $levels for valid values\r | |
73 | * @return array Lookup up table of fixes\r | |
74 | */\r | |
75 | public function getFixesForLevel($level)\r | |
76 | {\r | |
77 | if ($level == $this->levels[0]) {\r | |
78 | return array();\r | |
79 | }\r | |
80 | $activated_levels = array();\r | |
81 | for ($i = 1, $c = count($this->levels); $i < $c; $i++) {\r | |
82 | $activated_levels[] = $this->levels[$i];\r | |
83 | if ($this->levels[$i] == $level) {\r | |
84 | break;\r | |
85 | }\r | |
86 | }\r | |
87 | if ($i == $c) {\r | |
88 | trigger_error(\r | |
89 | 'Tidy level ' . htmlspecialchars($level) . ' not recognized',\r | |
90 | E_USER_WARNING\r | |
91 | );\r | |
92 | return array();\r | |
93 | }\r | |
94 | $ret = array();\r | |
95 | foreach ($activated_levels as $level) {\r | |
96 | foreach ($this->fixesForLevel[$level] as $fix) {\r | |
97 | $ret[$fix] = true;\r | |
98 | }\r | |
99 | }\r | |
100 | return $ret;\r | |
101 | }\r | |
102 | \r | |
103 | /**\r | |
104 | * Dynamically populates the $fixesForLevel member variable using\r | |
105 | * the fixes array. It may be custom overloaded, used in conjunction\r | |
106 | * with $defaultLevel, or not used at all.\r | |
107 | * @param array $fixes\r | |
108 | */\r | |
109 | public function makeFixesForLevel($fixes)\r | |
110 | {\r | |
111 | if (!isset($this->defaultLevel)) {\r | |
112 | return;\r | |
113 | }\r | |
114 | if (!isset($this->fixesForLevel[$this->defaultLevel])) {\r | |
115 | trigger_error(\r | |
116 | 'Default level ' . $this->defaultLevel . ' does not exist',\r | |
117 | E_USER_ERROR\r | |
118 | );\r | |
119 | return;\r | |
120 | }\r | |
121 | $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);\r | |
122 | }\r | |
123 | \r | |
124 | /**\r | |
125 | * Populates the module with transforms and other special-case code\r | |
126 | * based on a list of fixes passed to it\r | |
127 | * @param array $fixes Lookup table of fixes to activate\r | |
128 | */\r | |
129 | public function populate($fixes)\r | |
130 | {\r | |
131 | foreach ($fixes as $name => $fix) {\r | |
132 | // determine what the fix is for\r | |
133 | list($type, $params) = $this->getFixType($name);\r | |
134 | switch ($type) {\r | |
135 | case 'attr_transform_pre':\r | |
136 | case 'attr_transform_post':\r | |
137 | $attr = $params['attr'];\r | |
138 | if (isset($params['element'])) {\r | |
139 | $element = $params['element'];\r | |
140 | if (empty($this->info[$element])) {\r | |
141 | $e = $this->addBlankElement($element);\r | |
142 | } else {\r | |
143 | $e = $this->info[$element];\r | |
144 | }\r | |
145 | } else {\r | |
146 | $type = "info_$type";\r | |
147 | $e = $this;\r | |
148 | }\r | |
149 | // PHP does some weird parsing when I do\r | |
150 | // $e->$type[$attr], so I have to assign a ref.\r | |
151 | $f =& $e->$type;\r | |
152 | $f[$attr] = $fix;\r | |
153 | break;\r | |
154 | case 'tag_transform':\r | |
155 | $this->info_tag_transform[$params['element']] = $fix;\r | |
156 | break;\r | |
157 | case 'child':\r | |
158 | case 'content_model_type':\r | |
159 | $element = $params['element'];\r | |
160 | if (empty($this->info[$element])) {\r | |
161 | $e = $this->addBlankElement($element);\r | |
162 | } else {\r | |
163 | $e = $this->info[$element];\r | |
164 | }\r | |
165 | $e->$type = $fix;\r | |
166 | break;\r | |
167 | default:\r | |
168 | trigger_error("Fix type $type not supported", E_USER_ERROR);\r | |
169 | break;\r | |
170 | }\r | |
171 | }\r | |
172 | }\r | |
173 | \r | |
174 | /**\r | |
175 | * Parses a fix name and determines what kind of fix it is, as well\r | |
176 | * as other information defined by the fix\r | |
177 | * @param $name String name of fix\r | |
178 | * @return array(string $fix_type, array $fix_parameters)\r | |
179 | * @note $fix_parameters is type dependant, see populate() for usage\r | |
180 | * of these parameters\r | |
181 | */\r | |
182 | public function getFixType($name)\r | |
183 | {\r | |
184 | // parse it\r | |
185 | $property = $attr = null;\r | |
186 | if (strpos($name, '#') !== false) {\r | |
187 | list($name, $property) = explode('#', $name);\r | |
188 | }\r | |
189 | if (strpos($name, '@') !== false) {\r | |
190 | list($name, $attr) = explode('@', $name);\r | |
191 | }\r | |
192 | \r | |
193 | // figure out the parameters\r | |
194 | $params = array();\r | |
195 | if ($name !== '') {\r | |
196 | $params['element'] = $name;\r | |
197 | }\r | |
198 | if (!is_null($attr)) {\r | |
199 | $params['attr'] = $attr;\r | |
200 | }\r | |
201 | \r | |
202 | // special case: attribute transform\r | |
203 | if (!is_null($attr)) {\r | |
204 | if (is_null($property)) {\r | |
205 | $property = 'pre';\r | |
206 | }\r | |
207 | $type = 'attr_transform_' . $property;\r | |
208 | return array($type, $params);\r | |
209 | }\r | |
210 | \r | |
211 | // special case: tag transform\r | |
212 | if (is_null($property)) {\r | |
213 | return array('tag_transform', $params);\r | |
214 | }\r | |
215 | \r | |
216 | return array($property, $params);\r | |
217 | \r | |
218 | }\r | |
219 | \r | |
220 | /**\r | |
221 | * Defines all fixes the module will perform in a compact\r | |
222 | * associative array of fix name to fix implementation.\r | |
223 | * @return array\r | |
224 | */\r | |
225 | public function makeFixes()\r | |
226 | {\r | |
227 | }\r | |
228 | }\r | |
229 | \r | |
230 | // vim: et sw=4 sts=4\r |