diff --git a/library/HTMLPurifier/ChildDef/Required.php b/library/HTMLPurifier/ChildDef/Required.php index c6f706e2..4d253398 100644 --- a/library/HTMLPurifier/ChildDef/Required.php +++ b/library/HTMLPurifier/ChildDef/Required.php @@ -29,7 +29,6 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef } } $this->elements = $elements; - $this->gen = new HTMLPurifier_Generator(); } var $allow_empty = false; var $type = 'required'; @@ -57,6 +56,12 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef // some configuration $escape_invalid_children = $config->get('Core', 'EscapeInvalidChildren'); + // generator + static $gen = null; + if ($gen === null) { + $gen = new HTMLPurifier_Generator(); + } + foreach ($tokens_of_children as $token) { if (!empty($token->is_whitespace)) { $result[] = $token; @@ -80,7 +85,7 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef $result[] = $token; } elseif ($pcdata_allowed && $escape_invalid_children) { $result[] = new HTMLPurifier_Token_Text( - $this->gen->generateFromToken($token, $config) + $gen->generateFromToken($token, $config) ); } continue; @@ -91,7 +96,7 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef } elseif ($pcdata_allowed && $escape_invalid_children) { $result[] = new HTMLPurifier_Token_Text( - $this->gen->generateFromToken( $token, $config ) + $gen->generateFromToken( $token, $config ) ); } else { // drop silently diff --git a/library/HTMLPurifier/HTMLDefinition.php b/library/HTMLPurifier/HTMLDefinition.php index 244dc837..8aa3bbb7 100644 --- a/library/HTMLPurifier/HTMLDefinition.php +++ b/library/HTMLPurifier/HTMLDefinition.php @@ -198,7 +198,7 @@ class HTMLPurifier_HTMLDefinition } } - $this->info = $this->manager->getElements($this->config); + $this->info = $this->manager->getElements(); $this->info_content_sets = $this->manager->contentSets->lookup; } @@ -217,15 +217,14 @@ class HTMLPurifier_HTMLDefinition } $parent = $this->config->get('HTML', 'Parent'); - $def = $this->manager->getElement($parent, $this->config); + $def = $this->manager->getElement($parent, true); if ($def) { $this->info_parent = $parent; $this->info_parent_def = $def; } else { trigger_error('Cannot use unrecognized element as parent.', E_USER_ERROR); - $this->info_parent_def = $this->manager->getElement( - $this->info_parent, $this->config); + $this->info_parent_def = $this->manager->getElement($this->info_parent, true); } // support template text diff --git a/library/HTMLPurifier/HTMLModule.php b/library/HTMLPurifier/HTMLModule.php index 592f8101..b1392ce9 100644 --- a/library/HTMLPurifier/HTMLModule.php +++ b/library/HTMLPurifier/HTMLModule.php @@ -136,7 +136,7 @@ class HTMLPurifier_HTMLModule * can set advanced parameters * @protected */ - function &addElement($element, $safe, $type, $contents, $attr_includes, $attr = array()) { + function &addElement($element, $safe, $type, $contents, $attr_includes = array(), $attr = array()) { $this->elements[] = $element; // parse content_model list($content_model_type, $content_model) = $this->parseContents($contents); diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php index 6adbeb59..2820da06 100644 --- a/library/HTMLPurifier/HTMLModuleManager.php +++ b/library/HTMLPurifier/HTMLModuleManager.php @@ -76,6 +76,13 @@ class HTMLPurifier_HTMLModuleManager */ var $registeredModules = array(); + /** + * List of extra modules that were added by the user using addModule(). + * These get unconditionally merged into the current doctype, whatever + * it may be. + */ + var $userModules = array(); + /** * Associative array of element name to list of modules that have * definitions for the element; this array is dynamically filled. @@ -210,7 +217,9 @@ class HTMLPurifier_HTMLModuleManager * and then tacking it on to the active doctype */ function addModule($module) { - // unimplemented + $this->registerModule($module); + if (is_object($module)) $module = $module->name; + $this->userModules[] = $module; } /** @@ -234,16 +243,19 @@ class HTMLPurifier_HTMLModuleManager $doctype = $this->doctypes->make($config); $modules = $doctype->modules; + // merge in custom modules + $modules = array_merge($modules, $this->userModules); + foreach ($modules as $module) { if (is_object($module)) { - $this->modules[$module->name] = $module; + $this->registeredModules[$module->name] = $module; continue; } else { - if (!isset($this->modules[$module])) { + if (!isset($this->registeredModules[$module])) { $this->registerModule($module); } - $this->modules[$module] = $this->registeredModules[$module]; } + $this->modules[$module] = $this->registeredModules[$module]; } // setup lookup table based on all valid modules @@ -274,11 +286,9 @@ class HTMLPurifier_HTMLModuleManager /** * Retrieves merged element definitions. - * @param $config Instance of HTMLPurifier_Config, for determining - * stray elements. * @return Array of HTMLPurifier_ElementDef */ - function getElements($config) { + function getElements() { $elements = array(); foreach ($this->modules as $module) { @@ -286,7 +296,7 @@ class HTMLPurifier_HTMLModuleManager if (isset($elements[$name])) continue; // if element is not safe, don't use it if (!$this->trusted && ($v->safe === false)) continue; - $elements[$name] = $this->getElement($name, $config); + $elements[$name] = $this->getElement($name); } } @@ -303,12 +313,11 @@ class HTMLPurifier_HTMLModuleManager /** * Retrieves a single merged element definition * @param $name Name of element - * @param $config Instance of HTMLPurifier_Config, may not be necessary. * @param $trusted Boolean trusted overriding parameter: set to true * if you want the full version of an element * @return Merged HTMLPurifier_ElementDef */ - function getElement($name, $config, $trusted = null) { + function getElement($name, $trusted = null) { $def = false; if ($trusted === null) $trusted = $this->trusted; diff --git a/tests/HTMLPurifier/HTMLModuleManagerTest.php b/tests/HTMLPurifier/HTMLModuleManagerTest.php index 1826b6df..aa695a98 100644 --- a/tests/HTMLPurifier/HTMLModuleManagerTest.php +++ b/tests/HTMLPurifier/HTMLModuleManagerTest.php @@ -2,283 +2,76 @@ require_once 'HTMLPurifier/HTMLModuleManager.php'; -// stub classes for unit testing -class HTMLPurifier_HTMLModule_ManagerTestModule extends HTMLPurifier_HTMLModule { - var $name = 'ManagerTestModule'; -} -class HTMLPurifier_HTMLModuleManagerTest_TestModule extends HTMLPurifier_HTMLModule { - var $name = 'TestModule'; -} - class HTMLPurifier_HTMLModuleManagerTest extends UnitTestCase { - // unit tests temporarily disabled as we do big refactoring - - /** - * System under test, instance of HTMLPurifier_HTMLModuleManager. - */ - var $manager; - - function setup() { - $this->manager = new HTMLPurifier_HTMLModuleManager(true); - } - - function teardown() { - tally_errors($this); - } - - function createModule($name) { - $module = new HTMLPurifier_HTMLModule(); - $module->name = $name; - return $module; - } - - function untest_addModule_withAutoload() { - $this->manager->autoDoctype = 'Generic Document 0.1'; - $this->manager->autoCollection = 'Default'; + function test_addModule() { + $manager = new HTMLPurifier_HTMLModuleManager(); + $manager->doctypes->register('Blank'); // doctype normally is blank... - $module = new HTMLPurifier_HTMLModule(); - $module->name = 'Module'; + generate_mock_once('HTMLPurifier_AttrDef'); + $attrdef_nmtokens =& new HTMLPurifier_AttrDefMock($this); + $manager->attrTypes->info['NMTOKENS'] =& $attrdef_nmtokens; - $module2 = new HTMLPurifier_HTMLModule(); - $module2->name = 'Module2'; + // ...but we add user modules - // we need to grab the dynamically generated orders from - // the object since modules are not passed by reference + $common_module = new HTMLPurifier_HTMLModule(); + $common_module->name = 'Common'; + $common_module->attr_collections['Common'] = array('class' => 'NMTOKENS'); + $common_module->content_sets['Flow'] = 'Block | Inline'; + $manager->addModule($common_module); - $this->manager->addModule($module); - $module_order = $this->manager->modules['Module']->order; - $module->order = $module_order; - $this->assertIdentical($module, $this->manager->modules['Module']); + $structural_module = new HTMLPurifier_HTMLModule(); + $structural_module->name = 'Structural'; + $structural_module->addElement('p', true, 'Block', 'Inline', 'Common'); + $structural_module->addElement('div', false, 'Block', 'Flow'); + $manager->addModule($structural_module); - $this->manager->addModule($module2); - $module2_order = $this->manager->modules['Module2']->order; - $module2->order = $module2_order; - $this->assertIdentical($module2, $this->manager->modules['Module2']); - $this->assertIdentical($module_order + 1, $module2_order); + $formatting_module = new HTMLPurifier_HTMLModule(); + $formatting_module->name = 'Formatting'; + $formatting_module->addElement('em', true, 'Inline', 'Inline', 'Common'); + $manager->addModule($formatting_module); - $this->assertIdentical( - $this->manager->collections['Default']['Generic Document 0.1'], - array('Module', 'Module2') + $config = HTMLPurifier_Config::createDefault(); + $config->set('HTML', 'Trusted', false); + $config->set('HTML', 'Doctype', 'Blank'); + + $manager->setup($config); + + $p = new HTMLPurifier_ElementDef(); + $p->attr['class'] = $attrdef_nmtokens; + $p->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA')); + $p->content_model = 'em | #PCDATA'; + $p->content_model_type = 'optional'; + $p->descendants_are_inline = true; + $p->safe = true; + + $em = new HTMLPurifier_ElementDef(); + $em->attr['class'] = $attrdef_nmtokens; + $em->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA')); + $em->content_model = 'em | #PCDATA'; + $em->content_model_type = 'optional'; + $em->descendants_are_inline = true; + $em->safe = true; + + $this->assertEqual( + array('p' => $p, 'em' => $em), + $manager->getElements() ); - $this->manager->setup(HTMLPurifier_Config::createDefault()); + // test trusted parameter override - $modules = array( - 'Module' => $this->manager->modules['Module'], - 'Module2' => $this->manager->modules['Module2'] - ); + $div = new HTMLPurifier_ElementDef(); + $div->child = new HTMLPurifier_ChildDef_Optional(array('p', 'div', 'em', '#PCDATA')); + $div->content_model = 'p | div | em | #PCDATA'; + $div->content_model_type = 'optional'; + $div->descendants_are_inline = false; + $div->safe = false; - $this->assertIdentical( - $this->manager->collections['Default']['Generic Document 0.1'], - $modules - ); - $this->assertIdentical($this->manager->activeModules, $modules); - $this->assertIdentical($this->manager->activeCollections, array('Default')); + $this->assertEqual($div, $manager->getElement('div', true)); } - function untest_addModule_undefinedClass() { - $this->expectError('TotallyCannotBeDefined module does not exist'); - $this->manager->addModule('TotallyCannotBeDefined'); - } - - function untest_addModule_stringExpansion() { - $this->manager->addModule('ManagerTestModule'); - $this->assertIsA($this->manager->modules['ManagerTestModule'], - 'HTMLPurifier_HTMLModule_ManagerTestModule'); - } - - function untest_addPrefix() { - $this->manager->addPrefix('HTMLPurifier_HTMLModuleManagerTest_'); - $this->manager->addModule('TestModule'); - $this->assertIsA($this->manager->modules['TestModule'], - 'HTMLPurifier_HTMLModuleManagerTest_TestModule'); - } - - function assertProcessCollections($input, $expect = false) { - if ($expect === false) $expect = $input; - $this->manager->processCollections($input); - // substitute in modules for $expect - foreach ($expect as $col_i => $col) { - $disable = false; - foreach ($col as $mod_i => $mod) { - unset($expect[$col_i][$mod_i]); - if ($mod_i === '*') { - $disable = true; - continue; - } - $expect[$col_i][$mod] = $this->manager->modules[$mod]; - } - if ($disable) $expect[$col_i]['*'] = false; - } - $this->assertIdentical($input, $expect); - } - - function untestImpl_processCollections() { - $this->manager->initialize(); - $this->assertProcessCollections( - array() - ); - $this->assertProcessCollections( - array('HTML' => array('Text')) - ); - $this->assertProcessCollections( - array('HTML' => array('Text', 'Legacy')) - ); - $this->assertProcessCollections( // order is important! - array('HTML' => array('Legacy', 'Text')), - array('HTML' => array('Text', 'Legacy')) - ); - $this->assertProcessCollections( // privates removed after process - array('_Private' => array('Legacy', 'Text')), - array() - ); - $this->assertProcessCollections( // inclusions come first - array( - 'HTML' => array(array('XHTML'), 'Legacy'), - 'XHTML' => array('Text', 'Hypertext') - ), - array( - 'HTML' => array('Text', 'Hypertext', 'Legacy'), - 'XHTML' => array('Text', 'Hypertext') - ) - ); - $this->assertProcessCollections( - array( - 'HTML' => array(array('_Common'), 'Legacy'), - '_Common' => array('Text', 'Hypertext') - ), - array( - 'HTML' => array('Text', 'Hypertext', 'Legacy') - ) - ); - $this->assertProcessCollections( // nested inclusions - array( - 'Full' => array(array('Minimal'), 'Hypertext'), - 'Minimal' => array(array('Bare'), 'List'), - 'Bare' => array('Text') - ), - array( - 'Full' => array('Text', 'Hypertext', 'List'), - 'Minimal' => array('Text', 'List'), - 'Bare' => array('Text') - ) - ); - // strange but valid stuff that will be handled in assembleModules - $this->assertProcessCollections( - array( - 'Linky' => array('Hypertext'), - 'Listy' => array('List'), - '*' => array('Text') - ) - ); - $this->assertProcessCollections( - array( - 'Linky' => array('Hypertext'), - 'ListyOnly' => array('List', '*' => false), - '*' => array('Text') - ) - ); - } - - function untestImpl_processCollections_error() { - $this->manager->initialize(); - - $this->expectError( // active variables, watch out! - 'Illegal inclusion array at index 1 found collection HTML, '. - 'inclusion arrays must be at start of collection (index 0)'); - $c = array( - 'HTML' => array('Legacy', array('XHTML')), - 'XHTML' => array('Text', 'Hypertext') - - - ); - $this->manager->processCollections($c); - unset($c); - - $this->expectError('Collection HTML references undefined '. - 'module Foobar'); - $c = array( - 'HTML' => array('Foobar') - - - ); - $this->manager->processCollections($c); - unset($c); - - $this->expectError('Collection HTML tried to include undefined '. - 'collection _Common'); - $c = array( - 'HTML' => array(array('_Common'), 'Legacy') - - - ); - $this->manager->processCollections($c); - unset($c); - - // reports the first circular inclusion it runs across - $this->expectError('Circular inclusion detected in HTML collection'); - $c = array( - 'HTML' => array(array('XHTML')), - 'XHTML' => array(array('HTML')) - - - ); - $this->manager->processCollections($c); - unset($c); - - } - - function untest_makeCollection() { - $config = HTMLPurifier_Config::create(array( - 'HTML.Doctype' => 'Custom Doctype' - )); - $this->manager->addModule($this->createModule('ActiveModule')); - $this->manager->addModule($this->createModule('DudModule')); - $this->manager->addModule($this->createModule('ValidModule')); - $ActiveModule = $this->manager->modules['ActiveModule']; - $DudModule = $this->manager->modules['DudModule']; - $ValidModule = $this->manager->modules['ValidModule']; - $this->manager->collections['ToBeValid']['Custom Doctype'] = array('ValidModule'); - $this->manager->collections['ToBeActive']['Custom Doctype'] = array('ActiveModule'); - $this->manager->makeCollectionValid('ToBeValid'); - $this->manager->makeCollectionActive('ToBeActive'); - $this->manager->setup($config); - $this->assertIdentical($this->manager->validModules, array( - 'ValidModule' => $ValidModule, - 'ActiveModule' => $ActiveModule - )); - $this->assertIdentical($this->manager->activeModules, array( - 'ActiveModule' => $ActiveModule - )); - } - - function untest_makeCollection_undefinedCollection() { - $config = HTMLPurifier_Config::create(array( - 'HTML.Doctype' => 'Sweets Document 1.0' - )); - $this->manager->addModule($this->createModule('DonutsModule')); - $this->manager->addModule($this->createModule('ChocolateModule')); - $this->manager->collections['CocoaBased']['Sweets Document 1.0'] = array('ChocolateModule'); - // notice how BreadBased collection is missing - $this->manager->makeCollectionActive('CocoaBased'); // to prevent other errors - $this->manager->makeCollectionValid('BreadBased'); - $this->expectError('BreadBased collection is undefined'); - $this->manager->setup($config); - } - - function untest_soupStuff() { - $config = HTMLPurifier_Config::create(array( - 'HTML.Doctype' => 'The Soup Specification 8.0' - )); - $this->manager->addModule($this->createModule('VegetablesModule')); - $this->manager->addModule($this->createModule('MeatModule')); - - } - - } ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/Strategy/FixNestingTest.php b/tests/HTMLPurifier/Strategy/FixNestingTest.php index 20636614..d61e1713 100644 --- a/tests/HTMLPurifier/Strategy/FixNestingTest.php +++ b/tests/HTMLPurifier/Strategy/FixNestingTest.php @@ -107,7 +107,7 @@ class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness $this->expectError('Cannot use unrecognized element as parent.'); $this->assertResult( - '