1<?php
2
3/**
4 * @todo Rewrite to use Interchange objects
5 */
6class 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 */
210class 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 */
278class 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 */
385class 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