PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.7
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.7
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
insert-php / includes / class.error-handler.php

class.error-handler.php in Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts 2.7.7, at includes/class.error-handler.php

403 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Error handler for PHP snippets.
4 *
5 * @package WP_Plugin_Insert_PHP
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * WINP_Error_Handler class
15 */
16 class WINP_Error_Handler {
17
18 /**
19 * Holds the current snippet context for error handling.
20 *
21 * @var array<string, mixed>|null
22 */
23 private static $current_snippet = null;
24
25 /**
26 * Previous snippet contexts for nested shortcode execution.
27 *
28 * @var array<int, array<string, mixed>|null>
29 */
30 private static $snippet_stack = [];
31
32 /**
33 * Tracks whether the error handler has been initialized.
34 *
35 * @var bool
36 */
37 private static $initialized = false;
38
39 /**
40 * Initialize error handler.
41 *
42 * @return void
43 */
44 public static function init() {
45 if ( self::$initialized ) {
46 return;
47 }
48
49 add_filter( 'wp_php_error_message', [ __CLASS__, 'customize_error_message' ], 9, 1 );
50 self::$initialized = true;
51 }
52
53 /**
54 * Set the current snippet context for error handling.
55 *
56 * @param int $snippet_id Snippet ID.
57 * @param string $title Snippet title.
58 * @param string $content Optional snippet content. Accepted for backward compatibility and ignored.
59 *
60 * @return void
61 */
62 public static function set_current_snippet( $snippet_id, $title, $content = null ) {
63 self::$snippet_stack[] = self::$current_snippet;
64 self::$current_snippet = [
65 'id' => $snippet_id,
66 'title' => $title,
67 'content' => $content,
68 ];
69 }
70
71 /**
72 * Clear the current snippet context after execution.
73 *
74 * @return void
75 */
76 public static function clear_current_snippet() {
77 self::$current_snippet = array_pop( self::$snippet_stack );
78 }
79
80 /**
81 * Handle a catchable error raised while rendering a PHP shortcode.
82 *
83 * @param Throwable $exception Runtime error raised by evaluated snippet code.
84 * @return string Error details for administrators, or an empty string.
85 */
86 public static function handle_exception( $exception ) {
87 if ( ! self::$current_snippet ) {
88 return '';
89 }
90
91 /**
92 * Fires when PHP shortcode execution raises a catchable error.
93 *
94 * @param Throwable $exception Runtime error raised by the snippet.
95 * @param array<string, mixed> $context Current snippet context.
96 */
97 do_action( 'wbcr_inp_php_shortcode_error', $exception, self::$current_snippet );
98
99 if ( ! current_user_can( 'manage_options' ) ) {
100 return '';
101 }
102
103 $display = self::build_error_display(
104 self::$current_snippet['title'],
105 self::strip_stack_trace( $exception->getMessage() ),
106 $exception->getLine()
107 );
108
109 return false !== $display ? $display : '';
110 }
111
112 /**
113 * Customize the error message displayed.
114 *
115 * @param string $message Original error message.
116 * @return string Modified error message.
117 */
118 public static function customize_error_message( $message ) {
119 $error = error_get_last();
120
121 if ( ! $error || ! self::$current_snippet || ! current_user_can( 'manage_options' ) ) {
122 return $message;
123 }
124
125 if ( ! self::is_eval_error( $error['file'] ) ) {
126 return $message;
127 }
128
129 $snippet_id = (int) self::$current_snippet['id'];
130 $snippet_title = self::$current_snippet['title'];
131 $error_message = self::strip_stack_trace( $error['message'] );
132 $error_line = (int) $error['line'];
133
134 // Build the custom error display.
135 $custom_message = self::build_error_display( $snippet_title, $error_message, $error_line, $snippet_id );
136
137 return $message . $custom_message;
138 }
139
140 /**
141 * Check if error file indicates eval'd code.
142 *
143 * @param string $file Error file path.
144 * @return bool True if error is from eval'd code.
145 */
146 private static function is_eval_error( $file ) {
147 return strpos( $file, "eval()'d code" ) !== false ||
148 strpos( $file, 'eval()\'d code' ) !== false;
149 }
150
151 /**
152 * Strip stack trace from error message.
153 *
154 * @param string $error_message Raw error message that may contain stack trace.
155 * @return string Clean error message without stack trace.
156 */
157 private static function strip_stack_trace( $error_message ) {
158 // Remove file path and eval() references.
159 $error_message = preg_replace( '/\s+in\s+(?:\/|[A-Z]:[\/\\\\]).*?eval\(\)\'d code:\d+/i', '', $error_message );
160
161 // Remove generic "in /path/to/file" references.
162 if ( $error_message ) {
163 $error_message = preg_replace( '/\s+in\s+(?:\/[^\s]+|[A-Z]:[\/\\\\][^\s]+)/i', '', $error_message );
164 }
165
166 $patterns = [
167 '/\s*Stack trace:.*/is',
168 '/\n\s*#\d+\s+.*/is',
169 '/\s*thrown in .*/is',
170 ];
171
172 foreach ( $patterns as $pattern ) {
173 if ( $error_message ) {
174 $error_message = preg_replace( $pattern, '', $error_message );
175 }
176 }
177
178 if ( $error_message ) {
179 $error_message = trim( $error_message );
180 }
181
182 return $error_message ? $error_message : '';
183 }
184
185 /**
186 * Build the custom error display HTML.
187 *
188 * @param string $snippet_title Snippet title.
189 * @param string $error_message Error message.
190 * @param int $error_line Line number where error occurred.
191 * @param int $snippet_id Snippet ID.
192 * @return string|false HTML for error display.
193 */
194 private static function build_error_display( $snippet_title, $error_message, $error_line, $snippet_id = 0 ) {
195 $title = $snippet_title
196 ? sprintf(
197 // translators: %s is snippet title.
198 esc_html__( 'Snippet: %s', 'insert-php' ),
199 $snippet_title
200 )
201 : '';
202
203 $line_info = $error_line
204 ? sprintf(
205 // translators: %d is line number.
206 esc_html__( 'Line %d', 'insert-php' ),
207 $error_line
208 )
209 : '';
210 $id_info = $snippet_id
211 ? sprintf(
212 // translators: %d is the snippet ID.
213 __( 'Snippet ID: %d', 'insert-php' ),
214 $snippet_id
215 )
216 : '';
217
218 $full_error_text = sprintf(
219 "%s\n%s\n%s: %s",
220 $title,
221 $id_info,
222 $line_info,
223 $error_message
224 );
225
226 ob_start();
227 ?>
228 <style>
229 .winp-error-container {
230 background: #fff;
231 border: 1px solid #c3c4c7;
232 border-left: 4px solid #d63638;
233 box-shadow: 0 1px 1px rgba(0,0,0,.04);
234 margin: 20px 0;
235 padding: 20px;
236 }
237 .winp-error-title {
238 color: #d63638;
239 font-size: 18px;
240 font-weight: 600;
241 margin: 0 0 15px 0;
242 display: flex;
243 align-items: center;
244 gap: 10px;
245 }
246 .winp-error-icon {
247 width: 24px;
248 height: 24px;
249 }
250 .winp-error-details {
251 position: relative;
252 background: #f6f7f7;
253 border: 1px solid #dcdcde;
254 border-radius: 4px;
255 padding: 15px;
256 margin: 15px 0;
257 }
258 .winp-error-row {
259 margin: 10px 0;
260 line-height: 1.6;
261 }
262 .winp-error-label {
263 font-weight: 600;
264 color: #1d2327;
265 display: inline-block;
266 min-width: 80px;
267 }
268 .winp-error-value {
269 color: #2c3338;
270 font-family: Consolas, Monaco, monospace;
271 }
272 .winp-error-message {
273 color: #d63638;
274 font-weight: 500;
275 flex: 1;
276 }
277 .winp-copy-icon {
278 position: absolute;
279 top: 15px;
280 right: 15px;
281 flex-shrink: 0;
282 cursor: pointer;
283 padding: 4px;
284 border-radius: 3px;
285 transition: background 0.2s;
286 display: inline-flex;
287 align-items: center;
288 justify-content: center;
289 }
290 .winp-copy-icon:hover {
291 background: #e0e0e0;
292 }
293 .winp-copy-icon svg {
294 width: 18px;
295 height: 18px;
296 }
297 .winp-copy-icon.copied {
298 background: #d5f5d5;
299 }
300 .winp-error-line {
301 color: #d63638;
302 font-weight: 600;
303 font-size: 16px;
304 }
305 </style>
306
307 <div class="winp-error-container">
308 <h2 class="winp-error-title">
309 <svg class="winp-error-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
310 <circle cx="12" cy="12" r="10" stroke="#d63638" stroke-width="2"/>
311 <path d="M12 8v4m0 4h.01" stroke="#d63638" stroke-width="2" stroke-linecap="round"/>
312 </svg>
313 <?php esc_html_e( 'PHP Snippet Error', 'insert-php' ); ?>
314 </h2>
315
316 <div class="winp-error-details">
317 <div class="winp-error-row">
318 <span class="winp-error-label"><?php esc_html_e( 'Snippet:', 'insert-php' ); ?></span>
319 <span class="winp-error-value">
320 <?php echo esc_html( $snippet_title ); ?>
321 </span>
322 </div>
323
324 <?php if ( $snippet_id ) : ?>
325 <div class="winp-error-row">
326 <span class="winp-error-label"><?php esc_html_e( 'Snippet ID:', 'insert-php' ); ?></span>
327 <span class="winp-error-value"><?php echo esc_html( (string) $snippet_id ); ?></span>
328 </div>
329 <?php endif; ?>
330
331 <?php if ( $error_line ) : ?>
332 <div class="winp-error-row">
333 <span class="winp-error-label"><?php esc_html_e( 'Line:', 'insert-php' ); ?></span>
334 <span class="winp-error-line"><?php echo esc_html( (string) $error_line ); ?></span>
335 </div>
336 <?php endif; ?>
337
338 <div class="winp-error-row">
339 <span class="winp-error-label"><?php esc_html_e( 'Error:', 'insert-php' ); ?></span>
340 <span class="winp-error-value winp-error-message"><?php echo esc_html( $error_message ); ?></span>
341 </div>
342 <span class="winp-copy-icon" onclick="copyErrorToClipboard()" title="<?php esc_attr_e( 'Copy error details', 'insert-php' ); ?>" id="winp-copy-icon">
343 <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
344 <rect x="9" y="9" width="13" height="13" rx="2" stroke="#2c3338" stroke-width="2"/>
345 <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke="#2c3338" stroke-width="2"/>
346 </svg>
347 </span>
348 </div>
349 </div>
350
351 <script>
352 function copyErrorToClipboard() {
353 const errorText = <?php echo wp_json_encode( $full_error_text ); ?>;
354 const icon = document.getElementById('winp-copy-icon');
355
356 if (navigator.clipboard && navigator.clipboard.writeText) {
357 navigator.clipboard.writeText(errorText).then(function() {
358 showCopySuccess(icon);
359 }).catch(function(err) {
360 fallbackCopy(errorText, icon);
361 });
362 } else {
363 fallbackCopy(errorText, icon);
364 }
365 }
366
367 function fallbackCopy(text, icon) {
368 const textarea = document.createElement('textarea');
369 textarea.value = text;
370 textarea.style.position = 'fixed';
371 textarea.style.opacity = '0';
372 document.body.appendChild(textarea);
373 textarea.select();
374 try {
375 document.execCommand('copy');
376 showCopySuccess(icon);
377 } catch (err) {
378 alert(<?php echo wp_json_encode( __( 'Failed to copy. Please copy manually.', 'insert-php' ) ); ?>);
379 }
380 document.body.removeChild(textarea);
381 }
382
383 function showCopySuccess(icon) {
384 icon.classList.add('copied');
385 const originalTitle = icon.getAttribute('title');
386 icon.setAttribute('title', <?php echo wp_json_encode( __( 'Copied!', 'insert-php' ) ); ?>);
387
388 // Change icon to checkmark
389 icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 6L9 17l-5-5" stroke="#007017" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
390
391 setTimeout(function() {
392 icon.classList.remove('copied');
393 icon.setAttribute('title', originalTitle);
394 // Restore clipboard icon
395 icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="9" width="13" height="13" rx="2" stroke="#2c3338" stroke-width="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke="#2c3338" stroke-width="2"/></svg>';
396 }, 2000);
397 }
398 </script>
399 <?php
400 return ob_get_clean();
401 }
402 }
403