term_meta = $term_meta; $this->archive_settings = $archive_settings; $this->features = $features; $this->archive_graph = $archive_graph; } 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; } $term = $this->current_term(); if ( null === $term ) { return; } if ( ! (bool) $this->archive_settings->get( $term->taxonomy )['schema_enabled'] ) { return; } $json = $this->cached_json( $term ); 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_Term $term ): ?string { $cache_key = sprintf( 'term_graph_%d_v%d', $term->term_id, CacheVersion::get() ); $cached = \AcezSeo\Support\GraphCache::get( $cache_key ); if ( is_string( $cached ) ) { return $cached; } $json = ArchiveGraph::encode( $this->build_graph( $term ) ); if ( ! is_string( $json ) ) { return null; } \AcezSeo\Support\GraphCache::set( $cache_key, $json, self::CACHE_TTL ); return $json; } /** * @return array */ private function build_graph( \WP_Term $term ): array { $description = (string) $this->term_meta->get( $term->term_id, TermMetaFields::DESCRIPTION ); if ( '' === $description ) { $description = wp_strip_all_tags( term_description( $term->term_id ) ); } $url = (string) get_term_link( $term ); $node = array( '@type' => 'CollectionPage', '@id' => $url . '#collectionpage', 'url' => $url, 'name' => $term->name, 'description' => $description, 'isPartOf' => array( '@id' => SchemaIds::website() ), ); /** * Filters the CollectionPage schema node for a taxonomy archive. * * @param array $node * @param \WP_Term $term */ $node = (array) apply_filters( 'acez_seo_collection_page', $node, $term ); return $this->archive_graph->build( $node ); } private function should_handle(): bool { return (bool) apply_filters( 'acez_seo_should_handle_schema', ! SeoPluginDetector::has_conflict() ); } private function current_term(): ?\WP_Term { return CurrentArchiveTerm::get(); } }