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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
<?php
class Loco_error_AdminNotices extends Loco_hooks_Hookable { /** * @var Loco_error_AdminNotices */ private static $singleton;
/** * @var Loco_error_Exception[] */ private $errors = [];
/** * Inline messages are handled by our own template views * @var bool */ private $inline = false;
/** * @return Loco_error_AdminNotices */ public static function get(){ self::$singleton or self::$singleton = new Loco_error_AdminNotices; return self::$singleton; }
/** * Enable temporary buffering of PHP errors, reducing error reporting to debug level. * Call restore_error_handler to stop capturing. * @param int $level PHP error level bit mask, e.g. E_WARNING * @return void */ public static function capture( $level ){ set_error_handler( [__CLASS__,'handle_error'], $level ); }
/** * @internal * @param int $errno * @param string $errstr */ public static function handle_error( $errno, $errstr /*$errfile, $errline*/ ){ if( $errno & (E_ERROR|E_USER_ERROR) ){ return false; } $label = $errno & (E_WARNING|E_USER_WARNING) ? 'Warning' : 'Notice'; self::debug( '[PHP '.$label.'] '.$errstr ); return true; }
/** * @param Loco_error_Exception $error * @return Loco_error_Exception */ public static function add( Loco_error_Exception $error ){ $notices = self::get(); // Skip repeated error messages in same stack foreach( $notices->errors as $previous ){ if( $error->isIdentical($previous) ){ return $previous; } } // if exception wasn't thrown we have to do some work to establish where it was invoked if( __FILE__ === $error->getRealFile() ){ $error->setCallee(1); } // write error immediately under WP_CLI if( 'cli' === PHP_SAPI && class_exists('WP_CLI',false) ){ $error->logCli(); return $error; } // else buffer notices for displaying when UI is ready $notices->errors[] = $error; // do late flush if we missed the boat if( did_action('loco_admin_init') ){ $notices->on_loco_admin_init(); } if( did_action('admin_notices') ){ $notices->on_admin_notices(); } // Log message automatically if enabled if( $error->loggable() ){ $error->log(); } return $error; }
/** * Raise a success message * @param string $message * @return Loco_error_Exception */ public static function success( $message ){ $notice = new Loco_error_Success($message); return self::add( $notice->setCallee(1) ); }
/** * Raise a failure message * @param string $message * @return Loco_error_Exception */ public static function err( $message ){ $notice = new Loco_error_Exception($message); return self::add( $notice->setCallee(1) ); }
/** * Raise a warning message * @param string $message * @return Loco_error_Exception */ public static function warn( $message ){ $notice = new Loco_error_Warning($message); return self::add( $notice->setCallee(1) ); }
/** * Raise a generic info message * @param string $message * @return Loco_error_Exception */ public static function info( $message ){ $notice = new Loco_error_Notice($message); return self::add( $notice->setCallee(1) ); }
/** * Raise a debug notice, if debug is enabled * @param string $message * @return Loco_error_Debug */ public static function debug( $message ){ $notice = new Loco_error_Debug($message); $notice->setCallee(1); loco_debugging() and self::add( $notice ); return $notice; }
/** * Destroy and return buffer * @return Loco_error_Exception[] */ public static function destroy(){ $notices = self::$singleton; if( $notices instanceof Loco_error_AdminNotices ){ $buffer = $notices->errors; $notices->errors = []; self::$singleton = null; return $buffer; } return []; }
/** * @codeCoverageIgnore * @deprecated Since PHP 5.4 there is no need to cast array via calls to jsonSerialize */ public static function destroyAjax(){ $data = []; foreach( self::destroy() as $notice ){ $data[] = $notice->jsonSerialize(); } return $data; }
/** * @return void */ private function flushHtml(){ if( $this->errors ){ $htmls = []; foreach( $this->errors as $error ){ $html = sprintf ( '<p><strong class="has-icon">%s:</strong> <span>%s</span></p>', esc_html( $error->getTitle() ), esc_html( $error->getMessage() ) ); $styles = [ 'notice', 'notice-'.$error->getType() ]; if( $this->inline ){ $styles[] = 'inline'; } if( $links = $error->getLinks() ){ $styles[] = 'has-nav'; $html .= '<nav>'.implode( '<span> | </span>', $links ).'</nav>'; } $htmls[] = '<div class="'.implode(' ',$styles).'">'.$html.'</div>'; } $this->errors = []; echo implode("\n", $htmls),"\n"; } }
/** * @return void */ private function flushCli(){ foreach( $this->errors as $e ){ $e->logCli(); } $this->errors = []; }
/** * admin_notices action handler. */ public function on_admin_notices(){ if( ! $this->inline ){ $this->flushHtml(); } }
/** * loco_admin_notices callback. * Unlike WordPress "admin_notices" this fires from within template layout at the point we want them, hence they are marked as "inline" */ public function on_loco_admin_notices(){ $this->inline = true; $this->flushHtml(); }
/** * loco_admin_init callback * When we know a Loco admin controller will render the page we will control the point at which notices are printed */ public function on_loco_admin_init(){ $this->inline = true; }
/** * @internal * Make sure we always see notices if hooks didn't fire */ public function __destruct(){ $this->inline = false; $this->flush(); // handle situation where test case will have lost the buffer if( $this->errors && 'cli' === PHP_SAPI ){ throw new RuntimeException('Notices not flushed before destruction'); } }
/** * @param int $level * @return Loco_error_Exception[] */ public function filter( $level ){ $e = []; foreach( $this->errors as $error ){ if( $error->getLevel() <= $level ){ $e[] = $error; } } return $e; }
/** * @internal */ public function flush(){ if( class_exists('WP_CLI',false) ){ $this->flushCli(); } else if( loco_doing_ajax() ){ $this->errors = []; } else if( 'cli' !== PHP_SAPI ){ $this->flushHtml(); } // else probably in unit test and not properly handled, leave significant errors in buffer else { $this->errors = $this->filter( Loco_error_Exception::LEVEL_WARNING ); } return $this; }
}
|