Blocks.php
288 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\AI\GEO\Outputs; |
| 11 | |
| 12 | use SCF\AI\GEO\GEO; |
| 13 | |
| 14 | // Exit if accessed directly. |
| 15 | defined( 'ABSPATH' ) || exit; |
| 16 | |
| 17 | /** |
| 18 | * SCF GEO Blocks Output |
| 19 | * |
| 20 | * Extends ACF Blocks to add JSON-LD structured data output for block fields. |
| 21 | * |
| 22 | * To enable JSON-LD output for a block, add "autoJsonLd": true to the ACF namespace |
| 23 | * in your block.json file, or use the acf/ai/block_jsonld_enabled filter. |
| 24 | * |
| 25 | * See README.md for complete usage examples and documentation. |
| 26 | */ |
| 27 | class Blocks { |
| 28 | |
| 29 | /** |
| 30 | * Constructor |
| 31 | */ |
| 32 | public function __construct() { |
| 33 | $this->init(); |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Initialize the GEO Blocks extension. |
| 38 | * |
| 39 | * @since 6.8.0 |
| 40 | * |
| 41 | * @return void |
| 42 | */ |
| 43 | public function init() { |
| 44 | // Add support for autoJsonLd property from block.json ACF namespace. |
| 45 | add_filter( 'block_type_metadata_settings', array( $this, 'add_block_json_auto_jsonld_support' ), 10, 2 ); |
| 46 | |
| 47 | // Add support for autoJsonLd property from programmatic registration. |
| 48 | add_filter( 'acf/register_block_type_args', array( $this, 'add_programmatic_auto_jsonld_support' ) ); |
| 49 | |
| 50 | // Add front-end JSON-LD output for blocks. |
| 51 | add_action( 'acf/blocks/pre_block_template_render', array( $this, 'output_block_jsonld_data' ), 10, 6 ); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Add support for autoJsonLd property from block.json ACF namespace |
| 56 | * |
| 57 | * Maps the 'autoJsonLd' property from block.json's acf namespace to 'auto_jsonld' setting. |
| 58 | * Also maps 'schemaType' to 'schema_type' for custom Schema.org @type values. |
| 59 | * This runs after ACF's own block.json handler. |
| 60 | * |
| 61 | * @since 6.8.0 |
| 62 | * |
| 63 | * @param array $settings The compiled block settings. |
| 64 | * @param array $metadata The raw json metadata. |
| 65 | * @return array Modified block settings. |
| 66 | */ |
| 67 | public function add_block_json_auto_jsonld_support( $settings, $metadata ) { |
| 68 | // Only process ACF blocks. |
| 69 | if ( ! isset( $metadata['acf'] ) || ! is_array( $metadata['acf'] ) ) { |
| 70 | return $settings; |
| 71 | } |
| 72 | |
| 73 | // Map autoJsonLd from ACF namespace to auto_jsonld. |
| 74 | if ( isset( $metadata['acf']['autoJsonLd'] ) ) { |
| 75 | $settings['auto_jsonld'] = $metadata['acf']['autoJsonLd']; |
| 76 | } |
| 77 | |
| 78 | // Map schemaType from ACF namespace to schema_type. |
| 79 | if ( isset( $metadata['acf']['schemaType'] ) ) { |
| 80 | $settings['schema_type'] = $metadata['acf']['schemaType']; |
| 81 | } |
| 82 | |
| 83 | return $settings; |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Add support for autoJsonLd property from programmatic registration |
| 88 | * |
| 89 | * Maps the 'autoJsonLd' property from the acf namespace to 'auto_jsonld' setting |
| 90 | * and 'schemaType' to 'schema_type' for blocks registered via acf_register_block_type(). |
| 91 | * |
| 92 | * @since 6.8.0 |
| 93 | * |
| 94 | * @param array $block The block settings array. |
| 95 | * @return array Modified block settings. |
| 96 | */ |
| 97 | public function add_programmatic_auto_jsonld_support( $block ) { |
| 98 | // Check if this is a programmatic registration with ACF namespace. |
| 99 | if ( isset( $block['acf'] ) && is_array( $block['acf'] ) ) { |
| 100 | if ( isset( $block['acf']['autoJsonLd'] ) ) { |
| 101 | $block['auto_jsonld'] = $block['acf']['autoJsonLd']; |
| 102 | } |
| 103 | |
| 104 | if ( isset( $block['acf']['schemaType'] ) ) { |
| 105 | $block['schema_type'] = $block['acf']['schemaType']; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | return $block; |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Output JSON-LD structured data for ACF block fields. |
| 114 | * |
| 115 | * @since 6.8.0 |
| 116 | * |
| 117 | * @param array $block The block props. |
| 118 | * @param string $content The block content. |
| 119 | * @param boolean $is_preview Whether or not the block is being rendered for editing preview. |
| 120 | * @param integer $post_id The current post being edited or viewed. |
| 121 | * @param WP_Block $wp_block The block instance. |
| 122 | * @param array $context The block context array. |
| 123 | */ |
| 124 | public function output_block_jsonld_data( $block, $content, $is_preview, $post_id, $wp_block, $context ) { |
| 125 | /** |
| 126 | * Filters whether to output debug comments in HTML |
| 127 | * |
| 128 | * @since 6.8.0 |
| 129 | * |
| 130 | * @param bool $debug Whether to output debug comments. Default false. |
| 131 | */ |
| 132 | $debug = apply_filters( 'acf/schema/debug', false ); |
| 133 | |
| 134 | // Don't output JSON-LD in the block editor preview. |
| 135 | if ( $is_preview ) { |
| 136 | if ( $debug ) { |
| 137 | echo "<!-- SCF AI Block JSON-LD: Skipped (block editor preview) -->\n"; |
| 138 | } |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | // Don't output if we don't have a block name. |
| 143 | if ( empty( $block['name'] ) ) { |
| 144 | if ( $debug ) { |
| 145 | echo "<!-- SCF AI Block JSON-LD: Skipped (no block name) -->\n"; |
| 146 | } |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | if ( $debug ) { |
| 151 | echo '<!-- SCF AI Block JSON-LD: Checking block: ' . esc_html( $block['name'] ) . " -->\n"; |
| 152 | } |
| 153 | |
| 154 | // Get the block type. |
| 155 | $block_type = acf_get_block_type( $block['name'] ); |
| 156 | if ( ! $block_type ) { |
| 157 | if ( $debug ) { |
| 158 | echo '<!-- SCF AI Block JSON-LD: Block type not found for ' . esc_html( $block['name'] ) . " -->\n"; |
| 159 | } |
| 160 | return; |
| 161 | } |
| 162 | |
| 163 | // Check if this block has auto_jsonld enabled. |
| 164 | $auto_jsonld = isset( $block_type['auto_jsonld'] ) ? $block_type['auto_jsonld'] : false; |
| 165 | |
| 166 | /** |
| 167 | * Filters whether JSON-LD output is enabled for this specific block. |
| 168 | * |
| 169 | * @since 6.8.0 |
| 170 | * |
| 171 | * @param boolean $auto_jsonld Whether JSON-LD is enabled for this block. |
| 172 | * @param array $block The block props. |
| 173 | * @param array $block_type The block type settings. |
| 174 | */ |
| 175 | $auto_jsonld = apply_filters( 'acf/schema/block_jsonld_enabled', $auto_jsonld, $block, $block_type ); |
| 176 | |
| 177 | // Exit if auto JSON-LD is not enabled for this block. |
| 178 | if ( ! $auto_jsonld ) { |
| 179 | if ( $debug ) { |
| 180 | echo '<!-- SCF AI Block JSON-LD: Block \'' . esc_html( $block['name'] ) . "' does not have JSON-LD enabled -->\n"; |
| 181 | } |
| 182 | return; |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * Filters the field objects before retrieval, allowing blocks to provide custom data. |
| 187 | * |
| 188 | * This is useful for blocks that link to other post types or need custom field data handling. |
| 189 | * Return a non-null value to short-circuit the default get_field_objects() call. |
| 190 | * |
| 191 | * @since 6.8.0 |
| 192 | * |
| 193 | * @param array|null $field_objects The field objects array, or null to use default behavior. |
| 194 | * @param array $block The block props. |
| 195 | * @param array $block_type The block type settings. |
| 196 | * @param int $post_id The current post ID. |
| 197 | */ |
| 198 | $field_objects = apply_filters( 'acf/schema/block_field_objects', null, $block, $block_type, $post_id ); |
| 199 | |
| 200 | /** |
| 201 | * Filters the field objects for a specific block name/type. |
| 202 | * |
| 203 | * The dynamic portion of the hook name, `$block['name']`, refers to the block type name. |
| 204 | * For example, 'acf/schema/block_field_objects/block_name=acf/testimonial' for the testimonial block. |
| 205 | * |
| 206 | * @since 6.8.0 |
| 207 | * |
| 208 | * @param array|null $field_objects The field objects array, or null to use default behavior. |
| 209 | * @param array $block The block props. |
| 210 | * @param array $block_type The block type settings. |
| 211 | * @param int $post_id The current post ID. |
| 212 | */ |
| 213 | $field_objects = apply_filters( 'acf/schema/block_field_objects/block_name=' . $block['name'], $field_objects, $block, $block_type, $post_id ); |
| 214 | |
| 215 | // If no custom field objects were provided, get them from the block. |
| 216 | if ( null === $field_objects ) { |
| 217 | // Get all ACF field objects with values for this block. |
| 218 | // Use get_field_objects() to get both field metadata and values in a single call. |
| 219 | $field_objects = get_field_objects( $block['id'], false ); |
| 220 | } |
| 221 | |
| 222 | if ( ! $field_objects || ! is_array( $field_objects ) ) { |
| 223 | if ( $debug ) { |
| 224 | echo '<!-- SCF AI Block JSON-LD: No ACF fields found for block ' . esc_html( $block['name'] ) . " -->\n"; |
| 225 | } |
| 226 | return; |
| 227 | } |
| 228 | |
| 229 | if ( $debug ) { |
| 230 | echo '<!-- SCF AI Block JSON-LD: Found ' . count( $field_objects ) . " ACF fields -->\n"; |
| 231 | } |
| 232 | |
| 233 | // Process ACF fields and extract types from qualified properties. |
| 234 | // This handles schema_property mapping. |
| 235 | $processed_fields = GEO::process_fields( $field_objects ); |
| 236 | |
| 237 | // Get any explicitly set schema type for this block. |
| 238 | $provided_type = ! empty( $block_type['schema_type'] ) ? $block_type['schema_type'] : null; |
| 239 | $field_types = $processed_fields['field_types'] ?? array(); |
| 240 | |
| 241 | // Determine the final @type based on provided type or field types from qualified properties. |
| 242 | // Supports both string (single type) and array (multiple types). |
| 243 | $schema_type = GEO::determine_schema_type( $provided_type, $field_types, 'PropertyValue' ); |
| 244 | |
| 245 | // Remove internal type data from processed fields. |
| 246 | unset( $processed_fields['field_types'] ); |
| 247 | |
| 248 | // Build base JSON-LD structured data. |
| 249 | $jsonld_data = array( |
| 250 | '@context' => 'https://schema.org', |
| 251 | '@type' => $schema_type, // Can be string or array |
| 252 | '@id' => ! empty( $block['id'] ) ? get_permalink( $post_id ) . '#' . $block['id'] : get_permalink( $post_id ), |
| 253 | ); |
| 254 | |
| 255 | // Add block title if available. |
| 256 | if ( ! empty( $block_type['title'] ) ) { |
| 257 | $jsonld_data['name'] = $block_type['title']; |
| 258 | } |
| 259 | |
| 260 | // Add block description if available. |
| 261 | if ( ! empty( $block_type['description'] ) ) { |
| 262 | $jsonld_data['description'] = $block_type['description']; |
| 263 | } |
| 264 | |
| 265 | // Merge processed fields into JSON-LD data. |
| 266 | $jsonld_data = array_merge( $jsonld_data, $processed_fields ); |
| 267 | |
| 268 | /** |
| 269 | * Filters the JSON-LD data before output for a block. |
| 270 | * |
| 271 | * @since 6.8.0 |
| 272 | * |
| 273 | * @param array $jsonld_data The JSON-LD data array. |
| 274 | * @param array $block The block props. |
| 275 | * @param array $block_type The block type settings. |
| 276 | */ |
| 277 | $jsonld_data = apply_filters( 'acf/schema/data', $jsonld_data, $block, $block_type ); |
| 278 | |
| 279 | // Only output if we have data after filtering. |
| 280 | if ( empty( $jsonld_data ) ) { |
| 281 | return; |
| 282 | } |
| 283 | |
| 284 | // Output the JSON-LD using the shared helper. |
| 285 | GEO::render_jsonld_script( $jsonld_data ); |
| 286 | } |
| 287 | } |
| 288 |