'; $rendered_count = 0; foreach ($fields as $field_name => $field) { if (!in_array($field['type'], array('image', 'file', 'gallery'), true) || empty($field['value'])) { continue; } // Resolve the attachment ID(s) backing this field's value, reusing // the same value-shape branches used to render the markup below // (array-with-url shape -> the 'ID' key; numeric shape -> the value // itself), rather than introducing a third parallel resolution path. $attachment_id = 0; $all_from_canto = false; if ($field['type'] === 'gallery') { // A gallery value is an array of items. Resolve an attachment ID // per item and require EVERY item to carry Canto origin meta // before the gallery renders as a whole; a mixed-origin gallery // is treated as non-Canto for this render. $gallery_items = is_array($field['value']) ? $field['value'] : array(); $all_from_canto = !empty($gallery_items); foreach ($gallery_items as $gallery_item) { $item_attachment_id = 0; if (is_array($gallery_item) && isset($gallery_item['url'])) { $item_attachment_id = isset($gallery_item['ID']) ? (int) $gallery_item['ID'] : 0; } elseif (is_numeric($gallery_item)) { $item_attachment_id = (int) $gallery_item; } if (!canto_attachment_is_from_canto($item_attachment_id)) { $all_from_canto = false; break; } } } else { // image / file: a single attachment resolved from either value shape. $attachment_id = is_array($field['value']) ? (int) ($field['value']['ID'] ?? 0) : (int) $field['value']; } if (!$attachment_id && $field['type'] !== 'gallery') { continue; } // Decide whether this field originated from Canto before rendering. // Gallery fields use the per-item aggregate resolved above (every // item must carry Canto origin meta); image/file fields use the // single resolved attachment ID. if ($field['type'] === 'gallery') { $should_render = $all_from_canto; } else { $should_render = canto_attachment_is_from_canto($attachment_id); } /** * Filter whether a specific ACF field should be rendered by the Canto * duplicate-content block. Defaults to true only for Canto-origin * attachments (those carrying '_canto_asset_id' post meta). * * @param bool $should_render Whether to render this field. Default: Canto-origin check result. * @param array $field The ACF field array from get_field_objects(). * @param int $attachment_id Resolved attachment ID for the field's value (0 if unresolved or a gallery). * @param int $post_id The post ID being rendered. */ $should_render = apply_filters('canto_should_render_acf_field', $should_render, $field, $attachment_id, $post_id); // Origin gate: skip fields that are not Canto-origin (and were not // force-enabled by the filter above). This is what stops unrelated // WordPress-media ACF fields (e.g. a page's teaser image) from being // duplicated into post content. if (!$should_render) { continue; } // data-field-type reflects the field's actual ACF type (image/file/gallery). $field_html = '
'; if (isset($field['label'])) { $field_html .= '

' . esc_html($field['label']) . '

