1: <?php
2:
3: /**
4: * @todo Rewrite to use Interchange objects
5: */
6: class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
7: {
8:
9: /**
10: * Printers for specific fields.
11: * @type HTMLPurifier_Printer[]
12: */
13: protected $fields = array();
14:
15: /**
16: * Documentation URL, can have fragment tagged on end.
17: * @type string
18: */
19: protected $docURL;
20:
21: /**
22: * Name of form element to stuff config in.
23: * @type string
24: */
25: protected $name;
26:
27: /**
28: * Whether or not to compress directive names, clipping them off
29: * after a certain amount of letters. False to disable or integer letters
30: * before clipping.
31: * @type bool
32: */
33: protected $compress = false;
34:
35: /**
36: * @param string $name Form element name for directives to be stuffed into
37: * @param string $doc_url String documentation URL, will have fragment tagged on
38: * @param bool $compress Integer max length before compressing a directive name, set to false to turn off
39: */
40: public function __construct(
41: $name,
42: $doc_url = null,
43: $compress = false
44: ) {
45: parent::__construct();
46: $this->docURL = $doc_url;
47: $this->name = $name;
48: $this->compress = $compress;
49: // initialize sub-printers
50: $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default();
51: $this->fields[HTMLPurifier_VarParser::C_BOOL] = new HTMLPurifier_Printer_ConfigForm_bool();
52: }
53:
54: /**
55: * Sets default column and row size for textareas in sub-printers
56: * @param $cols Integer columns of textarea, null to use default
57: * @param $rows Integer rows of textarea, null to use default
58: */
59: public function setTextareaDimensions($cols = null, $rows = null)
60: {
61: if ($cols) {
62: $this->fields['default']->cols = $cols;
63: }
64: if ($rows) {
65: $this->fields['default']->rows = $rows;
66: }
67: }
68:
69: /**
70: * Retrieves styling, in case it is not accessible by webserver
71: */
72: public static function getCSS()
73: {
74: return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
75: }
76:
77: /**
78: * Retrieves JavaScript, in case it is not accessible by webserver
79: */
80: public static function getJavaScript()
81: {
82: return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
83: }
84:
85: /**
86: * Returns HTML output for a configuration form
87: * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array
88: * where [0] has an HTML namespace and [1] is being rendered.
89: * @param array|bool $allowed Optional namespace(s) and directives to restrict form to.
90: * @param bool $render_controls
91: * @return string
92: */
93: public function render($config, $allowed = true, $render_controls = true)
94: {
95: if (is_array($config) && isset($config[0])) {
96: $gen_config = $config[0];
97: $config = $config[1];
98: } else {
99: $gen_config = $config;
100: }
101:
102: $this->config = $config;
103: $this->genConfig = $gen_config;
104: $this->prepareGenerator($gen_config);
105:
106: $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def);
107: $all = array();
108: foreach ($allowed as $key) {
109: list($ns, $directive) = $key;
110: $all[$ns][$directive] = $config->get($ns . '.' . $directive);
111: }
112:
113: $ret = '';
114: $ret .= $this->start('table', array('class' => 'hp-config'));
115: $ret .= $this->start('thead');
116: $ret .= $this->start('tr');
117: $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive'));
118: $ret .= $this->element('th', 'Value', array('class' => 'hp-value'));
119: $ret .= $this->end('tr');
120: $ret .= $this->end('thead');
121: foreach ($all as $ns => $directives) {
122: $ret .= $this->renderNamespace($ns, $directives);
123: }
124: if ($render_controls) {
125: $ret .= $this->start('tbody');
126: $ret .= $this->start('tr');
127: $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
128: $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
129: $ret .= '[<a href="?">Reset</a>]';
130: $ret .= $this->end('td');
131: $ret .= $this->end('tr');
132: $ret .= $this->end('tbody');
133: }
134: $ret .= $this->end('table');
135: return $ret;
136: }
137:
138: /**
139: * Renders a single namespace
140: * @param $ns String namespace name
141: * @param array $directives array of directives to values
142: * @return string
143: */
144: protected function renderNamespace($ns, $directives)
145: {
146: $ret = '';
147: $ret .= $this->start('tbody', array('class' => 'namespace'));
148: $ret .= $this->start('tr');
149: $ret .= $this->element('th', $ns, array('colspan' => 2));
150: $ret .= $this->end('tr');
151: $ret .= $this->end('tbody');
152: $ret .= $this->start('tbody');
153: foreach ($directives as $directive => $value) {
154: $ret .= $this->start('tr');
155: $ret .= $this->start('th');
156: if ($this->docURL) {
157: $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL);
158: $ret .= $this->start('a', array('href' => $url));
159: }
160: $attr = array('for' => "{$this->name}:$ns.$directive");
161:
162: // crop directive name if it's too long
163: if (!$this->compress || (strlen($directive) < $this->compress)) {
164: $directive_disp = $directive;
165: } else {
166: $directive_disp = substr($directive, 0, $this->compress - 2) . '...';
167: $attr['title'] = $directive;
168: }
169:
170: $ret .= $this->element(
171: 'label',
172: $directive_disp,
173: // component printers must create an element with this id
174: $attr
175: );
176: if ($this->docURL) {
177: $ret .= $this->end('a');
178: }
179: $ret .= $this->end('th');
180:
181: $ret .= $this->start('td');
182: $def = $this->config->def->info["$ns.$directive"];
183: if (is_int($def)) {
184: $allow_null = $def < 0;
185: $type = abs($def);
186: } else {
187: $type = $def->type;
188: $allow_null = isset($def->allow_null);
189: }
190: if (!isset($this->fields[$type])) {
191: $type = 0;
192: } // default
193: $type_obj = $this->fields[$type];
194: if ($allow_null) {
195: $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
196: }
197: $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
198: $ret .= $this->end('td');
199: $ret .= $this->end('tr');
200: }
201: $ret .= $this->end('tbody');
202: return $ret;
203: }
204:
205: }
206:
207: /**
208: * Printer decorator for directives that accept null
209: */
210: class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
211: {
212: /**
213: * Printer being decorated
214: * @type HTMLPurifier_Printer
215: */
216: protected $obj;
217:
218: /**
219: * @param HTMLPurifier_Printer $obj Printer to decorate
220: */
221: public function __construct($obj)
222: {
223: parent::__construct();
224: $this->obj = $obj;
225: }
226:
227: /**
228: * @param string $ns
229: * @param string $directive
230: * @param string $value
231: * @param string $name
232: * @param HTMLPurifier_Config|array $config
233: * @return string
234: */
235: public function render($ns, $directive, $value, $name, $config)
236: {
237: if (is_array($config) && isset($config[0])) {
238: $gen_config = $config[0];
239: $config = $config[1];
240: } else {
241: $gen_config = $config;
242: }
243: $this->prepareGenerator($gen_config);
244:
245: $ret = '';
246: $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive"));
247: $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
248: $ret .= $this->text(' Null/Disabled');
249: $ret .= $this->end('label');
250: $attr = array(
251: 'type' => 'checkbox',
252: 'value' => '1',
253: 'class' => 'null-toggle',
254: 'name' => "$name" . "[Null_$ns.$directive]",
255: 'id' => "$name:Null_$ns.$directive",
256: 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!!
257: );
258: if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) {
259: // modify inline javascript slightly
260: $attr['onclick'] =
261: "toggleWriteability('$name:Yes_$ns.$directive',checked);" .
262: "toggleWriteability('$name:No_$ns.$directive',checked)";
263: }
264: if ($value === null) {
265: $attr['checked'] = 'checked';
266: }
267: $ret .= $this->elementEmpty('input', $attr);
268: $ret .= $this->text(' or ');
269: $ret .= $this->elementEmpty('br');
270: $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config));
271: return $ret;
272: }
273: }
274:
275: /**
276: * Swiss-army knife configuration form field printer
277: */
278: class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer
279: {
280: /**
281: * @type int
282: */
283: public $cols = 18;
284:
285: /**
286: * @type int
287: */
288: public $rows = 5;
289:
290: /**
291: * @param string $ns
292: * @param string $directive
293: * @param string $value
294: * @param string $name
295: * @param HTMLPurifier_Config|array $config
296: * @return string
297: */
298: public function render($ns, $directive, $value, $name, $config)
299: {
300: if (is_array($config) && isset($config[0])) {
301: $gen_config = $config[0];
302: $config = $config[1];
303: } else {
304: $gen_config = $config;
305: }
306: $this->prepareGenerator($gen_config);
307: // this should probably be split up a little
308: $ret = '';
309: $def = $config->def->info["$ns.$directive"];
310: if (is_int($def)) {
311: $type = abs($def);
312: } else {
313: $type = $def->type;
314: }
315: if (is_array($value)) {
316: switch ($type) {
317: case HTMLPurifier_VarParser::LOOKUP:
318: $array = $value;
319: $value = array();
320: foreach ($array as $val => $b) {
321: $value[] = $val;
322: }
323: //TODO does this need a break?
324: case HTMLPurifier_VarParser::ALIST:
325: $value = implode(PHP_EOL, $value);
326: break;
327: case HTMLPurifier_VarParser::HASH:
328: $nvalue = '';
329: foreach ($value as $i => $v) {
330: if (is_array($v)) {
331: // HACK
332: $v = implode(";", $v);
333: }
334: $nvalue .= "$i:$v" . PHP_EOL;
335: }
336: $value = $nvalue;
337: break;
338: default:
339: $value = '';
340: }
341: }
342: if ($type === HTMLPurifier_VarParser::C_MIXED) {
343: return 'Not supported';
344: $value = serialize($value);
345: }
346: $attr = array(
347: 'name' => "$name" . "[$ns.$directive]",
348: 'id' => "$name:$ns.$directive"
349: );
350: if ($value === null) {
351: $attr['disabled'] = 'disabled';
352: }
353: if (isset($def->allowed)) {
354: $ret .= $this->start('select', $attr);
355: foreach ($def->allowed as $val => $b) {
356: $attr = array();
357: if ($value == $val) {
358: $attr['selected'] = 'selected';
359: }
360: $ret .= $this->element('option', $val, $attr);
361: }
362: $ret .= $this->end('select');
363: } elseif ($type === HTMLPurifier_VarParser::TEXT ||
364: $type === HTMLPurifier_VarParser::ITEXT ||
365: $type === HTMLPurifier_VarParser::ALIST ||
366: $type === HTMLPurifier_VarParser::HASH ||
367: $type === HTMLPurifier_VarParser::LOOKUP) {
368: $attr['cols'] = $this->cols;
369: $attr['rows'] = $this->rows;
370: $ret .= $this->start('textarea', $attr);
371: $ret .= $this->text($value);
372: $ret .= $this->end('textarea');
373: } else {
374: $attr['value'] = $value;
375: $attr['type'] = 'text';
376: $ret .= $this->elementEmpty('input', $attr);
377: }
378: return $ret;
379: }
380: }
381:
382: /**
383: * Bool form field printer
384: */
385: class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer
386: {
387: /**
388: * @param string $ns
389: * @param string $directive
390: * @param string $value
391: * @param string $name
392: * @param HTMLPurifier_Config|array $config
393: * @return string
394: */
395: public function render($ns, $directive, $value, $name, $config)
396: {
397: if (is_array($config) && isset($config[0])) {
398: $gen_config = $config[0];
399: $config = $config[1];
400: } else {
401: $gen_config = $config;
402: }
403: $this->prepareGenerator($gen_config);
404: $ret = '';
405: $ret .= $this->start('div', array('id' => "$name:$ns.$directive"));
406:
407: $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive"));
408: $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
409: $ret .= $this->text(' Yes');
410: $ret .= $this->end('label');
411:
412: $attr = array(
413: 'type' => 'radio',
414: 'name' => "$name" . "[$ns.$directive]",
415: 'id' => "$name:Yes_$ns.$directive",
416: 'value' => '1'
417: );
418: if ($value === true) {
419: $attr['checked'] = 'checked';
420: }
421: if ($value === null) {
422: $attr['disabled'] = 'disabled';
423: }
424: $ret .= $this->elementEmpty('input', $attr);
425:
426: $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive"));
427: $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
428: $ret .= $this->text(' No');
429: $ret .= $this->end('label');
430:
431: $attr = array(
432: 'type' => 'radio',
433: 'name' => "$name" . "[$ns.$directive]",
434: 'id' => "$name:No_$ns.$directive",
435: 'value' => '0'
436: );
437: if ($value === false) {
438: $attr['checked'] = 'checked';
439: }
440: if ($value === null) {
441: $attr['disabled'] = 'disabled';
442: }
443: $ret .= $this->elementEmpty('input', $attr);
444:
445: $ret .= $this->end('div');
446:
447: return $ret;
448: }
449: }
450:
451: // vim: et sw=4 sts=4
452: