builder = $builder; $this->location_evaluator = $location_evaluator; $this->features = $features; } public function register(): void { add_action( 'wp_head', array( $this, 'print_schema' ), 10 ); } public function print_schema(): void { if ( ! $this->should_handle() || ! $this->features->enabled( 'schema' ) ) { return; } $post = CurrentSingularPost::get(); if ( null === $post || ! $this->location_evaluator->applies_to_schema( $post ) ) { return; } $json = $this->cached_json( $post ); if ( ! is_string( $json ) ) { return; } // wp_print_inline_script_tag() is core's own printer for inline script // tags: it builds the tag through WP_HTML_Tag_Processor, which refuses // to emit text that could close the element early, and it does NOT // html-escape the body — esc_html() on JSON breaks JSON-LD, because it // escapes the quotes INSIDE values and the document stops parsing // (measured, M74). The JSON itself is produced by wp_json_encode() with // JSON_HEX_TAG (see GraphCache), so `<` and `>` are already \u003C/\u003E. wp_print_inline_script_tag( $json, array( 'type' => 'application/ld+json' ) ); } private function cached_json( \WP_Post $post ): ?string { $cache_key = sprintf( 'graph_%d_v%d', $post->ID, CacheVersion::get() ); $cached = \AcezSeo\Support\GraphCache::get( $cache_key ); if ( is_string( $cached ) ) { return $cached; } $graph = $this->builder->build( $post ); // JSON_HEX_TAG turns every literal "<"/">" into , so no // dynamic string field (e.g. a post title) can ever contain a literal // "" in any case combination — this is the actual breakout // protection (a case-sensitive str_replace() on ""/""). The HEX_AMP/APOS/QUOT flags harden the // same value against being reused verbatim in an HTML attribute. $json = wp_json_encode( $graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); if ( ! is_string( $json ) ) { return null; } \AcezSeo\Support\GraphCache::set( $cache_key, $json, self::CACHE_TTL ); return $json; } private function should_handle(): bool { /** * Filters whether Acez SEO should output its own JSON-LD graph. * * @param bool $should_handle */ return (bool) apply_filters( 'acez_seo_should_handle_schema', ! SeoPluginDetector::has_conflict() ); } }