1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
<?php /** * Generic exception that we know has come from the Loco plugin */ class Loco_error_Exception extends Exception implements JsonSerializable {
const LEVEL_ERROR = 0; const LEVEL_WARNING = 1; const LEVEL_DEBUG = 2; const LEVEL_NOLOG = 3;
/** * Links to help docs etc.. to show along side error message * @var array */ private $links = [];
/** * Override file in which exception was thrown * @var string */ private $_file;
/** * Override line number from where exception was thrown * @var int */ private $_line;
/** * Whether log file writing is enabled * @var bool */ private $_log = true;
/** * {@inheritdoc} */ public function __construct( $message = '', $code = 0, $previous = null ) { parent::__construct( $message, $code, $previous ); }
/** * @return Throwable */ private function getRootException(){ $current = $this; // note that getPrevious is absent in PHP < 5.3 while( method_exists($current,'getPrevious') && ( $next = $current->getPrevious() ) ){ $current = $next; } return $current; }
/** * @return string */ public function getRealFile(){ if( $this->_file ){ return $this->_file; } return $this->getRootException()->getFile(); }
/** * @return int */ public function getRealLine(){ if( $this->_line ){ return $this->_line; } return $this->getRootException()->getLine(); }
/** * @return array */ public function getRealTrace(){ return $this->getRootException()->getTrace(); }
/** * @param int $depth number of levels up from callee */ public function setCallee( int $depth = 0 ):self { $stack = debug_backtrace(0); $callee = $stack[$depth]; $this->_file = $callee['file']; $this->_line = $callee['line']; // TODO could also log the stack trace from $depth upwards, but not required unless being logged or thrown return $this; }
/** * Write this error to file regardless of log level * @return void */ public function log(){ $file = new Loco_fs_File( $this->getRealFile() ); $path = $file->getRelativePath( loco_plugin_root() ); $text = sprintf('[Loco.%s] "%s" in %s:%u', $this->getType(), $this->getMessage(), $path, $this->getRealLine() ); // separate error log for cli tests if( 'cli' === PHP_SAPI && defined('LOCO_TEST_DATA_ROOT') ){ error_log( '['.date('c').'] '.$text."\n", 3, 'debug.log' ); //fwrite( STDERR, $this->getType().': '.$this->getMessage()."\n" ); } // Else write to default PHP log, but note that WordPress may have set this to wp-content/debug.log. // If no `error_log` is set this will send message to the SAPI, so check your httpd/fast-cgi errors too. else { error_log( $text, 0 ); } }
/** * Get view template for rendering error to HTML. * @return string path relative to root tpl directory */ public function getTemplate(){ return 'admin/errors/generic'; }
/** * Get notice level short code as a string * @return string */ public function getType(){ return 'error'; }
/** * Get verbosity level * @return int */ public function getLevel(){ return self::LEVEL_ERROR; }
/** * Call wp cli logging function * @return void */ public function logCli(){ WP_CLI::error( $this->getMessage(), false ); }
/** * Get localized notice level name * @return string */ public function getTitle(){ return __('Error','loco-translate'); }
/** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ $a = [ 'code' => $this->getCode(), 'type' => $this->getType(), 'title' => $this->getTitle(), 'message' => $this->getMessage(), ]; /*if( loco_debugging() ){ $a['file'] = str_replace( ABSPATH, '', $this->getRealFile() ); $a['line'] = $this->getRealLine(); $a = self::recurseJsonSerialize($a,$this); }*/ return $a; }
/** * @param string[] $a * @return array modified from $a * @codeCoverageIgnore */ private static function recurseJsonSerialize( array $a, Exception $child ){ $a['class'] = get_class($child); $a['trace'] = $child->getTraceAsString(); $parent = $child->getPrevious(); if( $parent instanceof Exception ){ $a['previous'] = self::recurseJsonSerialize([],$parent); } return $a; }
/** * Push navigation links into error. Use for help pages etc.. * @param string $href * @param string $text * @return Loco_error_Exception */ public function addLink( $href, $text ){ $this->links[] = sprintf('<a href="%s">%s</a>', esc_url($href), esc_html($text) ); return $this; }
/** * @return array */ public function getLinks(){ return $this->links; }
/** * Convert generic exception to one of ours * @param Exception $e original error * @return Loco_error_Exception */ public static function convert( Exception $e ){ if( $e instanceof Loco_error_Exception ){ return $e; } return new Loco_error_Exception( $e->getMessage(), $e->getCode(), $e ); }
/** * Test if this error should be automatically logged * @return bool */ public function loggable(){ if( $this->_log ){ // Log messages of minimum priority and up, depending on debug mode // note that non-debug level is in line with error_reporting set by WordPress (notices ignored) $priority = loco_debugging() ? Loco_error_Exception::LEVEL_DEBUG : Loco_error_Exception::LEVEL_WARNING; return $this->getLevel() <= $priority; } return false; } /** * Suppress logging for this error. e.g if you want to warn in UI but don't want to pollute log files. * @return self */ public function noLog(){ $this->_log = false; return $this; } /** * Check if passed exception is effectively the same as this one * @return bool */ public function isIdentical( Exception $other ){ return $this->getCode() === $other->getCode() && $this->getMessage() === $other->getMessage() && $this->getType() === ( $other instanceof Loco_error_Exception ? $other->getType() : 0 ); }
}
|