$replacements Map of %token% => value.
* @param \WP_Post $post Post being resolved.
*/
$replacements = (array) apply_filters( 'acez_seo_variable_replacements', $replacements, $post );
$resolved = strtr( $template, $replacements );
$resolved = self::resolve_acf( $resolved, $post );
return trim( (string) preg_replace( '/\s+/', ' ', $resolved ) );
}
/**
* %acf:field_name% (M49) — the field name is dynamic, so this runs as a
* callback pass instead of the static strtr map. Without ACF (or for an
* empty/non-scalar value) the token resolves to an EMPTY string — a
* literal "%acf:cena%" leaking into a rendered
would be worse
* than a missing fragment.
*/
private static function resolve_acf( string $resolved, \WP_Post $post ): string
{
if ( ! str_contains( $resolved, '%acf:' ) )
{
return $resolved;
}
static $cache = array();
return (string) preg_replace_callback(
'/%acf:([A-Za-z0-9_\-]+)%/',
static function ( array $matches ) use ( $post, &$cache ): string
{
if ( ! AcfDetector::is_active() )
{
return '';
}
$key = $post->ID . ':' . $matches[1];
if ( ! isset( $cache[ $key ] ) )
{
$value = get_field( $matches[1], $post->ID );
// Type-aware: scalars render, arrays/objects (image,
// relationship, repeater…) resolve to empty — a template
// fragment cannot meaningfully render them.
$cache[ $key ] = is_scalar( $value ) ? wp_strip_all_tags( (string) $value ) : '';
}
return $cache[ $key ];
},
$resolved
);
}
/**
* @return array
*/
private static function replacements( \WP_Post $post ): array
{
return array(
'%post_title%' => get_the_title( $post ),
'%post_excerpt%' => wp_strip_all_tags( (string) get_the_excerpt( $post ) ),
'%site_name%' => get_bloginfo( 'name' ),
'%tagline%' => get_bloginfo( 'description' ),
'%sep%' => '-',
'%author_name%' => get_the_author_meta( 'display_name', (int) $post->post_author ),
'%current_year%' => gmdate( 'Y' ),
);
}
}