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 / AI / GEO / FieldSettings.php
advanced-custom-fields / src / AI / GEO Last commit date
Outputs 5 months ago data 5 months ago FieldSettings.php 5 months ago GEO.php 5 months ago Schema.php 5 months ago SchemaData.php 5 months ago
FieldSettings.php
365 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\AI\GEO;
13
14 // Exit if accessed directly.
15 use WP_Error;
16
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * ACF GEO Field Settings
21 *
22 * Adds JSON-LD field role settings to ACF fields.
23 */
24 class FieldSettings {
25
26 /**
27 * Cache for schema properties by field type
28 *
29 * @var array
30 */
31 private static $properties_cache = array();
32
33 /**
34 * Constructs the FieldSettings class.
35 *
36 * @since 6.8.0
37 *
38 * @return void
39 */
40 public function __construct() {
41 $this->init();
42 }
43
44 /**
45 * Initialize the field settings
46 *
47 * @since 6.8.0
48 *
49 * @return void
50 */
51 public function init() {
52 // Add the Schema.org Property setting to field types that support it.
53 add_action( 'acf/render_field_general_settings', array( $this, 'render_field_schema_settings' ) );
54
55 // AJAX output format handler (needs to be added early for AJAX requests).
56 add_action( 'wp_ajax_acf/schema/get_output_formats', array( $this, 'ajax_get_output_formats' ) );
57 }
58
59 /**
60 * AJAX handler to get output format choices for a field type + property combination.
61 *
62 * @since 6.8.0
63 *
64 * @return void
65 */
66 public function ajax_get_output_formats() {
67 // Verify request.
68 if ( ! acf_verify_ajax() ) {
69 wp_send_json_error(
70 new WP_Error(
71 'acf_invalid_nonce',
72 __( 'Invalid nonce.', 'acf' )
73 )
74 );
75 }
76
77 // Verify user can admin.
78 if ( ! acf_current_user_can_admin() ) {
79 wp_send_json_error(
80 new WP_Error(
81 'acf_invalid_permissions',
82 __( 'Sorry, you do not have permission to do that.', 'acf' )
83 )
84 );
85 }
86
87 $field_type = acf_request_arg( 'field_type', '' );
88 $qualified_property = acf_request_arg( 'property', '' );
89 $property = Schema::get_property_name( $qualified_property );
90
91 if ( empty( $field_type ) || empty( $property ) ) {
92 wp_send_json_error(
93 new WP_Error(
94 'acf_invalid_param',
95 __( 'Missing required parameters', 'acf' )
96 )
97 );
98 }
99
100 $choices = array();
101 $valid_formats = Schema::get_valid_output_formats( $field_type, $property );
102
103 foreach ( $valid_formats as $format ) {
104 $choices[] = array(
105 'id' => $format,
106 'text' => $format,
107 );
108 }
109
110 $default = Schema::get_default_output_format( $field_type, $property );
111
112 wp_send_json_success(
113 array(
114 'choices' => $choices,
115 'default' => $default,
116 )
117 );
118 }
119
120 /**
121 * Render the field-level schema settings.
122 *
123 * @since 6.8.0
124 *
125 * @param array $field The field being edited.
126 * @return void
127 */
128 public function render_field_schema_settings( $field ) {
129 $field_type = $field['type'] ?? '';
130
131 // Check if field type supports JSON-LD output.
132 $supported_ranges = Schema::get_field_type_ranges( $field_type );
133 if ( empty( $supported_ranges ) ) {
134 // Field type doesn't support JSON-LD output (e.g., tab, accordion).
135 return;
136 }
137
138 // Get available Schema.org properties filtered by field type compatibility.
139 $parent_id = (int) ( $field['parent'] ?? 0 );
140
141 acf_render_field_setting(
142 $field,
143 array(
144 'label' => __( 'Schema.org Property', 'acf' ),
145 'instructions' => __( 'Map this field to a Schema.org property instead of using additionalProperty.', 'acf' ),
146 'type' => 'select',
147 'name' => 'schema_property',
148 'class' => 'acf-schema-property',
149 'wrapper' => array(
150 'class' => 'acf-field-meta-box',
151 ),
152 'choices' => $this->get_schema_properties( $field_type, $parent_id ),
153 'allow_null' => 1,
154 'ui' => 1,
155 'experimental' => 1,
156 )
157 );
158
159 $output_choices = $this->get_output_format_choices( $field );
160
161 // Get the default format for this field type + property.
162 $qualified_property = $field['schema_property'] ?? '';
163 $property_name = Schema::get_property_name( $qualified_property );
164 $default_format = Schema::get_default_output_format( $field_type, $property_name );
165
166 acf_render_field_setting(
167 $field,
168 array(
169 'label' => __( 'Schema.org Output Format', 'acf' ),
170 'type' => 'select',
171 'name' => 'schema_output_format',
172 'class' => 'acf-schema-output-format',
173 'wrapper' => array(
174 'class' => 'acf-field-meta-box',
175 ),
176 'choices' => $output_choices,
177 'default_value' => $default_format,
178 'ui' => 1,
179 'experimental' => 1,
180 'conditions' => array(
181 'field' => 'schema_property',
182 'operator' => '!=',
183 'value' => '',
184 ),
185 )
186 );
187 }
188
189 /**
190 * Get available Schema.org properties for a field type
191 *
192 * Returns a hierarchical array of Schema.org properties organized by type,
193 * filtered to only include properties compatible with the field type.
194 *
195 * Uses pre-computed compatibility data for fast lookups.
196 *
197 * @since 6.8.0
198 *
199 * @param string $field_type The ACF field type name.
200 * @param integer $context_id Optional field group ID for context-aware priority ordering.
201 * @return array Array of properties grouped by Schema.org type.
202 */
203 public function get_schema_properties( string $field_type = '', int $context_id = 0 ): array {
204 // Build cache key including context.
205 $cache_key = $field_type . '_' . $context_id;
206
207 // Return cached result if available.
208 if ( isset( self::$properties_cache[ $cache_key ] ) ) {
209 return self::$properties_cache[ $cache_key ];
210 }
211
212 $roles = array();
213
214 // Get compatible properties using pre-computed data.
215 $compatible_set = $this->get_compatible_properties_set( $field_type );
216
217 if ( empty( $compatible_set ) ) {
218 self::$properties_cache[ $cache_key ] = $roles;
219 return $roles;
220 }
221
222 // Get all properties grouped by type from schema.org vocabulary.
223 $properties_by_type = Schema::get_properties_by_type();
224
225 // Get priority types with context-aware ordering.
226 $priority_types = Schema::get_priority_types( $context_id );
227
228 // Add priority types first.
229 foreach ( $priority_types as $type ) {
230 if ( isset( $properties_by_type[ $type ] ) ) {
231 $type_compatible = array();
232 foreach ( $properties_by_type[ $type ] as $property ) {
233 if ( isset( $compatible_set[ $property ] ) ) {
234 $type_compatible[ $type . '.' . $property ] = $property;
235 }
236 }
237
238 if ( ! empty( $type_compatible ) ) {
239 $type_label = sprintf( '%s Properties', $type );
240 $roles[ $type_label ] = $type_compatible;
241 }
242 }
243 }
244
245 // Add remaining types alphabetically.
246 foreach ( $properties_by_type as $type => $properties ) {
247 // Skip priority types (already processed).
248 if ( in_array( $type, $priority_types, true ) ) {
249 continue;
250 }
251
252 // Skip types with no properties.
253 if ( empty( $properties ) ) {
254 continue;
255 }
256
257 $type_compatible = array();
258 foreach ( $properties as $property ) {
259 if ( isset( $compatible_set[ $property ] ) ) {
260 $type_compatible[ $type . '.' . $property ] = $property;
261 }
262 }
263
264 if ( ! empty( $type_compatible ) ) {
265 $type_label = sprintf( '%s Properties', $type );
266 $roles[ $type_label ] = $type_compatible;
267 }
268 }
269
270 /**
271 * Filter the available Schema.org properties.
272 *
273 * Allows developers to add custom Schema.org properties or modify existing ones.
274 *
275 * @param array $properties The Schema.org role mappings grouped by type.
276 * @param string $field_type The ACF field type being configured.
277 */
278 $roles = apply_filters( 'acf/schema/schema_properties', $roles, $field_type );
279
280 // Cache the result.
281 self::$properties_cache[ $cache_key ] = $roles;
282
283 return $roles;
284 }
285
286 /**
287 * Get compatible properties as a set (for fast lookup)
288 *
289 * Uses pre-computed data from SchemaData::get_compatible_properties().
290 *
291 * @since 6.8.0
292 *
293 * @param string $field_type The ACF field type name.
294 * @return array Properties as keys for O(1) lookup.
295 */
296 private function get_compatible_properties_set( $field_type ) {
297 // Get field type's output types.
298 $field_ranges = Schema::get_field_type_ranges( $field_type );
299
300 if ( empty( $field_ranges ) ) {
301 return array();
302 }
303
304 // Get pre-computed compatible properties mapping.
305 $compatible_by_type = SchemaData::get_compatible_properties();
306
307 // Merge compatible properties for all output types.
308 $compatible = array();
309 foreach ( $field_ranges as $output_type ) {
310 if ( isset( $compatible_by_type[ $output_type ] ) ) {
311 foreach ( $compatible_by_type[ $output_type ] as $property ) {
312 $compatible[ $property ] = true;
313 }
314 }
315 }
316
317 return $compatible;
318 }
319
320 /**
321 * Get output format choices for a field
322 *
323 * Returns the valid output formats for the field's type and selected property.
324 * For example, an Image field mapped to the 'image' property can output
325 * either 'URL' or 'ImageObject'.
326 *
327 * @since 6.8.0
328 *
329 * @param array $field The field being edited.
330 * @return array Array of format => label pairs.
331 */
332 private function get_output_format_choices( $field ) {
333 $field_type = $field['type'] ?? '';
334 $qualified_property = $field['schema_property'] ?? '';
335
336 // If no property selected yet, return empty choices.
337 if ( empty( $qualified_property ) ) {
338 return array();
339 }
340
341 // Extract just the property name from qualified property (e.g., "Recipe.recipeYield" -> "recipeYield").
342 $property = Schema::get_property_name( $qualified_property );
343 $valid_formats = Schema::get_valid_output_formats( $field_type, $property );
344
345 if ( empty( $valid_formats ) ) {
346 return array();
347 }
348
349 // Build choices array with format as both key and label.
350 $choices = array();
351 foreach ( $valid_formats as $format ) {
352 $choices[ $format ] = $format;
353 }
354
355 /**
356 * Filter the available output format choices.
357 *
358 * @param array $choices The output format choices.
359 * @param string $field_type The ACF field type.
360 * @param string $property The selected Schema.org property.
361 */
362 return apply_filters( 'acf/schema/output_format_choices', $choices, $field_type, $property );
363 }
364 }
365