You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
7.4KB

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