PageRenderTime 94ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/query-monitor/collectors/php_errors.php

https://bitbucket.org/Fabio88f/pw_agenzia_viaggi
PHP | 360 lines | 201 code | 55 blank | 104 comment | 26 complexity | 28b83e807b0c187b433ffb3f10e7aaa9 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * PHP error collector.
  4. *
  5. * @package query-monitor
  6. */
  7. class QM_Collector_PHP_Errors extends QM_Collector {
  8. public $id = 'php_errors';
  9. public $types = array();
  10. private $error_reporting = null;
  11. private $display_errors = null;
  12. private static $unexpected_error;
  13. private static $wordpress_couldnt;
  14. public function name() {
  15. return __( 'PHP Errors', 'query-monitor' );
  16. }
  17. public function __construct() {
  18. if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) and QM_DISABLE_ERROR_HANDLER ) {
  19. return;
  20. }
  21. parent::__construct();
  22. set_error_handler( array( $this, 'error_handler' ) );
  23. register_shutdown_function( array( $this, 'shutdown_handler' ) );
  24. $this->error_reporting = error_reporting();
  25. $this->display_errors = ini_get( 'display_errors' );
  26. ini_set( 'display_errors', 0 );
  27. }
  28. public function error_handler( $errno, $message, $file = null, $line = null, $context = null ) {
  29. do_action( 'qm/collect/new_php_error', $errno, $message, $file, $line, $context );
  30. switch ( $errno ) {
  31. case E_WARNING:
  32. case E_USER_WARNING:
  33. $type = 'warning';
  34. break;
  35. case E_NOTICE:
  36. case E_USER_NOTICE:
  37. $type = 'notice';
  38. break;
  39. case E_STRICT:
  40. $type = 'strict';
  41. break;
  42. case E_DEPRECATED:
  43. case E_USER_DEPRECATED:
  44. $type = 'deprecated';
  45. break;
  46. default:
  47. return false;
  48. break;
  49. }
  50. if ( ! class_exists( 'QM_Backtrace' ) ) {
  51. return false;
  52. }
  53. $error_group = 'errors';
  54. if ( 0 === error_reporting() && 0 !== $this->error_reporting ) {
  55. // This is most likely an @-suppressed error
  56. $error_group = 'suppressed';
  57. }
  58. if ( ! isset( self::$unexpected_error ) ) {
  59. // These strings are from core. They're passed through `__()` as variables so they get translated at runtime
  60. // but do not get seen by GlotPress when it populates its database of translatable strings for QM.
  61. $unexpected_error = 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="https://wordpress.org/support/">support forums</a>.';
  62. $wordpress_couldnt = '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)';
  63. self::$unexpected_error = call_user_func( '__', $unexpected_error );
  64. self::$wordpress_couldnt = call_user_func( '__', $wordpress_couldnt );
  65. }
  66. // Intentionally skip reporting these core warnings. They're a distraction when developing offline.
  67. // The failed HTTP request will still appear in QM's output so it's not a big problem hiding these warnings.
  68. if ( self::$unexpected_error === $message ) {
  69. return false;
  70. }
  71. if ( self::$unexpected_error . ' ' . self::$wordpress_couldnt === $message ) {
  72. return false;
  73. }
  74. $trace = new QM_Backtrace( array(
  75. 'ignore_current_filter' => false,
  76. ) );
  77. $caller = $trace->get_caller();
  78. $key = md5( $message . $file . $line . $caller['id'] );
  79. $filename = QM_Util::standard_dir( $file, '' );
  80. if ( isset( $this->data[ $error_group ][ $type ][ $key ] ) ) {
  81. $this->data[ $error_group ][ $type ][ $key ]->calls++;
  82. } else {
  83. $this->data[ $error_group ][ $type ][ $key ] = (object) array(
  84. 'errno' => $errno,
  85. 'type' => $type,
  86. 'message' => $message,
  87. 'file' => $file,
  88. 'filename' => $filename,
  89. 'line' => $line,
  90. 'trace' => $trace,
  91. 'calls' => 1,
  92. );
  93. }
  94. return apply_filters( 'qm/collect/php_errors_return_value', false );
  95. }
  96. public function shutdown_handler() {
  97. $e = error_get_last();
  98. if ( empty( $this->display_errors ) ) {
  99. return;
  100. }
  101. if ( empty( $e ) or ! ( $e['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_RECOVERABLE_ERROR ) ) ) {
  102. return;
  103. }
  104. if ( $e['type'] & E_RECOVERABLE_ERROR ) {
  105. $error = 'Catchable fatal error';
  106. } elseif ( $e['type'] & E_COMPILE_WARNING ) {
  107. $error = 'Warning';
  108. } else {
  109. $error = 'Fatal error';
  110. }
  111. if ( function_exists( 'xdebug_print_function_stack' ) ) {
  112. xdebug_print_function_stack( sprintf( '%1$s: %2$s in %3$s on line %4$d. Output triggered ',
  113. $error,
  114. $e['message'],
  115. $e['file'],
  116. $e['line']
  117. ) );
  118. } else {
  119. printf( // WPCS: XSS ok.
  120. '<br /><b>%1$s</b>: %2$s in <b>%3$s</b> on line <b>%4$d</b><br />',
  121. htmlentities( $error ),
  122. htmlentities( $e['message'] ),
  123. htmlentities( $e['file'] ),
  124. intval( $e['line'] )
  125. );
  126. }
  127. }
  128. public function tear_down() {
  129. parent::tear_down();
  130. ini_set( 'display_errors', $this->display_errors );
  131. restore_error_handler();
  132. }
  133. /**
  134. * Runs post-processing on the collected errors and updates the
  135. * errors collected in the data->errors property.
  136. *
  137. * Any unreportable errors are placed in the data->filtered_errors
  138. * property.
  139. */
  140. public function process() {
  141. $this->types = array(
  142. 'errors' => array(
  143. 'warning' => _x( 'Warning', 'PHP error level', 'query-monitor' ),
  144. 'notice' => _x( 'Notice', 'PHP error level', 'query-monitor' ),
  145. 'strict' => _x( 'Strict', 'PHP error level', 'query-monitor' ),
  146. 'deprecated' => _x( 'Deprecated', 'PHP error level', 'query-monitor' ),
  147. ),
  148. 'suppressed' => array(
  149. 'warning' => _x( 'Warning (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
  150. 'notice' => _x( 'Notice (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
  151. 'strict' => _x( 'Strict (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
  152. 'deprecated' => _x( 'Deprecated (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
  153. ),
  154. 'silenced' => array(
  155. 'warning' => _x( 'Warning (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
  156. 'notice' => _x( 'Notice (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
  157. 'strict' => _x( 'Strict (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
  158. 'deprecated' => _x( 'Deprecated (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
  159. ),
  160. );
  161. $components = array();
  162. if ( ! empty( $this->data ) && ! empty( $this->data['errors'] ) ) {
  163. /**
  164. * Filters the levels used for reported PHP errors on a per-component basis.
  165. *
  166. * Error levels can be specified in order to silence certain error levels from
  167. * plugins or the current theme. Most commonly, you may wish to use this filter
  168. * in order to silence annoying notices from third party plugins that you do not
  169. * have control over.
  170. *
  171. * Silenced errors will still appear in Query Monitor's output, but will not
  172. * cause highlighting to appear in the top level admin toolbar.
  173. *
  174. * For example, to show all errors in the 'foo' plugin except PHP notices use:
  175. *
  176. * add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
  177. * $levels['plugin']['foo'] = ( E_ALL & ~E_NOTICE );
  178. * return $levels;
  179. * } );
  180. *
  181. * Errors from themes, WordPress core, and other components can also be filtered:
  182. *
  183. * add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
  184. * $levels['theme']['stylesheet'] = ( E_WARNING & E_USER_WARNING );
  185. * $levels['theme']['template'] = ( E_WARNING & E_USER_WARNING );
  186. * $levels['core']['core'] = ( 0 );
  187. * return $levels;
  188. * } );
  189. *
  190. * Any component which doesn't have an error level specified via this filter is
  191. * assumed to have the default level of `E_ALL`, which shows all errors.
  192. *
  193. * Valid PHP error level bitmasks are supported for each component, including `0`
  194. * to silence all errors from a component. See the PHP documentation on error
  195. * reporting for more info: http://php.net/manual/en/function.error-reporting.php
  196. *
  197. * @param int[] $levels The error levels used for each component.
  198. */
  199. $levels = apply_filters( 'qm/collect/php_error_levels', array() );
  200. /**
  201. * Controls whether silenced PHP errors are hidden entirely by Query Monitor.
  202. *
  203. * To hide silenced errors, use:
  204. *
  205. * add_filter( 'qm/collect/hide_silenced_php_errors', '__return_true' );
  206. *
  207. * @param bool $hide Whether to hide silenced PHP errors. Default false.
  208. */
  209. $this->hide_silenced_php_errors = apply_filters( 'qm/collect/hide_silenced_php_errors', false );
  210. array_map( array( $this, 'filter_reportable_errors' ), $levels, array_keys( $levels ) );
  211. foreach ( $this->types as $error_group => $error_types ) {
  212. foreach ( $error_types as $type => $title ) {
  213. if ( isset( $this->data[ $error_group ][ $type ] ) ) {
  214. foreach ( $this->data[ $error_group ][ $type ] as $error ) {
  215. $component = $error->trace->get_component();
  216. $components[ $component->name ] = $component->name;
  217. }
  218. }
  219. }
  220. }
  221. }
  222. $this->data['components'] = $components;
  223. }
  224. /**
  225. * Filters the reportable PHP errors using the table specified. Users can customize the levels
  226. * using the `qm/collect/php_error_levels` filter.
  227. *
  228. * @param int[] $components The error levels keyed by component name.
  229. * @param string $component_type The component type, for example 'plugin' or 'theme'.
  230. */
  231. public function filter_reportable_errors( array $components, $component_type ) {
  232. $all_errors = $this->data['errors'];
  233. foreach ( $components as $component_context => $allowed_level ) {
  234. foreach ( $all_errors as $error_level => $errors ) {
  235. foreach ( $errors as $error_id => $error ) {
  236. if ( $this->is_reportable_error( $error->errno, $allowed_level ) ) {
  237. continue;
  238. }
  239. if ( ! $this->is_affected_component( $error->trace->get_component(), $component_type, $component_context ) ) {
  240. continue;
  241. }
  242. unset( $this->data['errors'][ $error_level ][ $error_id ] );
  243. if ( $this->hide_silenced_php_errors ) {
  244. continue;
  245. }
  246. $this->data['silenced'][ $error_level ][ $error_id ] = $error;
  247. }
  248. }
  249. }
  250. $this->data['errors'] = array_filter( $this->data['errors'] );
  251. }
  252. /**
  253. * Checks if the file path is within the specified plugin. This is
  254. * used to scope an error's file path to a plugin.
  255. *
  256. * @param string $plugin_name The name of the plugin
  257. * @param string $file_path The full path to the file
  258. * @return bool
  259. */
  260. public function is_affected_component( $component, $component_type, $component_context ) {
  261. if ( empty( $component ) ) {
  262. return false;
  263. }
  264. return ( $component->type === $component_type && $component->context === $component_context );
  265. }
  266. /**
  267. * Checks if the error number specified is viewable based on the
  268. * flags specified.
  269. *
  270. * @param int $error_no The errno from PHP
  271. * @param int $flags The config flags specified by users
  272. * @return int Truthy int value if reportable else 0.
  273. *
  274. * Eg:- If a plugin had the config flags,
  275. *
  276. * E_ALL & ~E_NOTICE
  277. *
  278. * then,
  279. *
  280. * is_reportable_error( E_NOTICE, E_ALL & ~E_NOTICE ) is false
  281. * is_reportable_error( E_WARNING, E_ALL & ~E_NOTICE ) is true
  282. *
  283. * If the $flag is null, all errors are assumed to be
  284. * reportable by default.
  285. */
  286. public function is_reportable_error( $error_no, $flags ) {
  287. if ( ! is_null( $flags ) ) {
  288. $result = $error_no & $flags;
  289. } else {
  290. $result = 1;
  291. }
  292. return (bool) $result;
  293. }
  294. /**
  295. * For testing purposes only. Sets the errors property manually.
  296. * Needed to test the filter since the data property is protected.
  297. *
  298. * @param array $errors The list of errors
  299. */
  300. public function set_php_errors( $errors ) {
  301. $this->data['errors'] = $errors;
  302. }
  303. }
  304. # Load early to catch early errors
  305. QM_Collectors::add( new QM_Collector_PHP_Errors );