diff --git a/NEWS b/NEWS
index 8d28f6ac..2413cb92 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,10 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
# New compact syntax for AttrDef objects that can be used to instantiate
new objects via make()
! HTML Purifier now works in PHP 4.3.2.
+! Configuration form-editing API makes tweaking HTMLPurifier_Config a
+ breeze!
+! Configuration directives that accept hashes now allow new string
+ format: key1,value1,key2,value2
- Deprecated and removed EnableRedundantUTF8Cleaning. It didn't even work!
. Unit test for ElementDef created, ElementDef behavior modified to
be more flexible
diff --git a/TODO b/TODO
index acf2e7d8..e9fb016a 100644
--- a/TODO
+++ b/TODO
@@ -20,7 +20,6 @@ TODO List
# Reorganize Unit Tests
- Refactor loop tests (esp. AttrDef_URI)
- Parse TinyMCE-style whitelist into our %HTML.Allow* whitelists
- ? HTML interface for tweaking configuration to see changes
1.8 release [Refactor, refactor!]
# URI validation routines tighter (see docs/dev-code-quality.html) (COMPLEX)
diff --git a/configdoc/generate.php b/configdoc/generate.php
index a5b06e96..d01878b8 100644
--- a/configdoc/generate.php
+++ b/configdoc/generate.php
@@ -14,6 +14,11 @@ TODO:
- factor out code into classes
*/
+// there are several hacks for the configForm.php smoketest
+// - load copies (override default schema)
+// - file/line server information hack
+// - post-processing, base-name change hack
+
// ---------------------------------------------------------------------------
// Check and configure environment
@@ -47,7 +52,15 @@ function appendHTMLDiv($document, $node, $html) {
// ---------------------------------------------------------------------------
// Load copies of HTMLPurifier_ConfigDef and HTMLPurifier
-$schema = HTMLPurifier_ConfigSchema::instance();
+// hack
+if (defined('HTMLPURIFIER_CUSTOM_SCHEMA')) {
+ // included from somewhere else
+ $var = HTMLPURIFIER_CUSTOM_SCHEMA;
+ $schema = $$var;
+ chdir(dirname(__FILE__));
+} else {
+ $schema = HTMLPurifier_ConfigSchema::instance();
+}
$purifier = new HTMLPurifier();
@@ -153,8 +166,11 @@ foreach($schema->info as $namespace_name => $namespace_info) {
foreach ($info->descriptions as $file => $file_descriptions) {
foreach ($file_descriptions as $line => $description) {
$dom_description = $dom_document->createElement('description');
- $dom_description->setAttribute('file', $file);
- $dom_description->setAttribute('line', $line);
+ // hack
+ if (!defined('HTMLPURIFIER_CUSTOM_SCHEMA')) {
+ $dom_description->setAttribute('file', $file);
+ $dom_description->setAttribute('line', $line);
+ }
appendHTMLDiv($dom_document, $dom_description, $description);
$dom_descriptions->appendChild($dom_description);
}
@@ -203,9 +219,13 @@ if (class_exists('Tidy')) {
$html_output = (string) $tidy;
}
-// write it to a file (todo: parse into seperate pages)
-file_put_contents("$xsl_stylesheet_name.html", $html_output);
-
+// hack
+if (!defined('HTMLPURIFIER_CUSTOM_SCHEMA')) {
+ // write it to a file (todo: parse into seperate pages)
+ file_put_contents("$xsl_stylesheet_name.html", $html_output);
+} else {
+ $html_output = str_replace('styles/plain.css', HTMLPURIFIER_SCRIPT_LOCATION . 'styles/plain.css', $html_output);
+}
// ---------------------------------------------------------------------------
// Output for instant feedback
diff --git a/configdoc/styles/plain.xsl b/configdoc/styles/plain.xsl
index cfd5a7ca..eb118778 100644
--- a/configdoc/styles/plain.xsl
+++ b/configdoc/styles/plain.xsl
@@ -76,15 +76,17 @@
-
- Used by: |
-
-
- ,
-
-
- |
-
+
+
+ Used by: |
+
+
+ ,
+
+
+ |
+
+
diff --git a/library/HTMLPurifier/Config.php b/library/HTMLPurifier/Config.php
index 3c8d0f52..033f069e 100644
--- a/library/HTMLPurifier/Config.php
+++ b/library/HTMLPurifier/Config.php
@@ -147,6 +147,14 @@ class HTMLPurifier_Config
return $this->conf[$namespace];
}
+ /**
+ * Retrieves all directives, organized by namespace
+ */
+ function getAll() {
+ if (!$this->finalized && $this->autoFinalize) $this->finalize();
+ return $this->conf;
+ }
+
/**
* Sets a value to configuration.
* @param $namespace String namespace
diff --git a/library/HTMLPurifier/ConfigSchema.php b/library/HTMLPurifier/ConfigSchema.php
index 1e58f196..f97ddf05 100644
--- a/library/HTMLPurifier/ConfigSchema.php
+++ b/library/HTMLPurifier/ConfigSchema.php
@@ -8,6 +8,7 @@ require_once 'HTMLPurifier/ConfigDef/DirectiveAlias.php';
/**
* Configuration definition, defines directives and their defaults.
+ * @note If you update this, please update Printer_ConfigForm
* @todo The ability to define things multiple times is confusing and should
* be factored out to its own function named registerDependency() or
* addNote(), where only the namespace.name and an extra descriptions
@@ -304,6 +305,7 @@ class HTMLPurifier_ConfigSchema {
if ($allow_null && $var === null) return null;
switch ($type) {
case 'mixed':
+ //if (is_string($var)) $var = unserialize($var);
return $var;
case 'istring':
case 'string':
@@ -344,6 +346,14 @@ class HTMLPurifier_ConfigSchema {
$var = explode(',',$var);
// remove spaces
foreach ($var as $i => $j) $var[$i] = trim($j);
+ if ($type === 'hash') {
+ // key,value,key,value
+ $nvar = array();
+ for ($i = 0, $c = count($var); $i + 1 < $c; $i += 2) {
+ $nvar[$var[$i]] = $var[$i + 1];
+ }
+ $var = $nvar;
+ }
}
if (!is_array($var)) break;
$keys = array_keys($var);
diff --git a/library/HTMLPurifier/Printer.php b/library/HTMLPurifier/Printer.php
index 8325ff45..95be17a2 100644
--- a/library/HTMLPurifier/Printer.php
+++ b/library/HTMLPurifier/Printer.php
@@ -28,9 +28,9 @@ class HTMLPurifier_Printer
/**
* Main function that renders object or aspect of that object
- * @param $config Configuration object
+ * @note Parameters vary depending on printer
*/
- function render($config) {}
+ // function render() {}
/**
* Returns a start tag
@@ -66,6 +66,18 @@ class HTMLPurifier_Printer
$this->end($tag);
}
+ function elementEmpty($tag, $attr = array()) {
+ return $this->generator->generateFromToken(
+ new HTMLPurifier_Token_Empty($tag, $attr)
+ );
+ }
+
+ function text($text) {
+ return $this->generator->generateFromToken(
+ new HTMLPurifier_Token_Text($text)
+ );
+ }
+
/**
* Prints a simple key/value row in a table.
* @param $name Key
diff --git a/library/HTMLPurifier/Printer/ConfigForm.css b/library/HTMLPurifier/Printer/ConfigForm.css
new file mode 100644
index 00000000..f9c9c936
--- /dev/null
+++ b/library/HTMLPurifier/Printer/ConfigForm.css
@@ -0,0 +1,7 @@
+
+.hp-config {}
+
+.hp-config tbody th {text-align:right;}
+.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;}
+.hp-config .namespace th {text-align:center;}
+.hp-config .verbose {display:none;}
diff --git a/library/HTMLPurifier/Printer/ConfigForm.js b/library/HTMLPurifier/Printer/ConfigForm.js
new file mode 100644
index 00000000..119ca4a0
--- /dev/null
+++ b/library/HTMLPurifier/Printer/ConfigForm.js
@@ -0,0 +1,3 @@
+function toggleWriteability(id_of_patient, checked) {
+ document.getElementById(id_of_patient).disabled = checked;
+}
\ No newline at end of file
diff --git a/library/HTMLPurifier/Printer/ConfigForm.php b/library/HTMLPurifier/Printer/ConfigForm.php
new file mode 100644
index 00000000..c579dd99
--- /dev/null
+++ b/library/HTMLPurifier/Printer/ConfigForm.php
@@ -0,0 +1,202 @@
+docURL = $doc_url;
+ $this->fields['default'] = new HTMLPurifier_Printer_ConfigForm_default();
+ $this->fields['bool'] = new HTMLPurifier_Printer_ConfigForm_bool();
+ }
+
+ function render($config) {
+ $this->config = $config;
+ $all = $config->getAll();
+ $ret = '';
+ $ret .= $this->start('table', array('class' => 'hp-config'));
+ $ret .= $this->start('thead');
+ $ret .= $this->start('tr');
+ $ret .= $this->element('th', 'Directive');
+ $ret .= $this->element('th', 'Value');
+ $ret .= $this->end('tr');
+ $ret .= $this->end('thead');
+ foreach ($all as $ns => $directives) {
+ $ret .= $this->renderNamespace($ns, $directives);
+ }
+ $ret .= $this->end('table');
+ return $ret;
+ }
+
+ function renderNamespace($ns, $directives) {
+ $ret = '';
+ $ret .= $this->start('tbody', array('class' => 'namespace'));
+ $ret .= $this->start('tr');
+ $ret .= $this->element('th', $ns, array('colspan' => 2));
+ $ret .= $this->end('tr');
+ $ret .= $this->end('tbody');
+ $ret .= $this->start('tbody');
+ foreach ($directives as $directive => $value) {
+ $ret .= $this->start('tr');
+ $ret .= $this->start('th');
+ if ($this->docURL) $ret .= $this->start('a', array('href' => $this->docURL . "#$ns.$directive"));
+ $ret .= $this->element(
+ 'label',
+ "%$ns.$directive",
+ array('for' => "$ns.$directive")
+ );
+ if ($this->docURL) $ret .= $this->end('a');
+ $ret .= $this->end('th');
+
+ $ret .= $this->start('td');
+ $def = $this->config->def->info[$ns][$directive];
+ $type = $def->type;
+ if (!isset($this->fields[$type])) $type = 'default';
+ $type_obj = $this->fields[$type];
+ if ($def->allow_null) {
+ $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
+ }
+ $ret .= $type_obj->render($ns, $directive, $value, $this->config);
+ $ret .= $this->end('td');
+ $ret .= $this->end('tr');
+ }
+ $ret .= $this->end('tbody');
+ return $ret;
+ }
+
+}
+
+class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer {
+ var $obj;
+ function HTMLPurifier_Printer_ConfigForm_NullDecorator($obj) {
+ parent::HTMLPurifier_Printer();
+ $this->obj = $obj;
+ }
+ function render($ns, $directive, $value, $config) {
+ $ret = '';
+ $ret .= $this->start('label', array('for' => "Null_$ns.$directive"));
+ $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+ $ret .= $this->text(' Null/Disabled');
+ $ret .= $this->end('label');
+ $attr = array(
+ 'type' => 'checkbox',
+ 'value' => '1',
+ 'class' => 'null-toggle',
+ 'name' => "Null_$ns.$directive",
+ 'id' => "Null_$ns.$directive",
+ 'onclick' => "toggleWriteability('$ns.$directive',checked)" // INLINE JAVASCRIPT!!!!
+ );
+ if ($value === null) $attr['checked'] = 'checked';
+ $ret .= $this->elementEmpty('input', $attr);
+ $ret .= $this->text(' or ');
+ $ret .= $this->elementEmpty('br');
+ $ret .= $this->obj->render($ns, $directive, $value, $config);
+ return $ret;
+ }
+}
+
+class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
+ function render($ns, $directive, $value, $config) {
+ $ret = '';
+ $def = $config->def->info[$ns][$directive];
+ if (is_array($value)) {
+ switch ($def->type) {
+ case 'lookup':
+ $array = $value;
+ $value = array();
+ foreach ($array as $val => $b) {
+ $value[] = $val;
+ }
+ case 'list':
+ $value = implode(',', $value);
+ break;
+ case 'hash':
+ $nvalue = '';
+ foreach ($value as $i => $v) {
+ $nvalue .= "$i,$v,";
+ }
+ $value = $nvalue;
+ break;
+ default:
+ $value = '';
+ }
+ }
+ if ($def->type === 'mixed') {
+ return 'Not supported';
+ $value = serialize($value);
+ }
+ $attr = array(
+ 'type' => 'text',
+ 'name' => "$ns.$directive",
+ 'id' => "$ns.$directive"
+ );
+ if ($value === null) $attr['disabled'] = 'disabled';
+ if (is_array($def->allowed)) {
+ $ret .= $this->start('select', $attr);
+ foreach ($def->allowed as $val => $b) {
+ $attr = array();
+ if ($value == $val) $attr['selected'] = 'selected';
+ $ret .= $this->element('option', $val, $attr);
+ }
+ $ret .= $this->end('select');
+ } else {
+ $attr['value'] = $value;
+ $ret .= $this->elementEmpty('input', $attr);
+ }
+ return $ret;
+ }
+}
+
+class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer {
+ function render($ns, $directive, $value, $config) {
+ $ret = '';
+
+ $ret .= $this->start('div', array('id' => "$ns.$directive"));
+
+ $ret .= $this->start('label', array('for' => "Yes_$ns.$directive"));
+ $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+ $ret .= $this->text(' Yes');
+ $ret .= $this->end('label');
+
+ $attr = array(
+ 'type' => 'radio',
+ 'name' => "Yes_$ns.$directive",
+ 'id' => "Yes_$ns.$directive",
+ 'value' => '1'
+ );
+ if ($value) $attr['checked'] = 'checked';
+ $ret .= $this->elementEmpty('input', $attr);
+
+ $ret .= $this->start('label', array('for' => "No_$ns.$directive"));
+ $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+ $ret .= $this->text(' No');
+ $ret .= $this->end('label');
+
+ $attr = array(
+ 'type' => 'radio',
+ 'name' => "No_$ns.$directive",
+ 'id' => "No_$ns.$directive",
+ 'value' => '0'
+ );
+ if (!$value) $attr['checked'] = 'checked';
+ $ret .= $this->elementEmpty('input', $attr);
+
+ $ret .= $this->end('div');
+
+ return $ret;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/smoketests/all.php b/smoketests/all.php
index 743440be..de09412c 100644
--- a/smoketests/all.php
+++ b/smoketests/all.php
@@ -29,6 +29,7 @@ while (false !== ($filename = readdir($dh))) {
if (strpos($filename, '.php') === false) continue;
if ($filename == 'common.php') continue;
if ($filename == 'all.php') continue;
+ if ($filename == 'testSchema.php') continue;
?>
+
+
+ HTML Purifier Config Form Smoketest
+
+
+
+
+
+HTML Purifier Config Form Smoketest
+This file outputs the configuration form for every single type
+of directive possible.
+
+
+getAll());
+?>
+
+
+
\ No newline at end of file
diff --git a/smoketests/testSchema.php b/smoketests/testSchema.php
new file mode 100644
index 00000000..1295566e
--- /dev/null
+++ b/smoketests/testSchema.php
@@ -0,0 +1,44 @@
+ true, '2' => true, '3' => true), 'lookup',
+ 'What numbers of neutrons for this element have been observed?');
+CS::define('Element', 'Traits', array('nonmetallic', 'odorless', 'flammable'), 'list',
+ 'What are general properties of the element?');
+CS::define('Element', 'IsotopeNames', array('1' => 'protium', '2' => 'deuterium', '3' => 'tritium'), 'hash',
+ 'Lookup hash of neutron counts to formal names.');
+
+CS::defineNamespace('Instrument', 'Of the musical type.');
+
+CS::define('Instrument', 'Manufacturer', 'Yamaha', 'string', 'Who made it?');
+CS::defineAllowedValues('Instrument', 'Manufacturer', array(
+ 'Yamaha', 'Conn-Selmer', 'Vandoren', 'Laubin', 'Buffet', 'other'));
+CS::defineValueAliases('Instrument', 'Manufacturer', array(
+ 'Selmer' => 'Conn-Selmer'));
+
+CS::define('Instrument', 'Family', 'woodwind', 'istring', 'What family is it?');
+CS::defineAllowedValues('Instrument', 'Family', array(
+ 'brass', 'woodwind', 'percussion', 'string', 'keyboard', 'electronic'));
+CS::defineValueAliases('Instrument', 'Family', array(
+ 'synth' => 'electronic'));
+
+CS::defineNamespace('ReportCard', 'It is for grades.');
+CS::define('ReportCard', 'English', null, 'string/null', 'Grade from English class.');
+CS::define('ReportCard', 'Absences', 0, 'int', 'How many times missing from school?');
+
+?>
\ No newline at end of file
diff --git a/tests/HTMLPurifier/ConfigSchemaTest.php b/tests/HTMLPurifier/ConfigSchemaTest.php
index 8a20988f..56f23b76 100644
--- a/tests/HTMLPurifier/ConfigSchemaTest.php
+++ b/tests/HTMLPurifier/ConfigSchemaTest.php
@@ -288,6 +288,8 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase
$this->assertValid(array(1 => 'moo'), 'hash');
$this->assertInvalid(array(0 => 'moo'), 'hash');
$this->assertValid('', 'hash', array());
+ $this->assertValid('foo,bar,too,two', 'hash', array('foo' => 'bar', 'too' => 'two'));
+ $this->assertValid('foo,bar,too', 'hash', array('foo' => 'bar'));
$this->assertValid(23, 'mixed');