ErrorCollector.php 7.2 KB

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