1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26: class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
27: {
28:
29:
30:
31: 32: 33:
34: public $info = array();
35:
36: 37: 38:
39: public $info_global_attr = array();
40:
41: 42: 43:
44: public $info_parent = 'div';
45:
46: 47: 48: 49:
50: public $info_parent_def;
51:
52: 53: 54: 55:
56: public $info_block_wrapper = 'p';
57:
58: 59: 60:
61: public $info_tag_transform = array();
62:
63: 64: 65:
66: public $info_attr_transform_pre = array();
67:
68: 69: 70:
71: public $info_attr_transform_post = array();
72:
73: 74: 75: 76:
77: public $info_content_sets = array();
78:
79: 80: 81:
82: public $info_injector = array();
83:
84: 85: 86:
87: public $doctype;
88:
89:
90:
91:
92:
93: 94: 95: 96: 97: 98: 99: 100: 101:
102: public function addAttribute($element_name, $attr_name, $def) {
103: $module = $this->getAnonymousModule();
104: if (!isset($module->info[$element_name])) {
105: $element = $module->addBlankElement($element_name);
106: } else {
107: $element = $module->info[$element_name];
108: }
109: $element->attr[$attr_name] = $def;
110: }
111:
112: 113: 114: 115: 116:
117: public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) {
118: $module = $this->getAnonymousModule();
119:
120:
121: $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
122: return $element;
123: }
124:
125: 126: 127: 128: 129: 130:
131: public function addBlankElement($element_name) {
132: $module = $this->getAnonymousModule();
133: $element = $module->addBlankElement($element_name);
134: return $element;
135: }
136:
137: 138: 139: 140: 141:
142: public function getAnonymousModule() {
143: if (!$this->_anonModule) {
144: $this->_anonModule = new HTMLPurifier_HTMLModule();
145: $this->_anonModule->name = 'Anonymous';
146: }
147: return $this->_anonModule;
148: }
149:
150: private $_anonModule;
151:
152:
153:
154:
155: public $type = 'HTML';
156: public $manager;
157:
158: 159: 160:
161: public function __construct() {
162: $this->manager = new HTMLPurifier_HTMLModuleManager();
163: }
164:
165: protected function doSetup($config) {
166: $this->processModules($config);
167: $this->setupConfigStuff($config);
168: unset($this->manager);
169:
170:
171: foreach ($this->info as $k => $v) {
172: unset($this->info[$k]->content_model);
173: unset($this->info[$k]->content_model_type);
174: }
175: }
176:
177: 178: 179:
180: protected function processModules($config) {
181:
182: if ($this->_anonModule) {
183:
184:
185:
186: $this->manager->addModule($this->_anonModule);
187: unset($this->_anonModule);
188: }
189:
190: $this->manager->setup($config);
191: $this->doctype = $this->manager->doctype;
192:
193: foreach ($this->manager->modules as $module) {
194: foreach($module->info_tag_transform as $k => $v) {
195: if ($v === false) unset($this->info_tag_transform[$k]);
196: else $this->info_tag_transform[$k] = $v;
197: }
198: foreach($module->info_attr_transform_pre as $k => $v) {
199: if ($v === false) unset($this->info_attr_transform_pre[$k]);
200: else $this->info_attr_transform_pre[$k] = $v;
201: }
202: foreach($module->info_attr_transform_post as $k => $v) {
203: if ($v === false) unset($this->info_attr_transform_post[$k]);
204: else $this->info_attr_transform_post[$k] = $v;
205: }
206: foreach ($module->info_injector as $k => $v) {
207: if ($v === false) unset($this->info_injector[$k]);
208: else $this->info_injector[$k] = $v;
209: }
210: }
211:
212: $this->info = $this->manager->getElements();
213: $this->info_content_sets = $this->manager->contentSets->lookup;
214:
215: }
216:
217: 218: 219:
220: protected function setupConfigStuff($config) {
221:
222: $block_wrapper = $config->get('HTML.BlockWrapper');
223: if (isset($this->info_content_sets['Block'][$block_wrapper])) {
224: $this->info_block_wrapper = $block_wrapper;
225: } else {
226: trigger_error('Cannot use non-block element as block wrapper',
227: E_USER_ERROR);
228: }
229:
230: $parent = $config->get('HTML.Parent');
231: $def = $this->manager->getElement($parent, true);
232: if ($def) {
233: $this->info_parent = $parent;
234: $this->info_parent_def = $def;
235: } else {
236: trigger_error('Cannot use unrecognized element as parent',
237: E_USER_ERROR);
238: $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
239: }
240:
241:
242: $support = "(for information on implementing this, see the ".
243: "support forums) ";
244:
245:
246:
247: $allowed_elements = $config->get('HTML.AllowedElements');
248: $allowed_attributes = $config->get('HTML.AllowedAttributes');
249:
250: if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
251: $allowed = $config->get('HTML.Allowed');
252: if (is_string($allowed)) {
253: list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
254: }
255: }
256:
257: if (is_array($allowed_elements)) {
258: foreach ($this->info as $name => $d) {
259: if(!isset($allowed_elements[$name])) unset($this->info[$name]);
260: unset($allowed_elements[$name]);
261: }
262:
263: foreach ($allowed_elements as $element => $d) {
264: $element = htmlspecialchars($element);
265: trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
266: }
267: }
268:
269:
270:
271: $allowed_attributes_mutable = $allowed_attributes;
272: if (is_array($allowed_attributes)) {
273:
274:
275:
276:
277: foreach ($this->info_global_attr as $attr => $x) {
278: $keys = array($attr, "*@$attr", "*.$attr");
279: $delete = true;
280: foreach ($keys as $key) {
281: if ($delete && isset($allowed_attributes[$key])) {
282: $delete = false;
283: }
284: if (isset($allowed_attributes_mutable[$key])) {
285: unset($allowed_attributes_mutable[$key]);
286: }
287: }
288: if ($delete) unset($this->info_global_attr[$attr]);
289: }
290:
291: foreach ($this->info as $tag => $info) {
292: foreach ($info->attr as $attr => $x) {
293: $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
294: $delete = true;
295: foreach ($keys as $key) {
296: if ($delete && isset($allowed_attributes[$key])) {
297: $delete = false;
298: }
299: if (isset($allowed_attributes_mutable[$key])) {
300: unset($allowed_attributes_mutable[$key]);
301: }
302: }
303: if ($delete) {
304: if ($this->info[$tag]->attr[$attr]->required) {
305: trigger_error("Required attribute '$attr' in element '$tag' was not allowed, which means '$tag' will not be allowed either", E_USER_WARNING);
306: }
307: unset($this->info[$tag]->attr[$attr]);
308: }
309: }
310: }
311:
312: foreach ($allowed_attributes_mutable as $elattr => $d) {
313: $bits = preg_split('/[.@]/', $elattr, 2);
314: $c = count($bits);
315: switch ($c) {
316: case 2:
317: if ($bits[0] !== '*') {
318: $element = htmlspecialchars($bits[0]);
319: $attribute = htmlspecialchars($bits[1]);
320: if (!isset($this->info[$element])) {
321: trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support");
322: } else {
323: trigger_error("Attribute '$attribute' in element '$element' not supported $support",
324: E_USER_WARNING);
325: }
326: break;
327: }
328:
329: case 1:
330: $attribute = htmlspecialchars($bits[0]);
331: trigger_error("Global attribute '$attribute' is not ".
332: "supported in any elements $support",
333: E_USER_WARNING);
334: break;
335: }
336: }
337:
338: }
339:
340:
341:
342: $forbidden_elements = $config->get('HTML.ForbiddenElements');
343: $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
344:
345: foreach ($this->info as $tag => $info) {
346: if (isset($forbidden_elements[$tag])) {
347: unset($this->info[$tag]);
348: continue;
349: }
350: foreach ($info->attr as $attr => $x) {
351: if (
352: isset($forbidden_attributes["$tag@$attr"]) ||
353: isset($forbidden_attributes["*@$attr"]) ||
354: isset($forbidden_attributes[$attr])
355: ) {
356: unset($this->info[$tag]->attr[$attr]);
357: continue;
358: }
359: elseif (isset($forbidden_attributes["$tag.$attr"])) {
360:
361: trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING);
362: }
363: }
364: }
365: foreach ($forbidden_attributes as $key => $v) {
366: if (strlen($key) < 2) continue;
367: if ($key[0] != '*') continue;
368: if ($key[1] == '.') {
369: trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING);
370: }
371: }
372:
373:
374: foreach ($this->info_injector as $i => $injector) {
375: if ($injector->checkNeeded($config) !== false) {
376:
377:
378: unset($this->info_injector[$i]);
379: }
380: }
381: }
382:
383: 384: 385: 386: 387: 388: 389: 390: 391:
392: public function parseTinyMCEAllowedList($list) {
393:
394: $list = str_replace(array(' ', "\t"), '', $list);
395:
396: $elements = array();
397: $attributes = array();
398:
399: $chunks = preg_split('/(,|[\n\r]+)/', $list);
400: foreach ($chunks as $chunk) {
401: if (empty($chunk)) continue;
402:
403: if (!strpos($chunk, '[')) {
404: $element = $chunk;
405: $attr = false;
406: } else {
407: list($element, $attr) = explode('[', $chunk);
408: }
409: if ($element !== '*') $elements[$element] = true;
410: if (!$attr) continue;
411: $attr = substr($attr, 0, strlen($attr) - 1);
412: $attr = explode('|', $attr);
413: foreach ($attr as $key) {
414: $attributes["$element.$key"] = true;
415: }
416: }
417:
418: return array($elements, $attributes);
419:
420: }
421:
422:
423: }
424:
425:
426: