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