Merge branch '4.4' into master
[civicrm-core.git] / CRM / Queue / ErrorPolicy.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
06b69b18 4 | CiviCRM version 4.5 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 * To ensure that PHP errors or unhandled exceptions are reported in JSON format,
30 * wrap this around your code. For example:
31 *
32 * @code
33 * $errorContainer = new CRM_Queue_ErrorPolicy();
34 * $errorContainer->call(function(){
35 * ...include some files, do some work, etc...
36 * });
37 * @endcode
38 *
39 * Note: Most of the code in this class is pretty generic vis-a-vis error
40 * handling -- except for 'reportError', whose message format is only
41 * appropriate for use with the CRM_Queue_Page_AJAX. Some kind of cleanup
42 * will be necessary to get reuse from the other parts of this class.
43 */
44class CRM_Queue_ErrorPolicy {
45 var $active;
46 function __construct($level = NULL) {
47 register_shutdown_function(array($this, 'onShutdown'));
48 if ($level === NULL) {
49 $level = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
50 }
51 $this->level = $level;
52 }
53
54 function activate() {
55 $this->active = TRUE;
56 $this->backup = array();
57 foreach (array(
58 'display_errors', 'html_errors', 'xmlrpc_errors') as $key) {
59 $this->backup[$key] = ini_get($key);
60 ini_set($key, 0);
61 }
62 set_error_handler(array($this, 'onError'), $this->level);
63 // FIXME make this temporary/reversible
64 $this->errorScope = CRM_Core_TemporaryErrorScope::useException();
65 }
66
67 function deactivate() {
68 $this->errorScope = NULL;
69 restore_error_handler();
70 foreach (array(
71 'display_errors', 'html_errors', 'xmlrpc_errors') as $key) {
72 ini_set($key, $this->backup[$key]);
73 }
74 $this->active = FALSE;
75 }
76
77 function call($callable) {
78 $this->activate();
79 try {
80 $result = $callable();
81 }
82 catch(Exception$e) {
83 $this->reportException($e);
84 }
85 $this->deactivate();
86 return $result;
87 }
88
89 /**
90 * Receive (semi) recoverable error notices
91 *
92 * @see set_error_handler
93 */
94 function onError($errno, $errstr, $errfile, $errline) {
95 if (!(error_reporting() & $errno)) {
96 return TRUE;
97 }
98 throw new Exception(sprintf('PHP Error %s at %s:%s: %s', $errno, $errfile, $errline, $errstr));
99 }
100
101 /**
102 * Receive non-recoverable error notices
103 *
104 * @see register_shutdown_function
105 * @see error_get_last
106 */
107 function onShutdown() {
108 if (!$this->active) {
109 return;
110 }
111 $error = error_get_last();
112 if (is_array($error) && ($error['type'] & $this->level)) {
113 $this->reportError($error);
114 }
115 }
116
117 /**
118 * Print a fatal error
119 *
120 * @param $error
121 */
122 function reportError($error) {
123 $response = array(
124 'is_error' => 1,
125 'is_continue' => 0,
126 'exception' => htmlentities(sprintf('Error %s: %s in %s, line %s', $error['type'], $error['message'], $error['file'], $error['line'])),
127 );
128 global $activeQueueRunner;
129 if (is_object($activeQueueRunner)) {
130 $response['last_task_title'] = $activeQueueRunner->lastTaskTitle;
131 }
132 CRM_Core_Error::debug_var('CRM_Queue_ErrorPolicy_reportError', $response);
133 echo json_encode($response);
134 // civiExit() is unnecessary -- we're only called as part of abend
135 }
136
137 /**
138 * Print an unhandled exception
139 *
140 * @param $e
141 */
142 function reportException(Exception $e) {
143 CRM_Core_Error::debug_var('CRM_Queue_ErrorPolicy_reportException', CRM_Core_Error::formatTextException($e));
144
145 $response = array(
146 'is_error' => 1,
147 'is_continue' => 0,
148 );
149
150 $config = CRM_Core_Config::singleton();
151 if ($config->backtrace || CRM_Core_Config::isUpgradeMode()) {
152 $response['exception'] = CRM_Core_Error::formatHtmlException($e);
153 }
154 else {
155 $response['exception'] = htmlentities($e->getMessage());
156 }
157
158 global $activeQueueRunner;
159 if (is_object($activeQueueRunner)) {
160 $response['last_task_title'] = $activeQueueRunner->lastTaskTitle;
161 }
162 echo json_encode($response);
163 CRM_Utils_System::civiExit();
164 }
165}
166