1: <?php
2:
3: /**
4: * Error collection class that enables HTML Purifier to report HTML
5: * problems back to the user
6: */
7: class 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: