1<?php
2
3/**
4 * Error collection class that enables HTML Purifier to report HTML
5 * problems back to the user
6 */
7class HTMLPurifier_ErrorCollector
8{
9
10    /**
11     * Identifiers for the returned error array. These are purposely numeric
12     * so list() can be used.
13     */
14    const LINENO   = 0;
15    const SEVERITY = 1;
16    const MESSAGE  = 2;
17    const CHILDREN = 3;
18
19    /**
20     * @type array
21     */
22    protected $errors;
23
24    /**
25     * @type array
26     */
27    protected $_current;
28
29    /**
30     * @type array
31     */
32    protected $_stacks = array(array());
33
34    /**
35     * @type HTMLPurifier_Language
36     */
37    protected $locale;
38
39    /**
40     * @type HTMLPurifier_Generator
41     */
42    protected $generator;
43
44    /**
45     * @type HTMLPurifier_Context
46     */
47    protected $context;
48
49    /**
50     * @type array
51     */
52    protected $lines = array();
53
54    /**
55     * @param HTMLPurifier_Context $context
56     */
57    public function __construct($context)
58    {
59        $this->locale    =& $context->get('Locale');
60        $this->context   = $context;
61        $this->_current  =& $this->_stacks[0];
62        $this->errors    =& $this->_stacks[0];
63    }
64
65    /**
66     * Sends an error message to the collector for later use
67     * @param int $severity Error severity, PHP error style (don't use E_USER_)
68     * @param string $msg Error message text
69     */
70    public function send($severity, $msg)
71    {
72        $args = array();
73        if (func_num_args() > 2) {
74            $args = func_get_args();
75            array_shift($args);
76            unset($args[0]);
77        }
78
79        $token = $this->context->get('CurrentToken', true);
80        $line  = $token ? $token->line : $this->context->get('CurrentLine', true);
81        $col   = $token ? $token->col  : $this->context->get('CurrentCol', true);
82        $attr  = $this->context->get('CurrentAttr', true);
83
84        // perform special substitutions, also add custom parameters
85        $subst = array();
86        if (!is_null($token)) {
87            $args['CurrentToken'] = $token;
88        }
89        if (!is_null($attr)) {
90            $subst['$CurrentAttr.Name'] = $attr;
91            if (isset($token->attr[$attr])) {
92                $subst['$CurrentAttr.Value'] = $token->attr[$attr];
93            }
94        }
95
96        if (empty($args)) {
97            $msg = $this->locale->getMessage($msg);
98        } else {
99            $msg = $this->locale->formatMessage($msg, $args);
100        }
101
102        if (!empty($subst)) {
103            $msg = strtr($msg, $subst);
104        }
105
106        // (numerically indexed)
107        $error = array(
108            self::LINENO   => $line,
109            self::SEVERITY => $severity,
110            self::MESSAGE  => $msg,
111            self::CHILDREN => array()
112        );
113        $this->_current[] = $error;
114
115        // NEW CODE BELOW ...
116        // Top-level errors are either:
117        //  TOKEN type, if $value is set appropriately, or
118        //  "syntax" type, if $value is null
119        $new_struct = new HTMLPurifier_ErrorStruct();
120        $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
121        if ($token) {
122            $new_struct->value = clone $token;
123        }
124        if (is_int($line) && is_int($col)) {
125            if (isset($this->lines[$line][$col])) {
126                $struct = $this->lines[$line][$col];
127            } else {
128                $struct = $this->lines[$line][$col] = $new_struct;
129            }
130            // These ksorts may present a performance problem
131            ksort($this->lines[$line], SORT_NUMERIC);
132        } else {
133            if (isset($this->lines[-1])) {
134                $struct = $this->lines[-1];
135            } else {
136                $struct = $this->lines[-1] = $new_struct;
137            }
138        }
139        ksort($this->lines, SORT_NUMERIC);
140
141        // Now, check if we need to operate on a lower structure
142        if (!empty($attr)) {
143            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
144            if (!$struct->value) {
145                $struct->value = array($attr, 'PUT VALUE HERE');
146            }
147        }
148        if (!empty($cssprop)) {
149            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
150            if (!$struct->value) {
151                // if we tokenize CSS this might be a little more difficult to do
152                $struct->value = array($cssprop, 'PUT VALUE HERE');
153            }
154        }
155
156        // Ok, structs are all setup, now time to register the error
157        $struct->addError($severity, $msg);
158    }
159
160    /**
161     * Retrieves raw error data for custom formatter to use
162     */
163    public function getRaw()
164    {
165        return $this->errors;
166    }
167
168    /**
169     * Default HTML formatting implementation for error messages
170     * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
171     * @param array $errors Errors array to display; used for recursion.
172     * @return string
173     */
174    public function getHTMLFormatted($config, $errors = null)
175    {
176        $ret = array();
177
178        $this->generator = new HTMLPurifier_Generator($config, $this->context);
179        if ($errors === null) {
180            $errors = $this->errors;
181        }
182
183        // 'At line' message needs to be removed
184
185        // generation code for new structure goes here. It needs to be recursive.
186        foreach ($this->lines as $line => $col_array) {
187            if ($line == -1) {
188                continue;
189            }
190            foreach ($col_array as $col => $struct) {
191                $this->_renderStruct($ret, $struct, $line, $col);
192            }
193        }
194        if (isset($this->lines[-1])) {
195            $this->_renderStruct($ret, $this->lines[-1]);
196        }
197
198        if (empty($errors)) {
199            return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
200        } else {
201            return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
202        }
203
204    }
205
206    private function _renderStruct(&$ret, $struct, $line = null, $col = null)
207    {
208        $stack = array($struct);
209        $context_stack = array(array());
210        while ($current = array_pop($stack)) {
211            $context = array_pop($context_stack);
212            foreach ($current->errors as $error) {
213                list($severity, $msg) = $error;
214                $string = '';
215                $string .= '<div>';
216                // W3C uses an icon to indicate the severity of the error.
217                $error = $this->locale->getErrorName($severity);
218                $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
219                if (!is_null($line) && !is_null($col)) {
220                    $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
221                } else {
222                    $string .= '<em class="location">End of Document: </em> ';
223                }
224                $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
225                $string .= '</div>';
226                // Here, have a marker for the character on the column appropriate.
227                // Be sure to clip extremely long lines.
228                //$string .= '<pre>';
229                //$string .= '';
230                //$string .= '</pre>';
231                $ret[] = $string;
232            }
233            foreach ($current->children as $array) {
234                $context[] = $current;
235                $stack = array_merge($stack, array_reverse($array, true));
236                for ($i = count($array); $i > 0; $i--) {
237                    $context_stack[] = $context;
238                }
239            }
240        }
241    }
242}
243
244// vim: et sw=4 sts=4
245