PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.8
Advanced Custom Fields (ACF®) v6.8.8
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 2 weeks ago
Site_Health.php
1039 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 'field_groups_by_post_type',
353 );
354
355 foreach ( $fields_to_unset as $field ) {
356 if ( isset( $data[ $field ] ) ) {
357 unset( $data[ $field ] );
358 }
359 }
360
361 foreach ( $data as $key => $value ) {
362 if ( 'event_' === substr( $key, 0, 6 ) ) {
363 unset( $data[ $key ] );
364 }
365 }
366
367 $debug_info['acf'] = array(
368 'label' => __( 'ACF', 'acf' ),
369 'description' => __( 'This section contains debug information about your ACF configuration which can be useful to provide to support.', 'acf' ),
370 'fields' => $data,
371 );
372
373 return $debug_info;
374 }
375
376 /**
377 * Gets the values for all data in the ACF site health section.
378 *
379 * @since 6.3
380 *
381 * @return array
382 */
383 public function get_site_health_values(): array {
384 global $wpdb;
385
386 $fields = array();
387 $is_pro = acf_is_pro();
388 $license = $is_pro ? acf_pro_get_license() : array();
389 $license_status = $is_pro ? acf_pro_get_license_status() : array();
390 $field_groups = acf_get_field_groups();
391 $post_types = acf_get_acf_post_types();
392 $taxonomies = acf_get_acf_taxonomies();
393
394 $yes = __( 'Yes', 'acf' );
395 $no = __( 'No', 'acf' );
396 $enabled = __( 'Enabled', 'acf' );
397 $disabled = __( 'Disabled', 'acf' );
398
399 $fields['version'] = array(
400 'label' => __( 'Plugin Version', 'acf' ),
401 'value' => defined( 'ACF_VERSION' ) ? ACF_VERSION : '',
402 );
403
404 $fields['plugin_type'] = array(
405 'label' => __( 'Plugin Type', 'acf' ),
406 'value' => $is_pro ? __( 'PRO', 'acf' ) : __( 'Free', 'acf' ),
407 'debug' => $is_pro ? 'PRO' : 'Free',
408 );
409
410 $fields['update_source'] = array(
411 'label' => __( 'Update Source', 'acf' ),
412 'value' => apply_filters( 'acf/site_health/update_source', __( 'wordpress.org', 'acf' ) ),
413 );
414
415 if ( $is_pro ) {
416 $fields['activated'] = array(
417 'label' => __( 'License Activated', 'acf' ),
418 'value' => ! empty( $license ) ? $yes : $no,
419 'debug' => ! empty( $license ),
420 );
421
422 $fields['activated_url'] = array(
423 'label' => __( 'Licensed URL', 'acf' ),
424 'value' => ! empty( $license['url'] ) ? $license['url'] : '',
425 );
426
427 $fields['license_type'] = array(
428 'label' => __( 'License Type', 'acf' ),
429 'value' => $license_status['name'],
430 );
431
432 $fields['license_status'] = array(
433 'label' => __( 'License Status', 'acf' ),
434 'value' => $license_status['status'],
435 );
436
437 $expiry = ! empty( $license_status['expiry'] ) ? $license_status['expiry'] : '';
438 $format = get_option( 'date_format', 'F j, Y' );
439
440 $fields['subscription_expires'] = array(
441 'label' => __( 'Subscription Expiry Date', 'acf' ),
442 'value' => is_numeric( $expiry ) ? date_i18n( $format, $expiry ) : '',
443 'debug' => $expiry,
444 );
445 }
446
447 $fields['wp_version'] = array(
448 'label' => __( 'WordPress Version', 'acf' ),
449 'value' => get_bloginfo( 'version' ),
450 );
451
452 $fields['mysql_version'] = array(
453 'label' => __( 'MySQL Version', 'acf' ),
454 'value' => $wpdb->db_server_info(),
455 );
456
457 $fields['is_multisite'] = array(
458 'label' => __( 'Is Multisite', 'acf' ),
459 'value' => is_multisite() ? __( 'Yes', 'acf' ) : __( 'No', 'acf' ),
460 'debug' => is_multisite(),
461 );
462
463 $active_theme = wp_get_theme();
464 $parent_theme = $active_theme->parent();
465
466 $fields['active_theme'] = array(
467 'label' => __( 'Active Theme', 'acf' ),
468 'value' => array(
469 'name' => $active_theme->get( 'Name' ),
470 'version' => $active_theme->get( 'Version' ),
471 'theme_uri' => $active_theme->get( 'ThemeURI' ),
472 'stylesheet' => $active_theme->get( 'Stylesheet' ),
473 ),
474 );
475
476 if ( $parent_theme ) {
477 $fields['parent_theme'] = array(
478 'label' => __( 'Parent Theme', 'acf' ),
479 'value' => array(
480 'name' => $parent_theme->get( 'Name' ),
481 'version' => $parent_theme->get( 'Version' ),
482 'theme_uri' => $parent_theme->get( 'ThemeURI' ),
483 'stylesheet' => $parent_theme->get( 'Stylesheet' ),
484 ),
485 );
486 }
487
488 $active_plugins = array();
489 $plugins = get_plugins();
490
491 foreach ( $plugins as $plugin_path => $plugin ) {
492 if ( ! is_plugin_active( $plugin_path ) ) {
493 continue;
494 }
495
496 $active_plugins[ $plugin_path ] = array(
497 'name' => $plugin['Name'],
498 'version' => $plugin['Version'],
499 'plugin_uri' => empty( $plugin['PluginURI'] ) ? '' : $plugin['PluginURI'],
500 );
501 }
502
503 $fields['active_plugins'] = array(
504 'label' => __( 'Active Plugins', 'acf' ),
505 'value' => $active_plugins,
506 );
507
508 $ui_field_groups = array_filter(
509 $field_groups,
510 function ( $field_group ) {
511 return empty( $field_group['local'] );
512 }
513 );
514
515 $fields['ui_field_groups'] = array(
516 'label' => __( 'Registered Field Groups (UI)', 'acf' ),
517 'value' => number_format_i18n( count( $ui_field_groups ) ),
518 );
519
520 $php_field_groups = array_filter(
521 $field_groups,
522 function ( $field_group ) {
523 return ! empty( $field_group['local'] ) && 'PHP' === $field_group['local'];
524 }
525 );
526
527 $fields['php_field_groups'] = array(
528 'label' => __( 'Registered Field Groups (PHP)', 'acf' ),
529 'value' => number_format_i18n( count( $php_field_groups ) ),
530 );
531
532 $json_field_groups = array_filter(
533 $field_groups,
534 function ( $field_group ) {
535 return ! empty( $field_group['local'] ) && 'json' === $field_group['local'];
536 }
537 );
538
539 $fields['json_field_groups'] = array(
540 'label' => __( 'Registered Field Groups (JSON)', 'acf' ),
541 'value' => number_format_i18n( count( $json_field_groups ) ),
542 );
543
544 $rest_field_groups = array_filter(
545 $field_groups,
546 function ( $field_group ) {
547 return ! empty( $field_group['show_in_rest'] );
548 }
549 );
550
551 $fields['rest_field_groups'] = array(
552 'label' => __( 'Field Groups Enabled for REST API', 'acf' ),
553 'value' => number_format_i18n( count( $rest_field_groups ) ),
554 );
555
556 $graphql_field_groups = array_filter(
557 $field_groups,
558 function ( $field_group ) {
559 return ! empty( $field_group['show_in_graphql'] );
560 }
561 );
562
563 if ( is_plugin_active( 'wpgraphql-acf/wpgraphql-acf.php' ) ) {
564 $fields['graphql_field_groups'] = array(
565 'label' => __( 'Field Groups Enabled for GraphQL', 'acf' ),
566 'value' => number_format_i18n( count( $graphql_field_groups ) ),
567 );
568 }
569
570 $all_fields = array();
571 $object_types = array();
572 $all_rules = array();
573 $field_groups_per_post_type = array();
574
575 foreach ( $field_groups as $field_group ) {
576 $all_fields = array_merge( $all_fields, acf_get_fields( $field_group ) );
577
578 foreach ( $field_group['location'] as $rules ) {
579 foreach ( $rules as $rule ) {
580 if ( empty( $rule['param'] ) ) {
581 continue;
582 }
583
584 $operator = ! empty( $rule['operator'] ) ? $rule['operator'] : '';
585 $value = ! empty( $rule['value'] ) ? $rule['value'] : '';
586 $all_rules[] = $rule['param'] . $operator . $value;
587
588 if ( 'post_type' === $rule['param']
589 && '==' === ( $rule['operator'] ?? '' )
590 && ! empty( $rule['value'] ) ) {
591 $field_groups_per_post_type[ $rule['value'] ][ $field_group['key'] ] = true;
592 }
593
594 if ( ! $is_pro ) {
595 continue;
596 }
597
598 $location = acf_get_location_type( $rule['param'] );
599 if ( ! $location ) {
600 continue;
601 }
602
603 $location_type = $location->get_object_type( $rule );
604 $object_types[ $field_group['key'] ][] = $location_type;
605 }
606 }
607 }
608
609 $fields['all_location_rules'] = array(
610 'label' => __( 'All Location Rules', 'acf' ),
611 'value' => array_values( array_unique( $all_rules ) ),
612 );
613
614 ksort( $field_groups_per_post_type );
615 $by_post_type = array();
616 foreach ( $field_groups_per_post_type as $post_type => $group_keys ) {
617 $by_post_type[] = array(
618 'post_type' => $post_type,
619 'field_group_count' => count( $group_keys ),
620 );
621 }
622 $fields['field_groups_by_post_type'] = array(
623 'label' => __( 'Field Groups by Post Type', 'acf' ),
624 'value' => $by_post_type,
625 );
626
627 if ( $is_pro ) {
628 $field_groups_with_single_block_rule = 0;
629 $field_groups_with_multiple_block_rules = 0;
630 $field_groups_with_blocks_and_other_rules = 0;
631
632 foreach ( $object_types as $types ) {
633 $num_types = array_count_values( $types );
634
635 // Bail if no block location rules.
636 if ( empty( $num_types['block'] ) ) {
637 continue;
638 }
639
640 if ( count( $num_types ) === 1 ) {
641 // Field group is only assigned to blocks.
642 if ( $num_types['block'] === 1 ) {
643 ++$field_groups_with_single_block_rule;
644 } else {
645 ++$field_groups_with_multiple_block_rules;
646 }
647 } else {
648 // Field group is assigned to blocks & other stuff.
649 ++$field_groups_with_blocks_and_other_rules;
650 }
651 }
652
653 $fields['field_groups_with_single_block_rule'] = array(
654 'label' => __( 'Number of Field Groups with a Single Block Location', 'acf' ),
655 'value' => number_format_i18n( $field_groups_with_single_block_rule ),
656 );
657
658 $fields['field_groups_with_multiple_block_rules'] = array(
659 'label' => __( 'Number of Field Groups with Multiple Block Locations', 'acf' ),
660 'value' => number_format_i18n( $field_groups_with_multiple_block_rules ),
661 );
662
663 $fields['field_groups_with_blocks_and_other_rules'] = array(
664 'label' => __( 'Number of Field Groups with Blocks and Other Locations', 'acf' ),
665 'value' => number_format_i18n( $field_groups_with_blocks_and_other_rules ),
666 );
667 }
668
669 $fields_by_type = array();
670 $third_party_fields_by_type = array();
671 $core_field_types = array_keys( acf_get_field_types() );
672
673 foreach ( $all_fields as $field ) {
674 if ( in_array( $field['type'], $core_field_types, true ) ) {
675 if ( ! isset( $fields_by_type[ $field['type'] ] ) ) {
676 $fields_by_type[ $field['type'] ] = 0;
677 }
678
679 ++$fields_by_type[ $field['type'] ];
680
681 continue;
682 }
683
684 if ( ! isset( $third_party_fields_by_type[ $field['type'] ] ) ) {
685 $third_party_fields_by_type[ $field['type'] ] = 0;
686 }
687
688 ++$third_party_fields_by_type[ $field['type'] ];
689 }
690
691 $fields['number_of_fields_by_type'] = array(
692 'label' => __( 'Number of Fields by Field Type', 'acf' ),
693 'value' => $fields_by_type,
694 );
695
696 $fields['number_of_third_party_fields_by_type'] = array(
697 'label' => __( 'Number of Third Party Fields by Field Type', 'acf' ),
698 'value' => $third_party_fields_by_type,
699 );
700
701 $enable_post_types = acf_get_setting( 'enable_post_types' );
702
703 $fields['post_types_enabled'] = array(
704 'label' => __( 'Post Types and Taxonomies Enabled', 'acf' ),
705 'value' => $enable_post_types ? $yes : $no,
706 'debug' => $enable_post_types,
707 );
708
709 $ui_post_types = array_filter(
710 $post_types,
711 function ( $post_type ) {
712 return ! empty( $post_type['ID'] );
713 }
714 );
715
716 $fields['ui_post_types'] = array(
717 'label' => __( 'Registered Post Types (UI)', 'acf' ),
718 'value' => number_format_i18n( count( $ui_post_types ) ),
719 );
720
721 $json_post_types = array_filter(
722 $post_types,
723 function ( $post_type ) {
724 return ! empty( $post_type['local'] ) && 'json' === $post_type['local'] && empty( $post_type['ID'] );
725 }
726 );
727
728 $fields['json_post_types'] = array(
729 'label' => __( 'Registered Post Types (JSON)', 'acf' ),
730 'value' => number_format_i18n( count( $json_post_types ) ),
731 );
732
733 $ui_taxonomies = array_filter(
734 $taxonomies,
735 function ( $taxonomy ) {
736 return ! empty( $taxonomy['ID'] );
737 }
738 );
739
740 $fields['ui_taxonomies'] = array(
741 'label' => __( 'Registered Taxonomies (UI)', 'acf' ),
742 'value' => number_format_i18n( count( $ui_taxonomies ) ),
743 );
744
745 $json_taxonomies = array_filter(
746 $taxonomies,
747 function ( $taxonomy ) {
748 return ! empty( $taxonomy['local'] ) && 'json' === $taxonomy['local'] && empty( $taxonomy['ID'] );
749 }
750 );
751
752 $fields['json_taxonomies'] = array(
753 'label' => __( 'Registered Taxonomies (JSON)', 'acf' ),
754 'value' => number_format_i18n( count( $json_taxonomies ) ),
755 );
756
757 if ( $is_pro ) {
758 $enable_options_pages_ui = acf_get_setting( 'enable_options_pages_ui' );
759
760 $fields['ui_options_pages_enabled'] = array(
761 'label' => __( 'Options Pages UI Enabled', 'acf' ),
762 'value' => $enable_options_pages_ui ? $yes : $no,
763 'debug' => $enable_options_pages_ui,
764 );
765
766 $options_pages = acf_get_options_pages();
767 $ui_options_pages = array();
768
769 if ( empty( $options_pages ) || ! is_array( $options_pages ) ) {
770 $options_pages = array();
771 }
772
773 if ( $enable_options_pages_ui ) {
774 $ui_options_pages = acf_get_ui_options_pages();
775
776 $ui_options_pages_in_ui = array_filter(
777 $ui_options_pages,
778 function ( $ui_options_page ) {
779 return ! empty( $ui_options_page['ID'] );
780 }
781 );
782
783 $json_options_pages = array_filter(
784 $ui_options_pages,
785 function ( $ui_options_page ) {
786 return ! empty( $ui_options_page['local'] ) && empty( $ui_options_page['ID'] );
787 }
788 );
789
790 $fields['ui_options_pages'] = array(
791 'label' => __( 'Registered Options Pages (UI)', 'acf' ),
792 'value' => number_format_i18n( count( $ui_options_pages_in_ui ) ),
793 );
794
795 $fields['json_options_pages'] = array(
796 'label' => __( 'Registered Options Pages (JSON)', 'acf' ),
797 'value' => number_format_i18n( count( $json_options_pages ) ),
798 );
799 }
800
801 $ui_options_page_slugs = array_column( $ui_options_pages, 'menu_slug' );
802 $php_options_pages = array_filter(
803 $options_pages,
804 function ( $options_page ) use ( $ui_options_page_slugs ) {
805 return ! in_array( $options_page['menu_slug'], $ui_options_page_slugs, true );
806 }
807 );
808
809 $fields['php_options_pages'] = array(
810 'label' => __( 'Registered Options Pages (PHP)', 'acf' ),
811 'value' => number_format_i18n( count( $php_options_pages ) ),
812 );
813 }
814
815 $rest_api_format = acf_get_setting( 'rest_api_format' );
816
817 $fields['rest_api_format'] = array(
818 'label' => __( 'REST API Format', 'acf' ),
819 'value' => 'standard' === $rest_api_format ? __( 'Standard', 'acf' ) : __( 'Light', 'acf' ),
820 'debug' => $rest_api_format,
821 );
822
823 $schema_objects = array(
824 'blocks' => 0,
825 'post_types' => 0,
826 );
827
828 if ( $is_pro ) {
829 $fields['registered_acf_blocks'] = array(
830 'label' => __( 'Registered ACF Blocks', 'acf' ),
831 'value' => number_format_i18n( acf_pro_get_registered_block_count() ),
832 );
833
834 $blocks = acf_get_block_types();
835 $block_api_versions = array();
836 $acf_block_versions = array();
837 $blocks_using_post_meta = 0;
838 $blocks_using_auto_inline_editing = 0;
839
840 foreach ( $blocks as $block ) {
841 if ( ! isset( $block_api_versions[ 'v' . $block['api_version'] ] ) ) {
842 $block_api_versions[ 'v' . $block['api_version'] ] = 0;
843 }
844
845 if ( ! isset( $acf_block_versions[ 'v' . $block['acf_block_version'] ] ) ) {
846 $acf_block_versions[ 'v' . $block['acf_block_version'] ] = 0;
847 }
848
849 if ( ! empty( $block['use_post_meta'] ) ) {
850 ++$blocks_using_post_meta;
851 }
852
853 if ( ! empty( $block['auto_inline_editing'] ) ) {
854 ++$blocks_using_auto_inline_editing;
855 }
856
857 if ( ! empty( $block['auto_jsonld'] ) ) {
858 ++$schema_objects['blocks'];
859 }
860
861 ++$block_api_versions[ 'v' . $block['api_version'] ];
862 ++$acf_block_versions[ 'v' . $block['acf_block_version'] ];
863 }
864
865 $fields['blocks_per_api_version'] = array(
866 'label' => __( 'Blocks Per API Version', 'acf' ),
867 'value' => $block_api_versions,
868 );
869
870 $fields['blocks_per_acf_block_version'] = array(
871 'label' => __( 'Blocks Per ACF Block Version', 'acf' ),
872 'value' => $acf_block_versions,
873 );
874
875 $fields['blocks_using_post_meta'] = array(
876 'label' => __( 'Blocks Using Post Meta', 'acf' ),
877 'value' => number_format_i18n( $blocks_using_post_meta ),
878 );
879
880 $fields['blocks_using_auto_inline_editing'] = array(
881 'label' => __( 'Blocks Using Auto Inline Editing', 'acf' ),
882 'value' => number_format_i18n( $blocks_using_auto_inline_editing ),
883 );
884
885 $preload_blocks = acf_get_setting( 'preload_blocks' );
886
887 $fields['preload_blocks'] = array(
888 'label' => __( 'Block Preloading Enabled', 'acf' ),
889 'value' => ! empty( $preload_blocks ) ? $yes : $no,
890 'debug' => $preload_blocks,
891 );
892 }
893
894 $show_admin = acf_get_setting( 'show_admin' );
895
896 $fields['admin_ui_enabled'] = array(
897 'label' => __( 'Admin UI Enabled', 'acf' ),
898 'value' => $show_admin ? $yes : $no,
899 'debug' => $show_admin,
900 );
901
902 $field_type_modal_enabled = apply_filters( 'acf/field_group/enable_field_browser', true );
903
904 $fields['field_type-modal_enabled'] = array(
905 'label' => __( 'Field Type Modal Enabled', 'acf' ),
906 'value' => ! empty( $field_type_modal_enabled ) ? $yes : $no,
907 'debug' => $field_type_modal_enabled,
908 );
909
910 $field_settings_tabs_enabled = apply_filters( 'acf/field_group/disable_field_settings_tabs', false );
911
912 $fields['field_settings_tabs_enabled'] = array(
913 'label' => __( 'Field Settings Tabs Enabled', 'acf' ),
914 'value' => empty( $field_settings_tabs_enabled ) ? $yes : $no,
915 'debug' => $field_settings_tabs_enabled,
916 );
917
918 $shortcode_enabled = acf_get_setting( 'enable_shortcode' );
919
920 $fields['shortcode_enabled'] = array(
921 'label' => __( 'Shortcode Enabled', 'acf' ),
922 'value' => ! empty( $shortcode_enabled ) ? $yes : $no,
923 'debug' => $shortcode_enabled,
924 );
925
926 $fields['registered_acf_forms'] = array(
927 'label' => __( 'Registered ACF Forms', 'acf' ),
928 'value' => number_format_i18n( count( acf_get_forms() ) ),
929 );
930
931 $local_json = acf_get_instance( 'ACF_Local_JSON' );
932 $save_paths = $local_json->get_save_paths();
933 $load_paths = $local_json->get_load_paths();
934
935 $fields['json_save_paths'] = array(
936 'label' => __( 'JSON Save Paths', 'acf' ),
937 'value' => number_format_i18n( count( $save_paths ) ),
938 'debug' => count( $save_paths ),
939 );
940
941 $fields['json_load_paths'] = array(
942 'label' => __( 'JSON Load Paths', 'acf' ),
943 'value' => number_format_i18n( count( $load_paths ) ),
944 'debug' => count( $load_paths ),
945 );
946
947 $ai_enabled = acf_get_setting( 'enable_acf_ai' );
948
949 $fields['ai_enabled'] = array(
950 'label' => __( 'AI Support', 'acf' ),
951 'value' => ! empty( $ai_enabled ) ? $enabled : $disabled,
952 'debug' => $ai_enabled,
953 );
954
955 // Add AI usage metrics if enabled and the Abilities API is available (WP 6.9+).
956 $abilities_api_available = function_exists( 'wp_register_ability' );
957
958 if ( $ai_enabled && ! $abilities_api_available ) {
959 $fields['ai_abilities_api'] = array(
960 'label' => __( 'Abilities API', 'acf' ),
961 'value' => __( 'Not available (requires WordPress 6.9+)', 'acf' ),
962 'debug' => 'unavailable',
963 );
964 }
965
966 if ( $ai_enabled && $abilities_api_available ) {
967 $ai_ready_counts = $this->ai_usage->get_ai_ready_counts( $field_groups, $post_types, $taxonomies );
968 $ai_usage = $this->ai_usage->get_usage_metrics();
969
970 $fields['ai_ready_objects'] = array(
971 'label' => __( 'AI-Ready Objects', 'acf' ),
972 'value' => sprintf(
973 /* translators: 1: field group count, 2: post type count, 3: taxonomy count */
974 __( '%1$d Field Groups, %2$d Custom Post Types, %3$d Taxonomies', 'acf' ),
975 $ai_ready_counts['field_groups'],
976 $ai_ready_counts['post_types'],
977 $ai_ready_counts['taxonomies'],
978 ),
979 'debug' => $ai_ready_counts,
980 );
981
982 $executions_text = sprintf(
983 /* translators: %s - number of successful tasks performed */
984 _n( '%s (Successful task performed)', '%s (Successful tasks performed)', $ai_usage['total_executions'], 'acf' ),
985 number_format_i18n( $ai_usage['total_executions'] )
986 );
987
988 $fields['ai_executions'] = array(
989 'label' => __( 'AI Actions Executed', 'acf' ),
990 'value' => $executions_text,
991 'debug' => $ai_usage['total_executions'],
992 );
993
994 if ( $ai_usage['error_count'] > 0 ) {
995 $fields['ai_errors'] = array(
996 'label' => __( 'AI Execution Errors', 'acf' ),
997 'value' => number_format_i18n( $ai_usage['error_count'] ),
998 'debug' => $ai_usage['error_count'],
999 );
1000 }
1001 }
1002
1003 $schema_support = acf_get_setting( 'enable_schema' );
1004 $fields['schema_support'] = array(
1005 'label' => __( 'Schema Support', 'acf' ),
1006 'value' => ! empty( $schema_support ) ? $enabled : $disabled,
1007 'debug' => $schema_support,
1008 );
1009
1010 foreach ( $post_types as $post_type ) {
1011 if ( ! empty( $post_type['active'] ) && ! empty( $post_type['auto_jsonld'] ) ) {
1012 ++$schema_objects['post_types'];
1013 }
1014 }
1015
1016 $fields['schema_ready_objects'] = array(
1017 'label' => __( 'Objects with Schema Support', 'acf' ),
1018 'value' => sprintf(
1019 /* translators: 1: number of post types, 2: number of blocks using ACF schema */
1020 __( '%1$d Post Types, %2$d Blocks', 'acf' ),
1021 $schema_objects['post_types'],
1022 $schema_objects['blocks'],
1023 ),
1024 'debug' => $schema_objects,
1025 );
1026
1027 if ( $is_pro ) {
1028 $datastore_enabled = function_exists( 'acf_is_using_datastore' ) && acf_is_using_datastore();
1029 $fields['datastore_enabled'] = array(
1030 'label' => __( 'Datastore Enabled', 'acf' ),
1031 'value' => $datastore_enabled ? $enabled : $disabled,
1032 'debug' => $datastore_enabled,
1033 );
1034 }
1035
1036 return $fields;
1037 }
1038 }
1039