'; } if ($field['type'] === 'gallery') { // Gallery value is an array of items (array-with-url shape, or // less commonly attachment IDs). Render one per item, // reusing the same value-shape handling as the single-image // branch below. $gallery_items = is_array($field['value']) ? $field['value'] : array(); foreach ($gallery_items as $gallery_item) { if (is_array($gallery_item) && isset($gallery_item['url'])) { $field_html .= sprintf( '%s', esc_url($gallery_item['url']), esc_attr(isset($gallery_item['alt']) ? $gallery_item['alt'] : ''), esc_attr(isset($gallery_item['ID']) ? $gallery_item['ID'] : ''), esc_url($gallery_item['url']) ); } elseif (is_numeric($gallery_item)) { $image_url = wp_get_attachment_url($gallery_item); $image_alt = get_post_meta($gallery_item, '_wp_attachment_image_alt', true); if ($image_url) { $field_html .= sprintf( '%s', esc_url($image_url), esc_attr($image_alt), esc_attr($gallery_item), esc_url($image_url) ); } } } } elseif ($field['type'] === 'file') { // File fields commonly hold non-image files (PDF, DOCX, etc.). // Render a link with the filename/title rather than an , // consistent with ACF's own documented file-display pattern. if (is_array($field['value']) && isset($field['value']['url'])) { if (!empty($field['value']['title'])) { $file_title = $field['value']['title']; } elseif (isset($field['value']['filename'])) { $file_title = $field['value']['filename']; } else { $file_title = basename($field['value']['url']); } $field_html .= sprintf( '%s', esc_url($field['value']['url']), esc_attr(isset($field['value']['ID']) ? $field['value']['ID'] : ''), esc_html($file_title) ); } elseif (is_numeric($field['value'])) { $file_url = wp_get_attachment_url($field['value']); if ($file_url) { $file_title = get_the_title($field['value']); if (!$file_title) { $file_title = basename($file_url); } $field_html .= sprintf( '%s', esc_url($file_url), esc_attr($field['value']), esc_html($file_title) ); } } } else { // image: a single attachment, resolved from either value shape. // Handle array format (value already transformed by format_image_value) if (is_array($field['value']) && isset($field['value']['url'])) { $field_html .= sprintf( '%s', esc_url($field['value']['url']), esc_attr($field['value']['alt'] ?? ''), esc_attr($field['value']['ID'] ?? ''), esc_url($field['value']['url']) ); } // Handle numeric ID format elseif (is_numeric($field['value'])) { $image_url = wp_get_attachment_url($field['value']); $image_alt = get_post_meta($field['value'], '_wp_attachment_image_alt', true); if ($image_url) { $field_html .= sprintf( '%s', esc_url($image_url), esc_attr($image_alt), esc_attr($field['value']), esc_url($image_url) ); } } } $field_html .= '
'; $acf_output .= apply_filters('canto_acf_field_html', $field_html, $field, $post_id); $rendered_count++; } $acf_output .= ''; // POST-RENDER GUARD: if the loop produced nothing (every candidate field // was filtered out — most commonly because none of them were Canto-origin), // return content untouched — do not append the empty container div or style block. if ($rendered_count === 0) { return $content; } // Output is now committed for this render, so consume the main-render slot. // Deliberately set here rather than earlier: a pass that bails out above // (no fields, no candidate fields, nothing Canto-origin) leaves the flag // untouched, so a later genuine render in the same request still emits. if ($is_main_render) { $rendered_post_ids[$post_id] = true; } // Enqueue the ACF fields stylesheet via wp_head (once per page load, not per content block), // matching the approach already in use for layout-safety. No `clear: both`, so the theme // retains control of float clearing. if (!wp_style_is('canto-acf-fields', 'enqueued')) { wp_register_style('canto-acf-fields', false, array(), CANTO_FBC_VERSION); wp_enqueue_style('canto-acf-fields'); wp_add_inline_style('canto-acf-fields', ' .acf-fields-container { margin: 2em 0; } .acf-field-image { margin: 1.5em 0; position: relative; } .acf-field-label { margin-bottom: 1em; } .acf-image { max-width: 100%; height: auto; display: block; margin: 1em 0; } '); } // Original content passes through untouched; Canto ACF output appended after return apply_filters('canto_acf_fields_output', $content . $acf_output, $post_id); } // Hook the_content filter — only when ACF is available if (function_exists('get_field_objects')) { add_filter('the_content', 'canto_render_acf_fields', 20); } // Footer script — only when ACF is available; guarded inside against pages with no Canto output if (function_exists('get_field_objects')) { add_action('wp_footer', function() { ?> function($post) { return canto_render_acf_fields('', $post['id']); }, 'schema' => array( 'description' => 'Canto ACF fields HTML output', 'type' => 'string' ) )); }); /** * Returns the main instance of Canto to prevent the need to use globals. * * @return object Canto * @since 1.0.0 */ function Canto() { $instance = Canto::instance(__FILE__, '1.0.0'); if (is_null($instance->settings)) { $instance->settings = Canto_Settings::instance($instance); } return $instance; } Canto(); /* function canto_enqueue_block_editor_assets() { // Scripts. wp_enqueue_script( 'canto-block', FBC_URL . 'block/block.js', array( 'wp-blocks', 'wp-i18n', 'wp-element' ) ); // Styles. wp_enqueue_style( 'canto-block-editor', FBC_URL . 'assets/css/editor.css', array( 'wp-edit-blocks' ) ); } add_action( 'init', 'canto_enqueue_block_editor_assets' ); function canto_enqueue_block_assets() { wp_enqueue_style( 'canto-frontend', FBC_URL . 'assets/css/style.css', array( 'wp-blocks' ) ); } add_action( 'init', 'canto_enqueue_block_assets' ); */