PluginProbe
BeyondWords – AI audio for publishers / 7.2.0
BeyondWords – AI audio for publishers v7.2.0
7.2.0 7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 All 44 releases
speechkit / src / site-health / class-site-health.php

class-site-health.php in BeyondWords – AI audio for publishers 7.2.0, at src/site-health/class-site-health.php

340 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * BeyondWords section for the WordPress Site Health debugging info.
4 *
5 * @package BeyondWords\SiteHealth
6 * @since 3.7.0
7 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
8 */
9
10 declare( strict_types = 1 );
11
12 namespace BeyondWords\SiteHealth;
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * Site Health debug panel for BeyondWords.
18 *
19 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
20 */
21 class SiteHealth {
22
23 /**
24 * BeyondWords filter hooks that are still active.
25 *
26 * Surfaced in Site Health so support can see which extension points are in use.
27 *
28 * @var string[]
29 */
30 const FILTERS = [
31 'beyondwords_content_params',
32 'beyondwords_player_script_onload',
33 'beyondwords_player_html',
34 'beyondwords_player_sdk_params',
35 'beyondwords_settings_player_styles',
36 'beyondwords_settings_post_types',
37 'beyondwords_settings_post_statuses',
38 ];
39
40 /**
41 * BeyondWords filter hooks that are deprecated but may still be in use.
42 *
43 * Used to flag legacy hooks in Site Health so we can chase them down.
44 *
45 * @var string[]
46 */
47 const DEPRECATED_FILTERS = [
48 'beyondwords_amp_player_html',
49 'beyondwords_body_params',
50 'beyondwords_content',
51 'beyondwords_content_id',
52 'beyondwords_js_player_html',
53 'beyondwords_js_player_params',
54 'beyondwords_player_styles',
55 'beyondwords_post_audio_enabled_blocks',
56 'beyondwords_post_metadata',
57 'beyondwords_post_player_enabled',
58 'beyondwords_post_statuses',
59 'beyondwords_post_types',
60 'beyondwords_project_id',
61 'sk_player_after',
62 'sk_player_before',
63 'sk_the_content',
64 'speechkit_amp_player_html',
65 'speechkit_content',
66 'speechkit_js_player_html',
67 'speechkit_js_player_params',
68 'speechkit_post_player_enabled',
69 'speechkit_post_statuses',
70 'speechkit_post_types',
71 ];
72
73 /**
74 * Register WordPress hooks.
75 */
76 public static function init(): void {
77 add_filter( 'debug_information', [ self::class, 'debug_information' ] );
78 }
79
80 /**
81 * Build the BeyondWords section of `debug_information`.
82 *
83 * @param array $info Site Health debug array.
84 *
85 * @return array
86 */
87 public static function debug_information( $info ) {
88 $info['beyondwords']['label'] = __( 'BeyondWords - Text-to-Speech', 'speechkit' );
89
90 self::add_plugin_version( $info );
91 self::add_rest_api_connection( $info );
92
93 $info['beyondwords']['fields']['compatible-post-types'] = [
94 'label' => __( 'Compatible post types', 'speechkit' ),
95 'value' => implode( ', ', \BeyondWords\Settings\Utils::get_compatible_post_types() ),
96 ];
97
98 $info['beyondwords']['fields']['integration-method'] = [
99 'label' => __( 'Integration method', 'speechkit' ),
100 'value' => \BeyondWords\Settings\Fields::get_integration_method(),
101 ];
102
103 $info['beyondwords']['fields']['beyondwords_api_key'] = [
104 'label' => __( 'API Key', 'speechkit' ),
105 'value' => self::mask_string( get_option( 'beyondwords_api_key' ) ),
106 ];
107
108 $info['beyondwords']['fields']['beyondwords_project_id'] = [
109 'label' => __( 'Project ID', 'speechkit' ),
110 'value' => get_option( 'beyondwords_project_id' ),
111 ];
112
113 self::add_preferences( $info );
114 self::add_filters( $info );
115 self::add_notice_settings( $info );
116
117 self::add_constant( $info, 'BEYONDWORDS_AUTOREGENERATE' );
118
119 return $info;
120 }
121
122 /**
123 * Add the per-site preferences (Preferences tab) to the debug array.
124 *
125 * @param array $info Debug array, modified in place.
126 */
127 public static function add_preferences( array &$info ): void {
128 $info['beyondwords']['fields']['beyondwords_prepend_excerpt'] = [
129 'label' => __( 'Include excerpt', 'speechkit' ),
130 'value' => get_option( 'beyondwords_prepend_excerpt' ) ? __( 'Yes', 'speechkit' ) : __( 'No', 'speechkit' ),
131 'debug' => get_option( 'beyondwords_prepend_excerpt' ) ? 'yes' : 'no',
132 ];
133
134 $info['beyondwords']['fields']['beyondwords_player_ui'] = [
135 'label' => __( 'Player UI', 'speechkit' ),
136 'value' => get_option( 'beyondwords_player_ui' ),
137 ];
138
139 $info['beyondwords']['fields']['beyondwords_preselect'] = [
140 'label' => __( 'Preselect ‘Generate audio’', 'speechkit' ),
141 'value' => (string) wp_json_encode( get_option( 'beyondwords_preselect' ), JSON_PRETTY_PRINT ),
142 ];
143 }
144
145 /**
146 * Add plugin version (with file/db version mismatch detection) to the debug array.
147 *
148 * @param array $info Debug array, modified in place.
149 */
150 public static function add_plugin_version( array &$info ): void {
151 $const_version = defined( 'BEYONDWORDS__PLUGIN_VERSION' ) ? BEYONDWORDS__PLUGIN_VERSION : '';
152 $db_version = get_option( 'beyondwords_version' );
153
154 if ( $const_version && $const_version === $db_version ) {
155 $info['beyondwords']['fields']['plugin-version'] = [
156 'label' => __( 'Plugin version', 'speechkit' ),
157 'value' => BEYONDWORDS__PLUGIN_VERSION,
158 ];
159 return;
160 }
161
162 $info['beyondwords']['fields']['plugin-version'] = [
163 'label' => __( 'Plugin version', 'speechkit' ),
164 'value' => sprintf(
165 /* translators: 1: Current plugin version, 2: Database plugin version */
166 __( 'Version mismatch: file: %1$s / db: %2$s', 'speechkit' ),
167 $const_version,
168 $db_version
169 ),
170 ];
171 }
172
173 /**
174 * Add REST API connectivity info to the debug array.
175 *
176 * @param array $info Debug array, modified in place.
177 */
178 public static function add_rest_api_connection( array &$info ): void {
179 $api_url = \BeyondWords\Core\Urls::get_api_url();
180
181 $info['beyondwords']['fields']['api-url'] = [
182 'label' => __( 'REST API URL', 'speechkit' ),
183 'value' => $api_url,
184 ];
185
186 $info['beyondwords']['fields']['api-communication'] = array_merge(
187 [ 'label' => __( 'Communication with REST API', 'speechkit' ) ],
188 self::get_api_communication( $api_url )
189 );
190 }
191
192 /**
193 * Resolve the "Communication with REST API" value/debug pair.
194 *
195 * Deliberately not cached: Site Health is a diagnostic, and core's own
196 * `dotorg_communication` field likewise probes uncached on every render.
197 *
198 * @param string $api_url BeyondWords REST API base URL.
199 *
200 * @return array<string,string>
201 */
202 private static function get_api_communication( string $api_url ): array {
203 // Nothing to probe until the site has API credentials.
204 if ( ! \BeyondWords\Settings\Utils::has_api_creds() ) {
205 return [
206 'value' => __( 'Not checked — BeyondWords API credentials are not configured', 'speechkit' ),
207 'debug' => 'not-configured',
208 ];
209 }
210
211 // A plain request on purpose: `call_api()` would clear the valid-connection
212 // flag on a 401, and the VIP helper's circuit breaker can fake failures.
213 $response = wp_remote_request(
214 $api_url,
215 [
216 'method' => 'GET',
217 'timeout' => \BeyondWords\Api\Client::DEFAULT_REQUEST_TIMEOUT,
218 ]
219 );
220
221 if ( ! is_wp_error( $response ) ) {
222 return [
223 'value' => __( 'BeyondWords API is reachable', 'speechkit' ),
224 'debug' => 'true',
225 ];
226 }
227
228 return [
229 'value' => sprintf(
230 /* translators: 1: The IP address the REST API resolves to. 2: The error returned by the request. */
231 __( 'Unable to reach BeyondWords API at %1$s: %2$s', 'speechkit' ),
232 self::resolve_api_host( $api_url ),
233 $response->get_error_message()
234 ),
235 'debug' => $response->get_error_message(),
236 ];
237 }
238
239 /**
240 * Resolve the API host to an IP address for the connectivity diagnostic.
241 *
242 * `gethostbyname()` needs a bare hostname, so the URL is parsed first; it
243 * returns the host (or URL) unchanged when resolution fails.
244 *
245 * @param string $api_url BeyondWords REST API base URL.
246 */
247 private static function resolve_api_host( string $api_url ): string {
248 $host = wp_parse_url( $api_url, PHP_URL_HOST );
249
250 if ( ! is_string( $host ) || '' === $host ) {
251 return $api_url;
252 }
253
254 return gethostbyname( $host );
255 }
256
257 /**
258 * Add registered + deprecated filter hook info to the debug array.
259 *
260 * @param array $info Debug array, modified in place.
261 */
262 public static function add_filters( array &$info ): void {
263 $registered = array_values( array_filter( self::FILTERS, 'has_filter' ) );
264
265 $info['beyondwords']['fields']['registered-filters'] = [
266 'label' => __( 'Registered filters', 'speechkit' ),
267 'value' => empty( $registered ) ? __( 'None', 'speechkit' ) : implode( ', ', $registered ),
268 'debug' => empty( $registered ) ? 'none' : implode( ', ', $registered ),
269 ];
270
271 $registered = array_values( array_filter( self::DEPRECATED_FILTERS, 'has_filter' ) );
272
273 $info['beyondwords']['fields']['registered-deprecated-filters'] = [
274 'label' => __( 'Registered deprecated filters', 'speechkit' ),
275 'value' => empty( $registered ) ? __( 'None', 'speechkit' ) : implode( ', ', $registered ),
276 'debug' => empty( $registered ) ? 'none' : implode( ', ', $registered ),
277 ];
278 }
279
280 /**
281 * Add notice/activation timestamps to the debug array.
282 *
283 * @param array $info Debug array, modified in place.
284 */
285 public static function add_notice_settings( array &$info ): void {
286 $info['beyondwords']['fields']['beyondwords_date_activated'] = [
287 'label' => __( 'Date Activated', 'speechkit' ),
288 'value' => get_option( 'beyondwords_date_activated', '' ),
289 ];
290
291 $info['beyondwords']['fields']['beyondwords_notice_review_dismissed'] = [
292 'label' => __( 'Review Notice Dismissed', 'speechkit' ),
293 'value' => get_option( 'beyondwords_notice_review_dismissed', '' ),
294 ];
295 }
296
297 /**
298 * Add a single constant's value to the debug array.
299 *
300 * @param array $info Debug array, modified in place.
301 * @param string $name Constant name.
302 */
303 public static function add_constant( array &$info, string $name ): void {
304 $value = __( 'Undefined', 'speechkit' );
305
306 if ( defined( $name ) ) {
307 $value = constant( $name );
308
309 if ( is_bool( $value ) ) {
310 $value = true === $value ? 'True' : 'False';
311 }
312 }
313
314 $info['beyondwords']['fields'][ $name ] = [
315 'label' => $name,
316 'value' => $value,
317 'debug' => $value,
318 ];
319 }
320
321 /**
322 * Mask a sensitive string for display in Site Health.
323 *
324 * @param string|false $value Value to mask. `false` (e.g. missing option) renders empty.
325 * @param int $count Number of trailing characters to preserve.
326 * @param string $char Character used to mask.
327 */
328 public static function mask_string( string|false $value, int $count = 4, string $char = 'X' ): string {
329 if ( ! is_string( $value ) ) {
330 return '';
331 }
332
333 if ( strlen( $value ) < 8 ) {
334 return str_repeat( $char, strlen( $value ) );
335 }
336
337 return str_repeat( $char, strlen( $value ) - $count ) . substr( $value, -$count );
338 }
339 }
340