| @@ -22,8 +22,15 @@ | ||
| 22 | 22 | */ |
| 23 | 23 | private static $current_snippet = null; |
| 24 | 24 | |
| 25 | 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 | + /** | |
| 26 | 33 | * Tracks whether the error handler has been initialized. |
| 27 | 34 | * |
| 28 | 35 | * @var bool |
| 29 | 36 | */ |
| @@ -52,8 +59,9 @@ | ||
| 52 | 59 | * |
| 53 | 60 | * @return void |
| 54 | 61 | */ |
| 55 | 62 | public static function set_current_snippet( $snippet_id, $title, $content = null ) { |
| 63 | + self::$snippet_stack[] = self::$current_snippet; | |
| 56 | 64 | self::$current_snippet = [ |
| 57 | 65 | 'id' => $snippet_id, |
| 58 | 66 | 'title' => $title, |
| 59 | 67 | 'content' => $content, |
| @@ -65,12 +73,44 @@ | ||
| 65 | 73 | * |
| 66 | 74 | * @return void |
| 67 | 75 | */ |
| 68 | 76 | public static function clear_current_snippet() { |
| 69 | - self::$current_snippet = null; | |
| 77 | + self::$current_snippet = array_pop( self::$snippet_stack ); | |
| 70 | 78 | } |
| 71 | 79 | |
| 72 | 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 | + /** | |
| 73 | 113 | * Customize the error message displayed. |
| 74 | 114 | * |
| 75 | 115 | * @param string $message Original error message. |
| 76 | 116 | * @return string Modified error message. |
| @@ -77,9 +117,9 @@ | ||
| 77 | 117 | */ |
| 78 | 118 | public static function customize_error_message( $message ) { |
| 79 | 119 | $error = error_get_last(); |
| 80 | 120 | |
| 81 | - if ( ! $error || ! self::$current_snippet ) { | |
| 121 | + if ( ! $error || ! self::$current_snippet || ! current_user_can( 'manage_options' ) ) { | |
| 82 | 122 | return $message; |
| 83 | 123 | } |
| 84 | 124 | |
| 85 | 125 | if ( ! self::is_eval_error( $error['file'] ) ) { |
| @@ -85,14 +125,15 @@ | ||
| 85 | 125 | if ( ! self::is_eval_error( $error['file'] ) ) { |
| 86 | 126 | return $message; |
| 87 | 127 | } |
| 88 | 128 | |
| 129 | + $snippet_id = (int) self::$current_snippet['id']; | |
| 89 | 130 | $snippet_title = self::$current_snippet['title']; |
| 90 | 131 | $error_message = self::strip_stack_trace( $error['message'] ); |
| 91 | 132 | $error_line = (int) $error['line']; |
| 92 | 133 | |
| 93 | 134 | // Build the custom error display. |
| 94 | - $custom_message = self::build_error_display( $snippet_title, $error_message, $error_line ); | |
| 135 | + $custom_message = self::build_error_display( $snippet_title, $error_message, $error_line, $snippet_id ); | |
| 95 | 136 | |
| 96 | 137 | return $message . $custom_message; |
| 97 | 138 | } |
| 98 | 139 | |
| @@ -123,9 +164,9 @@ | ||
| 123 | 164 | } |
| 124 | 165 | |
| 125 | 166 | $patterns = [ |
| 126 | 167 | '/\s*Stack trace:.*/is', |
| 127 | - '/\s*#\d+\s+.*/is', | |
| 168 | + '/\n\s*#\d+\s+.*/is', | |
| 128 | 169 | '/\s*thrown in .*/is', |
| 129 | 170 | ]; |
| 130 | 171 | |
| 131 | 172 | foreach ( $patterns as $pattern ) { |
| @@ -146,11 +187,12 @@ | ||
| 146 | 187 | * |
| 147 | 188 | * @param string $snippet_title Snippet title. |
| 148 | 189 | * @param string $error_message Error message. |
| 149 | 190 | * @param int $error_line Line number where error occurred. |
| 191 | + * @param int $snippet_id Snippet ID. | |
| 150 | 192 | * @return string|false HTML for error display. |
| 151 | 193 | */ |
| 152 | - private static function build_error_display( $snippet_title, $error_message, $error_line ) { | |
| 194 | + private static function build_error_display( $snippet_title, $error_message, $error_line, $snippet_id = 0 ) { | |
| 153 | 195 | $title = $snippet_title |
| 154 | 196 | ? sprintf( |
| 155 | 197 | // translators: %s is snippet title. |
| 156 | 198 | esc_html__( 'Snippet: %s', 'insert-php' ), |
| @@ -164,12 +206,20 @@ | ||
| 164 | 206 | esc_html__( 'Line %d', 'insert-php' ), |
| 165 | 207 | $error_line |
| 166 | 208 | ) |
| 167 | 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 | + : ''; | |
| 168 | 217 | |
| 169 | 218 | $full_error_text = sprintf( |
| 170 | - "%s\n%s: %s", | |
| 219 | + "%s\n%s\n%s: %s", | |
| 171 | 220 | $title, |
| 221 | + $id_info, | |
| 172 | 222 | $line_info, |
| 173 | 223 | $error_message |
| 174 | 224 | ); |
| 175 | 225 | |
| @@ -269,8 +319,15 @@ | ||
| 269 | 319 | <span class="winp-error-value"> |
| 270 | 320 | <?php echo esc_html( $snippet_title ); ?> |
| 271 | 321 | </span> |
| 272 | 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; ?> | |
| 273 | 330 | |
| 274 | 331 | <?php if ( $error_line ) : ?> |
| 275 | 332 | <div class="winp-error-row"> |
| 276 | 333 | <span class="winp-error-label"><?php esc_html_e( 'Line:', 'insert-php' ); ?></span> |