PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / trunk
Secure Custom Fields vtrunk
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / src / Site_Health / Site_Health.php
secure-custom-fields / src / Site_Health Last commit date
AI_Usage.php 2 months ago Site_Health.php 1 month ago
Site_Health.php
1014 lines
1 <?php
2 /**
3 * ACF 6.8.0 feature port.
4 *
5 * @package wordpress/secure-custom-fields
6 */
7
8 // phpcs:disable -- Upstream ACF 6.8.0 feature-port files are kept close to source.
9
10 namespace SCF\Site_Health;
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 /**
16 * The ACF Site Health class responsible for populating SCF debug information in WordPress Site Health.
17 */
18 class Site_Health {
19
20 /**
21 * The option name used to store site health data.
22 *
23 * @var string
24 */
25 public string $option_name = 'acf_site_health';
26
27 /**
28 * Stores an instance of the AI_Usage helper class.
29 *
30 * @var AI_Usage
31 */
32 private AI_Usage $ai_usage;
33
34 /**
35 * Constructs the ACF_Site_Health class.
36 *
37 * @since 6.3
38 */
39 public function __construct() {
40 $this->ai_usage = new AI_Usage( $this );
41
42 add_filter( 'debug_information', array( $this, 'render_tab_content' ) );
43 add_action( 'acf_update_site_health_data', array( $this, 'update_site_health_data_event' ) );
44
45 $hook = 'acf_update_site_health_data';
46 $timestamp = wp_next_scheduled( $hook );
47
48 if ( ! $timestamp ) {
49 wp_schedule_event( time(), 'daily', $hook );
50 } elseif ( 'daily' !== wp_get_schedule( $hook ) ) {
51 wp_unschedule_event( $timestamp, $hook );
52 wp_schedule_event( time(), 'daily', $hook );
53 }
54
55 // ACF events.
56 add_action( 'acf/first_activated', array( $this, 'add_activation_event_action' ) );
57 add_action( 'acf/activated_pro', array( $this, 'add_activation_event_action' ) );
58 add_filter( 'acf/pre_update_field_group', array( $this, 'pre_update_acf_internal_cpt' ) );
59 add_filter( 'acf/pre_update_post_type', array( $this, 'pre_update_acf_internal_cpt' ) );
60 add_filter( 'acf/pre_update_taxonomy', array( $this, 'pre_update_acf_internal_cpt' ) );
61 add_filter( 'acf/pre_update_ui_options_page', array( $this, 'pre_update_acf_internal_cpt' ) );
62 }
63
64 /**
65 * Gets the stored site health information.
66 *
67 * @since 6.3
68 *
69 * @return array
70 */
71 public function get_site_health(): array {
72 $site_health = get_option( $this->option_name, '' );
73
74 if ( is_string( $site_health ) ) {
75 $site_health = json_decode( $site_health, true );
76 }
77
78 return is_array( $site_health ) ? $site_health : array();
79 }
80
81 /**
82 * Updates the site health information.
83 *
84 * @since 6.3
85 *
86 * @param array $data An array of site health information to update.
87 * @return boolean
88 */
89 public function update_site_health( array $data = array() ): bool {
90 return update_option( $this->option_name, wp_json_encode( $data ), false );
91 }
92
93 /**
94 * Updates the site health information for action callbacks.
95 *
96 * @since 6.8
97 *
98 * @param array $data Data to update with (optional).
99 * @return void
100 */
101 public function update_site_health_data_event( array $data = array() ): void {
102 $this->update_site_health_data( $data );
103 }
104
105 /**
106 * Stores debug data in the SCF site health option.
107 *
108 * @since 6.3
109 *
110 * @param array $data Data to update with (optional).
111 * @return boolean
112 */
113 public function update_site_health_data( array $data = array() ): bool {
114 if ( wp_doing_cron() ) {
115 // Bootstrap wp-admin, as WP_Cron doesn't do this for us.
116 require_once trailingslashit( ABSPATH ) . 'wp-admin/includes/admin.php';
117 }
118
119 $site_health = $this->get_site_health();
120 $values = ! empty( $data ) ? $data : $this->get_site_health_values();
121 $updated = array();
122
123 if ( ! empty( $values ) ) {
124 foreach ( $values as $key => $value ) {
125 $updated[ $key ] = $value['debug'] ?? $value['value'];
126 }
127 }
128
129 foreach ( $site_health as $key => $value ) {
130 // Preserve event_* keys for activation tracking.
131 if ( 'event_' === substr( $key, 0, 6 ) ) {
132 $updated[ $key ] = $value;
133 continue;
134 }
135
136 // Preserve AI usage data.
137 if ( 'ai_usage' === $key ) {
138 $updated[ $key ] = $value;
139 continue;
140 }
141 }
142
143 $this->maybe_log_first_registered_block( $site_health, $updated );
144
145 $updated['last_updated'] = time();
146
147 return $this->update_site_health( $updated );
148 }
149
150 /**
151 * Logs the first-run timestamp of a WP-CLI command.
152 *
153 * Stores an associative array under the `event_cli_commands` key in the
154 * site health option. Each entry maps a full command name (e.g.
155 * "acf json import") to the Unix timestamp when it was first executed.
156 * Subsequent calls for the same command are no-ops.
157 *
158 * @since 6.8
159 *
160 * @param string $command The full CLI command name (e.g. "acf json import").
161 * @return boolean True if a new entry was written, false if already recorded.
162 */
163 public function log_cli_command( string $command ): bool {
164 $site_health = $this->get_site_health();
165
166 if ( ! isset( $site_health['event_cli_commands'] ) || ! is_array( $site_health['event_cli_commands'] ) ) {
167 $site_health['event_cli_commands'] = array();
168 }
169
170 if ( isset( $site_health['event_cli_commands'][ $command ] ) ) {
171 return false;
172 }
173
174 $time = time();
175
176 $site_health['event_cli_commands'][ $command ] = $time;
177 $site_health['last_updated'] = $time;
178
179 return $this->update_site_health( $site_health );
180 }
181
182 /**
183 * Pushes an event to the SCF site health option.
184 *
185 * @since 6.3
186 *
187 * @param string $event_name The name of the event to push.
188 * @return boolean
189 */
190 public function add_site_health_event( string $event_name = '' ): bool {
191 $site_health = $this->get_site_health();
192
193 // Allow using action/filter hooks to set events.
194 if ( empty( $event_name ) ) {
195 $current_filter = current_filter();
196
197 if ( strpos( $current_filter, 'acf/' ) !== false ) {
198 $event_name = str_replace( 'acf/', '', $current_filter );
199 }
200 }
201
202 // Bail if this event was already stored.
203 if ( empty( $event_name ) || ! empty( $site_health[ 'event_' . $event_name ] ) ) {
204 return false;
205 }
206
207 $time = time();
208
209 $site_health[ 'event_' . $event_name ] = $time;
210 $site_health['last_updated'] = $time;
211
212 return $this->update_site_health( $site_health );
213 }
214
215 /**
216 * Logs activation events for free/pro.
217 *
218 * @since 6.3
219 *
220 * @return boolean
221 */
222 public function add_activation_event() {
223 $event_name = 'first_activated';
224
225 if ( acf_is_pro() ) {
226 $event_name = 'first_activated_pro';
227
228 if ( 'acf/first_activated' !== current_filter() ) {
229 $site_health = $this->get_site_health();
230
231 /**
232 * We already have an event for when pro was first activated,
233 * so we don't need to log an additional event here.
234 */
235 if ( ! empty( $site_health[ 'event_' . $event_name ] ) ) {
236 return false;
237 }
238
239 $event_name = 'activated_pro';
240 }
241 }
242
243 return $this->add_site_health_event( $event_name );
244 }
245
246 /**
247 * Logs activation events for action callbacks.
248 *
249 * @since 6.8
250 *
251 * @return void
252 */
253 public function add_activation_event_action(): void {
254 $this->add_activation_event();
255 }
256
257 /**
258 * Logs when a site first registers an ACF Block.
259 *
260 * Only sets the event when the stored registered_acf_blocks count
261 * transitions from 0 to a positive number. Requires a previously
262 * stored count of 0 to avoid backfilling existing users. Tracks a
263 * has_had_blocks flag to prevent false positives when users remove
264 * all blocks and later re-add them.
265 *
266 * @since 6.8
267 *
268 * @param array $site_health The previously stored site health data.
269 * @param array $updated The updated site health data. This array may be modified to include the first registered block event timestamp.
270 * @return void
271 */
272 public function maybe_log_first_registered_block( array $site_health, array &$updated ): void {
273 if ( ! acf_is_pro() ) {
274 return;
275 }
276
277 if ( ! empty( $site_health['has_had_blocks'] ) ) {
278 $updated['has_had_blocks'] = true;
279 }
280
281 // Already tracked — event is immutable.
282 if ( ! empty( $updated['event_first_registered_block'] ) ) {
283 return;
284 }
285
286 // No previous block count stored — site predates this field. Skip to establish a baseline.
287 if ( ! isset( $site_health['registered_acf_blocks'] ) ) {
288 return;
289 }
290
291 // Blocks were previously registered — set flag and skip.
292 if ( (int) $site_health['registered_acf_blocks'] > 0 ) {
293 $updated['has_had_blocks'] = true;
294 return;
295 }
296
297 // Flag already set — user had blocks before (remove-then-re-add scenario).
298 if ( ! empty( $updated['has_had_blocks'] ) ) {
299 return;
300 }
301
302 // Transition from 0 → positive: first time registering a block.
303 if ( $this->get_registered_block_count() > 0 ) {
304 $updated['event_first_registered_block'] = time();
305 $updated['has_had_blocks'] = true;
306 }
307 }
308
309 /**
310 * Gets the number of registered SCF blocks.
311 *
312 * @since 6.8
313 *
314 * @return integer
315 */
316 private function get_registered_block_count(): int {
317 return count( acf_get_block_types() );
318 }
319
320 /**
321 * Adds events when ACF internal post types are created.
322 *
323 * @since 6.3
324 *
325 * @param array $post The post about to be updated.
326 * @return array
327 */
328 public function pre_update_acf_internal_cpt( array $post = array() ): array {
329 if ( empty( $post['key'] ) ) {
330 return $post;
331 }
332
333 $post_type = acf_determine_internal_post_type( $post['key'] );
334
335 if ( $post_type ) {
336 $posts = acf_get_internal_post_type_posts( $post_type );
337
338 if ( empty( $posts ) ) {
339 $post_type = str_replace(
340 array(
341 'acf-',
342 '-',
343 ),
344 array(
345 '',
346 '_',
347 ),
348 $post_type
349 );
350 $this->add_site_health_event( 'first_created_' . $post_type );
351 }
352 }
353
354 return $post;
355 }
356
357 /**
358 * Appends the ACF section to the "Info" tab of the WordPress Site Health screen.
359 *
360 * @since 6.3
361 *
362 * @param array $debug_info The current debug info for site health.
363 * @return array The debug info appended with the ACF section.
364 */
365 public function render_tab_content( array $debug_info ): array {
366 $data = $this->get_site_health_values();
367
368 $this->update_site_health_data( $data );
369
370 // Unset values we don't want to display yet.
371 $fields_to_unset = array(
372 'wp_version',
373 'mysql_version',
374 'is_multisite',
375 'active_theme',
376 'parent_theme',
377 'active_plugins',
378 'number_of_fields_by_type',
379 'number_of_third_party_fields_by_type',
380 'field_groups_with_single_block_rule',
381 'field_groups_with_multiple_block_rules',
382 'field_groups_with_blocks_and_other_rules',
383 'all_location_rules',
384 );
385
386 foreach ( $fields_to_unset as $field ) {
387 if ( isset( $data[ $field ] ) ) {
388 unset( $data[ $field ] );
389 }
390 }
391
392 foreach ( $data as $key => $value ) {
393 if ( 'event_' === substr( $key, 0, 6 ) ) {
394 unset( $data[ $key ] );
395 }
396 }
397
398 $debug_info['secure-custom-fields'] = array(
399 'label' => __( 'SCF', 'secure-custom-fields' ),
400 'description' => __( 'This section contains debug information about your SCF configuration which can be useful to provide to support.', 'secure-custom-fields' ),
401 'fields' => $data,
402 );
403
404 return $debug_info;
405 }
406
407 /**
408 * Gets the values for all data in the SCF site health section.
409 *
410 * @since 6.3
411 *
412 * @return array
413 */
414 public function get_site_health_values(): array {
415 global $wpdb;
416
417 $fields = array();
418 $is_pro = acf_is_pro();
419 $field_groups = acf_get_field_groups();
420 $post_types = acf_get_acf_post_types();
421 $taxonomies = acf_get_acf_taxonomies();
422
423 $yes = __( 'Yes', 'secure-custom-fields' );
424 $no = __( 'No', 'secure-custom-fields' );
425 $enabled = __( 'Enabled', 'secure-custom-fields' );
426 $disabled = __( 'Disabled', 'secure-custom-fields' );
427
428 $fields['version'] = array(
429 'label' => __( 'Plugin Version', 'secure-custom-fields' ),
430 'value' => defined( 'ACF_VERSION' ) ? ACF_VERSION : '',
431 );
432
433 $fields['plugin_type'] = array(
434 'label' => __( 'Plugin Type', 'secure-custom-fields' ),
435 'value' => __( 'Secure Custom Fields', 'secure-custom-fields' ),
436 'debug' => 'Secure Custom Fields',
437 );
438
439 $fields['update_source'] = array(
440 'label' => __( 'Update Source', 'secure-custom-fields' ),
441 'value' => apply_filters( 'acf/site_health/update_source', __( 'wordpress.org', 'secure-custom-fields' ) ),
442 );
443
444 $fields['wp_version'] = array(
445 'label' => __( 'WordPress Version', 'secure-custom-fields' ),
446 'value' => get_bloginfo( 'version' ),
447 );
448
449 $fields['mysql_version'] = array(
450 'label' => __( 'MySQL Version', 'secure-custom-fields' ),
451 'value' => $wpdb->db_server_info(),
452 );
453
454 $fields['is_multisite'] = array(
455 'label' => __( 'Is Multisite', 'secure-custom-fields' ),
456 'value' => is_multisite() ? __( 'Yes', 'secure-custom-fields' ) : __( 'No', 'secure-custom-fields' ),
457 'debug' => is_multisite(),
458 );
459
460 $active_theme = wp_get_theme();
461 $parent_theme = $active_theme->parent();
462
463 $fields['active_theme'] = array(
464 'label' => __( 'Active Theme', 'secure-custom-fields' ),
465 'value' => array(
466 'name' => $active_theme->get( 'Name' ),
467 'version' => $active_theme->get( 'Version' ),
468 'theme_uri' => $active_theme->get( 'ThemeURI' ),
469 'stylesheet' => $active_theme->get( 'Stylesheet' ),
470 ),
471 );
472
473 if ( $parent_theme ) {
474 $fields['parent_theme'] = array(
475 'label' => __( 'Parent Theme', 'secure-custom-fields' ),
476 'value' => array(
477 'name' => $parent_theme->get( 'Name' ),
478 'version' => $parent_theme->get( 'Version' ),
479 'theme_uri' => $parent_theme->get( 'ThemeURI' ),
480 'stylesheet' => $parent_theme->get( 'Stylesheet' ),
481 ),
482 );
483 }
484
485 $active_plugins = array();
486 $plugins = get_plugins();
487
488 foreach ( $plugins as $plugin_path => $plugin ) {
489 if ( ! is_plugin_active( $plugin_path ) ) {
490 continue;
491 }
492
493 $active_plugins[ $plugin_path ] = array(
494 'name' => $plugin['Name'],
495 'version' => $plugin['Version'],
496 'plugin_uri' => empty( $plugin['PluginURI'] ) ? '' : $plugin['PluginURI'],
497 );
498 }
499
500 $fields['active_plugins'] = array(
501 'label' => __( 'Active Plugins', 'secure-custom-fields' ),
502 'value' => $active_plugins,
503 );
504
505 $ui_field_groups = array_filter(
506 $field_groups,
507 function ( $field_group ) {
508 return empty( $field_group['local'] );
509 }
510 );
511
512 $fields['ui_field_groups'] = array(
513 'label' => __( 'Registered Field Groups (UI)', 'secure-custom-fields' ),
514 'value' => number_format_i18n( count( $ui_field_groups ) ),
515 );
516
517 $php_field_groups = array_filter(
518 $field_groups,
519 function ( $field_group ) {
520 return ! empty( $field_group['local'] ) && 'PHP' === $field_group['local'];
521 }
522 );
523
524 $fields['php_field_groups'] = array(
525 'label' => __( 'Registered Field Groups (PHP)', 'secure-custom-fields' ),
526 'value' => number_format_i18n( count( $php_field_groups ) ),
527 );
528
529 $json_field_groups = array_filter(
530 $field_groups,
531 function ( $field_group ) {
532 return ! empty( $field_group['local'] ) && 'json' === $field_group['local'];
533 }
534 );
535
536 $fields['json_field_groups'] = array(
537 'label' => __( 'Registered Field Groups (JSON)', 'secure-custom-fields' ),
538 'value' => number_format_i18n( count( $json_field_groups ) ),
539 );
540
541 $rest_field_groups = array_filter(
542 $field_groups,
543 function ( $field_group ) {
544 return ! empty( $field_group['show_in_rest'] );
545 }
546 );
547
548 $fields['rest_field_groups'] = array(
549 'label' => __( 'Field Groups Enabled for REST API', 'secure-custom-fields' ),
550 'value' => number_format_i18n( count( $rest_field_groups ) ),
551 );
552
553 $graphql_field_groups = array_filter(
554 $field_groups,
555 function ( $field_group ) {
556 return ! empty( $field_group['show_in_graphql'] );
557 }
558 );
559
560 if ( is_plugin_active( 'wpgraphql-acf/wpgraphql-acf.php' ) ) {
561 $fields['graphql_field_groups'] = array(
562 'label' => __( 'Field Groups Enabled for GraphQL', 'secure-custom-fields' ),
563 'value' => number_format_i18n( count( $graphql_field_groups ) ),
564 );
565 }
566
567 $all_fields = array();
568 $object_types = array();
569 $all_rules = array();
570
571 foreach ( $field_groups as $field_group ) {
572 $all_fields = array_merge( $all_fields, acf_get_fields( $field_group ) );
573
574 foreach ( $field_group['location'] as $rules ) {
575 foreach ( $rules as $rule ) {
576 if ( empty( $rule['param'] ) ) {
577 continue;
578 }
579
580 $operator = ! empty( $rule['operator'] ) ? $rule['operator'] : '';
581 $value = ! empty( $rule['value'] ) ? $rule['value'] : '';
582 $all_rules[] = $rule['param'] . $operator . $value;
583
584 if ( ! $is_pro ) {
585 continue;
586 }
587
588 $location = acf_get_location_type( $rule['param'] );
589 if ( ! $location ) {
590 continue;
591 }
592
593 $location_type = $location->get_object_type( $rule );
594 $object_types[ $field_group['key'] ][] = $location_type;
595 }
596 }
597 }
598
599 $fields['all_location_rules'] = array(
600 'label' => __( 'All Location Rules', 'secure-custom-fields' ),
601 'value' => array_values( array_unique( $all_rules ) ),
602 );
603
604 if ( $is_pro ) {
605 $field_groups_with_single_block_rule = 0;
606 $field_groups_with_multiple_block_rules = 0;
607 $field_groups_with_blocks_and_other_rules = 0;
608
609 foreach ( $object_types as $types ) {
610 $num_types = array_count_values( $types );
611
612 // Bail if no block location rules.
613 if ( empty( $num_types['block'] ) ) {
614 continue;
615 }
616
617 if ( count( $num_types ) === 1 ) {
618 // Field group is only assigned to blocks.
619 if ( $num_types['block'] === 1 ) {
620 ++$field_groups_with_single_block_rule;
621 } else {
622 ++$field_groups_with_multiple_block_rules;
623 }
624 } else {
625 // Field group is assigned to blocks & other stuff.
626 ++$field_groups_with_blocks_and_other_rules;
627 }
628 }
629
630 $fields['field_groups_with_single_block_rule'] = array(
631 'label' => __( 'Number of Field Groups with a Single Block Location', 'secure-custom-fields' ),
632 'value' => number_format_i18n( $field_groups_with_single_block_rule ),
633 );
634
635 $fields['field_groups_with_multiple_block_rules'] = array(
636 'label' => __( 'Number of Field Groups with Multiple Block Locations', 'secure-custom-fields' ),
637 'value' => number_format_i18n( $field_groups_with_multiple_block_rules ),
638 );
639
640 $fields['field_groups_with_blocks_and_other_rules'] = array(
641 'label' => __( 'Number of Field Groups with Blocks and Other Locations', 'secure-custom-fields' ),
642 'value' => number_format_i18n( $field_groups_with_blocks_and_other_rules ),
643 );
644 }
645
646 $fields_by_type = array();
647 $third_party_fields_by_type = array();
648 $core_field_types = array_keys( acf_get_field_types() );
649
650 foreach ( $all_fields as $field ) {
651 if ( in_array( $field['type'], $core_field_types, true ) ) {
652 if ( ! isset( $fields_by_type[ $field['type'] ] ) ) {
653 $fields_by_type[ $field['type'] ] = 0;
654 }
655
656 ++$fields_by_type[ $field['type'] ];
657
658 continue;
659 }
660
661 if ( ! isset( $third_party_fields_by_type[ $field['type'] ] ) ) {
662 $third_party_fields_by_type[ $field['type'] ] = 0;
663 }
664
665 ++$third_party_fields_by_type[ $field['type'] ];
666 }
667
668 $fields['number_of_fields_by_type'] = array(
669 'label' => __( 'Number of Fields by Field Type', 'secure-custom-fields' ),
670 'value' => $fields_by_type,
671 );
672
673 $fields['number_of_third_party_fields_by_type'] = array(
674 'label' => __( 'Number of Third Party Fields by Field Type', 'secure-custom-fields' ),
675 'value' => $third_party_fields_by_type,
676 );
677
678 $enable_post_types = acf_get_setting( 'enable_post_types' );
679
680 $fields['post_types_enabled'] = array(
681 'label' => __( 'Post Types and Taxonomies Enabled', 'secure-custom-fields' ),
682 'value' => $enable_post_types ? $yes : $no,
683 'debug' => $enable_post_types,
684 );
685
686 $ui_post_types = array_filter(
687 $post_types,
688 function ( $post_type ) {
689 return ! empty( $post_type['ID'] );
690 }
691 );
692
693 $fields['ui_post_types'] = array(
694 'label' => __( 'Registered Post Types (UI)', 'secure-custom-fields' ),
695 'value' => number_format_i18n( count( $ui_post_types ) ),
696 );
697
698 $json_post_types = array_filter(
699 $post_types,
700 function ( $post_type ) {
701 return ! empty( $post_type['local'] ) && 'json' === $post_type['local'] && empty( $post_type['ID'] );
702 }
703 );
704
705 $fields['json_post_types'] = array(
706 'label' => __( 'Registered Post Types (JSON)', 'secure-custom-fields' ),
707 'value' => number_format_i18n( count( $json_post_types ) ),
708 );
709
710 $ui_taxonomies = array_filter(
711 $taxonomies,
712 function ( $taxonomy ) {
713 return ! empty( $taxonomy['ID'] );
714 }
715 );
716
717 $fields['ui_taxonomies'] = array(
718 'label' => __( 'Registered Taxonomies (UI)', 'secure-custom-fields' ),
719 'value' => number_format_i18n( count( $ui_taxonomies ) ),
720 );
721
722 $json_taxonomies = array_filter(
723 $taxonomies,
724 function ( $taxonomy ) {
725 return ! empty( $taxonomy['local'] ) && 'json' === $taxonomy['local'] && empty( $taxonomy['ID'] );
726 }
727 );
728
729 $fields['json_taxonomies'] = array(
730 'label' => __( 'Registered Taxonomies (JSON)', 'secure-custom-fields' ),
731 'value' => number_format_i18n( count( $json_taxonomies ) ),
732 );
733
734 if ( $is_pro ) {
735 $enable_options_pages_ui = acf_get_setting( 'enable_options_pages_ui' );
736
737 $fields['ui_options_pages_enabled'] = array(
738 'label' => __( 'Options Pages UI Enabled', 'secure-custom-fields' ),
739 'value' => $enable_options_pages_ui ? $yes : $no,
740 'debug' => $enable_options_pages_ui,
741 );
742
743 $options_pages = acf_get_options_pages();
744 $ui_options_pages = array();
745
746 if ( empty( $options_pages ) || ! is_array( $options_pages ) ) {
747 $options_pages = array();
748 }
749
750 if ( $enable_options_pages_ui ) {
751 $ui_options_pages = acf_get_ui_options_pages();
752
753 $ui_options_pages_in_ui = array_filter(
754 $ui_options_pages,
755 function ( $ui_options_page ) {
756 return ! empty( $ui_options_page['ID'] );
757 }
758 );
759
760 $json_options_pages = array_filter(
761 $ui_options_pages,
762 function ( $ui_options_page ) {
763 return ! empty( $ui_options_page['local'] ) && empty( $ui_options_page['ID'] );
764 }
765 );
766
767 $fields['ui_options_pages'] = array(
768 'label' => __( 'Registered Options Pages (UI)', 'secure-custom-fields' ),
769 'value' => number_format_i18n( count( $ui_options_pages_in_ui ) ),
770 );
771
772 $fields['json_options_pages'] = array(
773 'label' => __( 'Registered Options Pages (JSON)', 'secure-custom-fields' ),
774 'value' => number_format_i18n( count( $json_options_pages ) ),
775 );
776 }
777
778 $ui_options_page_slugs = array_column( $ui_options_pages, 'menu_slug' );
779 $php_options_pages = array_filter(
780 $options_pages,
781 function ( $options_page ) use ( $ui_options_page_slugs ) {
782 return ! in_array( $options_page['menu_slug'], $ui_options_page_slugs, true );
783 }
784 );
785
786 $fields['php_options_pages'] = array(
787 'label' => __( 'Registered Options Pages (PHP)', 'secure-custom-fields' ),
788 'value' => number_format_i18n( count( $php_options_pages ) ),
789 );
790 }
791
792 $rest_api_format = acf_get_setting( 'rest_api_format' );
793
794 $fields['rest_api_format'] = array(
795 'label' => __( 'REST API Format', 'secure-custom-fields' ),
796 'value' => 'standard' === $rest_api_format ? __( 'Standard', 'secure-custom-fields' ) : __( 'Light', 'secure-custom-fields' ),
797 'debug' => $rest_api_format,
798 );
799
800 $schema_objects = array(
801 'blocks' => 0,
802 'post_types' => 0,
803 );
804
805 if ( $is_pro ) {
806 $fields['registered_acf_blocks'] = array(
807 'label' => __( 'Registered SCF Blocks', 'secure-custom-fields' ),
808 'value' => number_format_i18n( $this->get_registered_block_count() ),
809 );
810
811 $blocks = acf_get_block_types();
812 $block_api_versions = array();
813 $acf_block_versions = array();
814 $blocks_using_post_meta = 0;
815 $blocks_using_auto_inline_editing = 0;
816
817 foreach ( $blocks as $block ) {
818 if ( ! isset( $block_api_versions[ 'v' . $block['api_version'] ] ) ) {
819 $block_api_versions[ 'v' . $block['api_version'] ] = 0;
820 }
821
822 if ( ! isset( $acf_block_versions[ 'v' . $block['acf_block_version'] ] ) ) {
823 $acf_block_versions[ 'v' . $block['acf_block_version'] ] = 0;
824 }
825
826 if ( ! empty( $block['use_post_meta'] ) ) {
827 ++$blocks_using_post_meta;
828 }
829
830 if ( ! empty( $block['auto_inline_editing'] ) ) {
831 ++$blocks_using_auto_inline_editing;
832 }
833
834 if ( ! empty( $block['auto_jsonld'] ) ) {
835 ++$schema_objects['blocks'];
836 }
837
838 ++$block_api_versions[ 'v' . $block['api_version'] ];
839 ++$acf_block_versions[ 'v' . $block['acf_block_version'] ];
840 }
841
842 $fields['blocks_per_api_version'] = array(
843 'label' => __( 'Blocks Per API Version', 'secure-custom-fields' ),
844 'value' => $block_api_versions,
845 );
846
847 $fields['blocks_per_acf_block_version'] = array(
848 'label' => __( 'Blocks Per SCF Block Version', 'secure-custom-fields' ),
849 'value' => $acf_block_versions,
850 );
851
852 $fields['blocks_using_post_meta'] = array(
853 'label' => __( 'Blocks Using Post Meta', 'secure-custom-fields' ),
854 'value' => number_format_i18n( $blocks_using_post_meta ),
855 );
856
857 $fields['blocks_using_auto_inline_editing'] = array(
858 'label' => __( 'Blocks Using Auto Inline Editing', 'secure-custom-fields' ),
859 'value' => number_format_i18n( $blocks_using_auto_inline_editing ),
860 );
861
862 $preload_blocks = acf_get_setting( 'preload_blocks' );
863
864 $fields['preload_blocks'] = array(
865 'label' => __( 'Block Preloading Enabled', 'secure-custom-fields' ),
866 'value' => ! empty( $preload_blocks ) ? $yes : $no,
867 'debug' => $preload_blocks,
868 );
869 }
870
871 $show_admin = acf_get_setting( 'show_admin' );
872
873 $fields['admin_ui_enabled'] = array(
874 'label' => __( 'Admin UI Enabled', 'secure-custom-fields' ),
875 'value' => $show_admin ? $yes : $no,
876 'debug' => $show_admin,
877 );
878
879 $field_type_modal_enabled = apply_filters( 'acf/field_group/enable_field_browser', true );
880
881 $fields['field_type-modal_enabled'] = array(
882 'label' => __( 'Field Type Modal Enabled', 'secure-custom-fields' ),
883 'value' => ! empty( $field_type_modal_enabled ) ? $yes : $no,
884 'debug' => $field_type_modal_enabled,
885 );
886
887 $field_settings_tabs_enabled = apply_filters( 'acf/field_group/disable_field_settings_tabs', false );
888
889 $fields['field_settings_tabs_enabled'] = array(
890 'label' => __( 'Field Settings Tabs Enabled', 'secure-custom-fields' ),
891 'value' => empty( $field_settings_tabs_enabled ) ? $yes : $no,
892 'debug' => $field_settings_tabs_enabled,
893 );
894
895 $shortcode_enabled = acf_get_setting( 'enable_shortcode' );
896
897 $fields['shortcode_enabled'] = array(
898 'label' => __( 'Shortcode Enabled', 'secure-custom-fields' ),
899 'value' => ! empty( $shortcode_enabled ) ? $yes : $no,
900 'debug' => $shortcode_enabled,
901 );
902
903 $fields['registered_acf_forms'] = array(
904 'label' => __( 'Registered SCF Forms', 'secure-custom-fields' ),
905 'value' => number_format_i18n( count( acf_get_forms() ) ),
906 );
907
908 $local_json = acf_get_instance( 'ACF_Local_JSON' );
909 $save_paths = $local_json->get_save_paths();
910 $load_paths = $local_json->get_load_paths();
911
912 $fields['json_save_paths'] = array(
913 'label' => __( 'JSON Save Paths', 'secure-custom-fields' ),
914 'value' => number_format_i18n( count( $save_paths ) ),
915 'debug' => count( $save_paths ),
916 );
917
918 $fields['json_load_paths'] = array(
919 'label' => __( 'JSON Load Paths', 'secure-custom-fields' ),
920 'value' => number_format_i18n( count( $load_paths ) ),
921 'debug' => count( $load_paths ),
922 );
923
924 $ai_enabled = acf_get_setting( 'enable_acf_ai' );
925
926 $fields['ai_enabled'] = array(
927 'label' => __( 'AI Support', 'secure-custom-fields' ),
928 'value' => ! empty( $ai_enabled ) ? $enabled : $disabled,
929 'debug' => $ai_enabled,
930 );
931
932 // Add AI usage metrics if enabled and the Abilities API is available (WP 6.9+).
933 $abilities_api_available = function_exists( 'wp_register_ability' );
934
935 if ( $ai_enabled && ! $abilities_api_available ) {
936 $fields['ai_abilities_api'] = array(
937 'label' => __( 'Abilities API', 'secure-custom-fields' ),
938 'value' => __( 'Not available (requires WordPress 6.9+)', 'secure-custom-fields' ),
939 'debug' => 'unavailable',
940 );
941 }
942
943 if ( $ai_enabled && $abilities_api_available ) {
944 $ai_ready_counts = $this->ai_usage->get_ai_ready_counts( $field_groups, $post_types, $taxonomies );
945 $ai_usage = $this->ai_usage->get_usage_metrics();
946
947 $fields['ai_ready_objects'] = array(
948 'label' => __( 'AI-Ready Objects', 'secure-custom-fields' ),
949 'value' => sprintf(
950 /* translators: 1: field group count, 2: post type count, 3: taxonomy count */
951 __( '%1$d Field Groups, %2$d Custom Post Types, %3$d Taxonomies', 'secure-custom-fields' ),
952 $ai_ready_counts['field_groups'],
953 $ai_ready_counts['post_types'],
954 $ai_ready_counts['taxonomies'],
955 ),
956 'debug' => $ai_ready_counts,
957 );
958
959 $executions_text = sprintf(
960 /* translators: %s - number of successful tasks performed */
961 _n( '%s (Successful task performed)', '%s (Successful tasks performed)', $ai_usage['total_executions'], 'secure-custom-fields' ),
962 number_format_i18n( $ai_usage['total_executions'] )
963 );
964
965 $fields['ai_executions'] = array(
966 'label' => __( 'AI Actions Executed', 'secure-custom-fields' ),
967 'value' => $executions_text,
968 'debug' => $ai_usage['total_executions'],
969 );
970
971 if ( $ai_usage['error_count'] > 0 ) {
972 $fields['ai_errors'] = array(
973 'label' => __( 'AI Execution Errors', 'secure-custom-fields' ),
974 'value' => number_format_i18n( $ai_usage['error_count'] ),
975 'debug' => $ai_usage['error_count'],
976 );
977 }
978 }
979
980 $schema_support = acf_get_setting( 'enable_schema' );
981 $fields['schema_support'] = array(
982 'label' => __( 'Schema Support', 'secure-custom-fields' ),
983 'value' => ! empty( $schema_support ) ? $enabled : $disabled,
984 'debug' => $schema_support,
985 );
986
987 foreach ( $post_types as $post_type ) {
988 if ( ! empty( $post_type['active'] ) && ! empty( $post_type['auto_jsonld'] ) ) {
989 ++$schema_objects['post_types'];
990 }
991 }
992
993 $fields['schema_ready_objects'] = array(
994 'label' => __( 'Objects with Schema Support', 'secure-custom-fields' ),
995 'value' => sprintf(
996 /* translators: 1: number of post types, 2: number of blocks using ACF schema */
997 __( '%1$d Post Types, %2$d Blocks', 'secure-custom-fields' ),
998 $schema_objects['post_types'],
999 $schema_objects['blocks'],
1000 ),
1001 'debug' => $schema_objects,
1002 );
1003
1004 $datastore_enabled = function_exists( 'acf_is_using_datastore' ) && acf_is_using_datastore();
1005 $fields['datastore_enabled'] = array(
1006 'label' => __( 'Datastore Enabled', 'secure-custom-fields' ),
1007 'value' => $datastore_enabled ? $enabled : $disabled,
1008 'debug' => $datastore_enabled,
1009 );
1010
1011 return $fields;
1012 }
1013 }
1014