';
$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(
'

',
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(
'

',
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(
'

',
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(
'

',
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' );
*/