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 / Outputs / Posts.php
advanced-custom-fields / src / AI / GEO / Outputs Last commit date
Posts.php 5 months ago
Posts.php
276 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\Outputs;
13
14 use ACF\AI\GEO\GEO;
15
16 // Exit if accessed directly.
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * ACF GEO Posts Output
21 *
22 * Handles JSON-LD structured data output for ACF fields on post types.
23 */
24 class Posts {
25
26 /**
27 * Constructor
28 *
29 * @since 6.8.0
30 */
31 public function __construct() {
32 $this->init();
33 }
34
35 /**
36 * Initialize the GEO Posts extension
37 *
38 * @since 6.8.0
39 */
40 public function init() {
41 // Add front-end JSON-LD output for posts.
42 add_action( 'wp_head', array( $this, 'output_jsonld_data' ) );
43 }
44
45 /**
46 * Output JSON-LD structured data for ACF fields on posts
47 *
48 * @since 6.8.0
49 */
50 public function output_jsonld_data() {
51 /**
52 * Filters whether to output debug comments in HTML
53 *
54 * @param bool $debug Whether to output debug comments. Default false.
55 */
56 $debug = apply_filters( 'acf/schema/debug', false );
57
58 // Only output on singular posts/pages.
59 if ( ! is_singular() ) {
60 if ( $debug ) {
61 echo "<!-- ACF AI JSON-LD: Not a singular post/page -->\n";
62 }
63 return;
64 }
65
66 global $post;
67 if ( ! $post ) {
68 if ( $debug ) {
69 echo "<!-- ACF AI JSON-LD: No post object found -->\n";
70 }
71 return;
72 }
73
74 $post_type = get_post_type( $post );
75 if ( ! $post_type ) {
76 if ( $debug ) {
77 echo "<!-- ACF AI JSON-LD: No post type found -->\n";
78 }
79 return;
80 }
81
82 if ( $debug ) {
83 echo '<!-- ACF AI JSON-LD: Checking post type: ' . esc_html( $post_type ) . " -->\n";
84 }
85
86 // Build list of post types with JSON-LD enabled.
87 $enabled_post_types = array();
88
89 // Get ACF custom post types with JSON-LD enabled.
90 $acf_post_types = acf_get_acf_post_types();
91 foreach ( $acf_post_types as $acf_post_type ) {
92 if ( ! empty( $acf_post_type['auto_jsonld'] ) && isset( $acf_post_type['post_type'] ) ) {
93 $enabled_post_types[] = $acf_post_type['post_type'];
94 }
95 }
96
97 /**
98 * Filters the list of post types that have JSON-LD output enabled.
99 *
100 * @param array $enabled_post_types Array of post type names with JSON-LD enabled.
101 */
102 $enabled_post_types = apply_filters( 'acf/schema/enabled_post_types', $enabled_post_types );
103
104 // Exit if current post type is not in the enabled list.
105 if ( ! in_array( $post_type, $enabled_post_types, true ) ) {
106 if ( $debug ) {
107 echo '<!-- ACF AI JSON-LD: Post type \'' . esc_html( $post_type ) . "' does not have JSON-LD enabled -->\n";
108 }
109 return;
110 }
111
112 /**
113 * Filters the field objects before retrieval, allowing posts to provide custom data.
114 *
115 * This is useful for posts that need custom field data handling.
116 * Return a non-null value to short-circuit the default get_field_objects() call.
117 *
118 * @since 6.8.0
119 *
120 * @param array|null $field_objects The field objects array, or null to use default behavior.
121 * @param WP_Post $post The post object.
122 * @param string $post_type The post type.
123 */
124 $field_objects = apply_filters( 'acf/schema/post_field_objects', null, $post, $post_type );
125
126 /**
127 * Filters the field objects for a specific post type.
128 *
129 * The dynamic portion of the hook name, `$post_type`, refers to the post type name.
130 * For example, 'acf/schema/post_field_objects/post_type=product' for the product post type.
131 *
132 * @since 6.8.0
133 *
134 * @param array|null $field_objects The field objects array, or null to use default behavior.
135 * @param WP_Post $post The post object.
136 */
137 $field_objects = apply_filters( 'acf/schema/post_field_objects/post_type=' . $post_type, $field_objects, $post );
138
139 // If no custom field objects were provided, get them from the post.
140 if ( null === $field_objects ) {
141 // Get all ACF field objects for this post without formatting to get raw ACF storage format.
142 $field_objects = get_field_objects( $post->ID, false );
143 }
144
145 if ( ! $field_objects || ! is_array( $field_objects ) ) {
146 if ( $debug ) {
147 echo '<!-- ACF AI JSON-LD: No ACF fields found for post ID ' . absint( $post->ID ) . " -->\n";
148 }
149 return;
150 }
151
152 if ( $debug ) {
153 echo '<!-- ACF AI JSON-LD: Found ' . count( $field_objects ) . " ACF fields -->\n";
154 }
155
156 // Process ACF fields and extract types from qualified properties.
157 // This handles schema_property mapping.
158 $processed_fields = GEO::process_fields( $field_objects );
159
160 // Get any explicitly set schema type for this post type.
161 $acf_post_type_object = null;
162 foreach ( $acf_post_types as $acf_post_type ) {
163 if ( isset( $acf_post_type['post_type'] ) && $acf_post_type['post_type'] === $post_type ) {
164 $acf_post_type_object = $acf_post_type;
165 break;
166 }
167 }
168
169 $provided_type = $acf_post_type_object['schema_type'] ?? null;
170 $field_types = $processed_fields['field_types'] ?? array();
171
172 // Determine the final @type based on provided type or field types from qualified properties.
173 $schema_type = GEO::determine_schema_type( $provided_type, $field_types, 'Article' );
174
175 // Remove internal type data from processed fields.
176 unset( $processed_fields['field_types'] );
177
178 // Build base JSON-LD structured data.
179 $jsonld_data = array(
180 '@context' => 'https://schema.org',
181 '@type' => $schema_type,
182 '@id' => get_permalink( $post ),
183 'url' => get_permalink( $post ),
184 'name' => get_the_title( $post ),
185 'headline' => get_the_title( $post ),
186 );
187
188 // Add publication date if available.
189 if ( $post->post_date ) {
190 $jsonld_data['datePublished'] = get_the_date( 'c', $post );
191 }
192
193 if ( $post->post_modified ) {
194 $jsonld_data['dateModified'] = get_the_modified_date( 'c', $post );
195 }
196
197 /**
198 * Filter whether to automatically add featured image to JSON-LD output
199 *
200 * @param bool $add_image Whether to add the featured image. Default true.
201 * @param WP_Post $post The post object.
202 * @param string $post_type The post type.
203 */
204 $add_image = apply_filters( 'acf/schema/auto_add_image', true, $post, $post_type );
205
206 // Add featured image if available and enabled.
207 if ( $add_image && has_post_thumbnail( $post ) ) {
208 $thumbnail_id = get_post_thumbnail_id( $post );
209 $thumbnail_url = wp_get_attachment_image_url( $thumbnail_id, 'full' );
210
211 if ( $thumbnail_url ) {
212 // Get image metadata for additional properties.
213 $image_meta = wp_get_attachment_metadata( $thumbnail_id );
214
215 $jsonld_data['image'] = array(
216 '@type' => 'ImageObject',
217 'url' => $thumbnail_url,
218 );
219 if ( isset( $image_meta['width'] ) ) {
220 $jsonld_data['image']['width'] = $image_meta['width'];
221 }
222 if ( isset( $image_meta['height'] ) ) {
223 $jsonld_data['image']['height'] = $image_meta['height'];
224 }
225 }
226 }
227
228 /**
229 * Filter whether to automatically add author to JSON-LD output
230 *
231 * @param bool $add_author Whether to add the author. Default true.
232 * @param WP_Post $post The post object.
233 * @param string $post_type The post type.
234 */
235 $add_author = apply_filters( 'acf/schema/auto_add_author', true, $post, $post_type );
236
237 // Add author if enabled.
238 if ( $add_author && $post->post_author ) {
239 $author = get_userdata( $post->post_author );
240
241 if ( $author ) {
242 $jsonld_data['author'] = array(
243 '@type' => 'Person',
244 'name' => $author->display_name,
245 );
246
247 // Add author URL if available.
248 $author_url = get_author_posts_url( $post->post_author );
249 if ( $author_url ) {
250 $jsonld_data['author']['url'] = $author_url;
251 }
252 }
253 }
254
255 // Merge processed fields into JSON-LD data.
256 $jsonld_data = array_merge( $jsonld_data, $processed_fields );
257
258 /**
259 * Filters the JSON-LD data before output for a post.
260 *
261 * @param array $jsonld_data The JSON-LD data array.
262 * @param WP_Post $post The post object.
263 * @param string $post_type The post type.
264 */
265 $jsonld_data = apply_filters( 'acf/schema/data', $jsonld_data, $post, $post_type );
266
267 // Only output if we have data after filtering.
268 if ( empty( $jsonld_data ) ) {
269 return;
270 }
271
272 // Output the JSON-LD using the shared helper.
273 GEO::render_jsonld_script( $jsonld_data );
274 }
275 }
276