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