PluginProbe
Accessibility by AllAccessible / trunk
Accessibility by AllAccessible vtrunk
2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.6 trunk 1.0 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2 1.2.1 1.2.10 1.2.2 1.2.3 1.2.4 1.2.5 All 43 releases
allaccessible / inc / Debug.php

Debug.php in Accessibility by AllAccessible trunk, at inc/Debug.php

190 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AllAccessible — centralized debug-mode helper.
4 */
5
6 if (!defined('ABSPATH')) { exit; }
7
8 final class AllAccessible_Debug {
9
10 /**
11 * Cache the toggle for the duration of a single request
12 *
13 * @var bool|null
14 */
15 private static $cached_enabled = null;
16
17 /**
18 * Is debug logging enabled? Reads aacb_options.debug_mode once per
19 * request and caches the value.
20 */
21 public static function is_enabled(): bool {
22 if (self::$cached_enabled === null) {
23 $opts = get_option('aacb_options', array());
24 self::$cached_enabled = !empty($opts['debug_mode']);
25 }
26 return self::$cached_enabled;
27 }
28
29 /**
30 * Emit a console.groupCollapsed payload to the browser DevTools.
31 * No-op when debug_mode is off.
32 *
33 * @param string $label Surface name shown in the console group title.
34 * Example: "EditorMetaBox — post 42".
35 * @param mixed $payload Anything JSON-encodable. Usually an
36 * associative array of {requested, response,
37 * render_branch, ...}.
38 * @param array $opts Optional:
39 * - 'group_method' => 'group'|'groupCollapsed'
40 * (default 'groupCollapsed' to keep noise low)
41 * - 'console_method' => 'log'|'table'
42 * (default 'log'; use 'table' for row-shaped
43 * arrays where columns help readability)
44 */
45 public static function console(string $label, $payload, array $opts = array()): void {
46 if (!self::is_enabled()) {
47 return;
48 }
49
50 $group_method = isset($opts['group_method']) ? (string) $opts['group_method'] : 'groupCollapsed';
51 $console_method = isset($opts['console_method']) ? (string) $opts['console_method'] : 'log';
52
53 // Whitelist to avoid injecting arbitrary method names into the
54 // emitted JS. Anything not on this list falls back to safe defaults.
55 $allowed_group = array('group', 'groupCollapsed');
56 $allowed_console = array('log', 'table', 'info', 'warn');
57 if (!in_array($group_method, $allowed_group, true)) $group_method = 'groupCollapsed';
58 if (!in_array($console_method, $allowed_console, true)) $console_method = 'log';
59
60 $title_safe = '[AllAccessible] ' . $label;
61 $payload_json = wp_json_encode($payload);
62
63 // Stable formatting so console output diff-reads cleanly between
64 // pageloads when debugging. Title goes through wp_json_encode for
65 // proper JS string escaping (handles quotes, backslashes, etc).
66 $title_json = wp_json_encode($title_safe);
67
68 echo '<script>(function(){console.' . esc_html($group_method) . '(' . $title_json . ');console.' . esc_html($console_method) . '(' . $payload_json . ');console.groupEnd();})();</script>';
69 }
70
71 /**
72 * Convenience — dump common API-response context in one call.
73 * Standardizes the shape across plugin surfaces so debug output is
74 * grep-friendly in the console.
75 */
76 public static function api(string $surface, $request, $response): void {
77 if (!self::is_enabled()) {
78 return;
79 }
80 self::console($surface, array(
81 'request' => $request,
82 'is_wp_error' => is_wp_error($response),
83 'error' => is_wp_error($response) ? $response->get_error_message() : null,
84 'response' => is_wp_error($response) ? null : $response,
85 ));
86 }
87
88 /**
89 * Hard failure — something that should never happen and we want to
90 * see aggregated across the installed base. Sent to error reporting.
91 *
92 * @param string $surface Where it happened (e.g.
93 * "PostLinkBackfill::link_single").
94 * @param string|\Throwable|\WP_Error $err Message string, exception, or WP_Error.
95 * @param array $context Extra fields (post_id, batch_size, etc).
96 */
97 public static function error(string $surface, $err, array $context = array()): void {
98 $message = self::error_to_string($err);
99 $payload = array_merge(array('surface' => $surface), $context);
100
101 // 1. error_log — always.
102 error_log(sprintf('[AllAccessible] %s ERROR: %s', $surface, $message));
103
104 // 2. Browser console — only when debug toggle on. Reuses the
105 // existing console pipeline so the user sees the same
106 // structured payload they'd see for normal debug dumps.
107 if (self::is_enabled()) {
108 self::console($surface . ' [error]', array_merge(
109 array('message' => $message),
110 $payload
111 ), array('console_method' => 'warn'));
112 }
113
114 // 3. Error reporting. Throwables go to capture_exception (preserves
115 // stack trace); strings + WP_Errors go to capture_message.
116 if (class_exists('AllAccessible_Sentry')) {
117 if ($err instanceof \Throwable) {
118 AllAccessible_Sentry::capture_exception($err, $payload);
119 } else {
120 AllAccessible_Sentry::capture_message(
121 sprintf('[%s] %s', $surface, $message),
122 'error',
123 $payload
124 );
125 }
126 }
127 }
128
129 /**
130 * Soft failure — something recoverable but worth noting. Error
131 * reporting still captures (severity=warning) so we can see rate patterns.
132 */
133 public static function warn(string $surface, string $message, array $context = array()): void {
134 $payload = array_merge(array('surface' => $surface), $context);
135
136 error_log(sprintf('[AllAccessible] %s WARN: %s', $surface, $message));
137
138 if (self::is_enabled()) {
139 self::console($surface . ' [warn]', array_merge(
140 array('message' => $message),
141 $payload
142 ), array('console_method' => 'warn'));
143 }
144
145 if (class_exists('AllAccessible_Sentry')) {
146 AllAccessible_Sentry::capture_message(
147 sprintf('[%s] %s', $surface, $message),
148 'warning',
149 $payload
150 );
151 }
152 }
153
154 /**
155 * Informational — cron skips, cooldown hits, eligibility gates.
156 * NEVER goes to error reporting (would flood it). error_log +
157 * console only. Use this instead of bare error_log so a future
158 * "route all noisy info elsewhere" decision is one-file.
159 */
160 public static function info(string $surface, string $message, array $context = array()): void {
161 $payload = array_merge(array('surface' => $surface), $context);
162
163 error_log(sprintf('[AllAccessible] %s INFO: %s', $surface, $message));
164
165 if (self::is_enabled()) {
166 self::console($surface . ' [info]', array_merge(
167 array('message' => $message),
168 $payload
169 ));
170 }
171 }
172
173 /**
174 * Normalize anything throwable/wp-erroric/stringy into a single
175 * human-readable line.
176 */
177 private static function error_to_string($err): string {
178 if (is_string($err)) {
179 return $err;
180 }
181 if ($err instanceof \Throwable) {
182 return sprintf('%s: %s', get_class($err), $err->getMessage());
183 }
184 if (is_wp_error($err)) {
185 return sprintf('WP_Error[%s]: %s', $err->get_error_code(), $err->get_error_message());
186 }
187 return wp_json_encode($err);
188 }
189 }
190