' . "\n", $property ? 'property' : 'name', esc_attr( $key ), esc_attr( $value ) ); } } /** Output once, never replacing core titles, robots, or singular canonicals. */ function sts_seo_render_metadata() { static $rendered = false; if ( $rendered ) { return; } $features = array( 'description' => sts_seo_feature_enabled( 'description' ), 'social' => sts_seo_feature_enabled( 'social' ), 'posts_canonical' => sts_seo_feature_enabled( 'posts_canonical' ), ); if ( ! array_filter( $features ) ) { return; } $data = sts_seo_metadata(); if ( ! $data ) { return; } $rendered = true; $canonical = $features['posts_canonical'] && is_home() && ! is_singular() && apply_filters( 'sts_seo_posts_canonical_enabled', true ); sts_seo_output_metadata( $data, $canonical, $features ); } /** * Render a calculated model; separated for deterministic output testing. * * @param array $data Model from sts_seo_metadata(). * @param bool $canonical Whether to print the posts-index canonical. * @param array $features Optional. Which of 'description' and 'social' to print. Default both. */ function sts_seo_output_metadata( $data, $canonical = false, $features = array() ) { $features = wp_parse_args( $features, array( 'description' => true, 'social' => true, ) ); if ( $features['description'] ) { sts_seo_meta_tag( 'description', $data['description'] ); } if ( $features['social'] ) { foreach ( array( 'title', 'description', 'type', 'site_name' ) as $key ) { sts_seo_meta_tag( 'og:' . $key, $data[ $key ], true ); } sts_seo_meta_tag( 'og:url', esc_url_raw( $data['url'], array( 'http', 'https' ) ), true ); sts_seo_meta_tag( 'twitter:card', $data['image'] ? 'summary_large_image' : 'summary' ); sts_seo_meta_tag( 'twitter:title', $data['title'] ); sts_seo_meta_tag( 'twitter:description', $data['description'] ); if ( $data['image'] ) { $url = esc_url_raw( $data['image']['url'], array( 'http', 'https' ) ); sts_seo_meta_tag( 'og:image', $url, true ); sts_seo_meta_tag( 'twitter:image', $url ); sts_seo_meta_tag( 'og:image:alt', $data['image']['alt'], true ); sts_seo_meta_tag( 'twitter:image:alt', $data['image']['alt'] ); } } // Core rel_canonical() owns every singular request, including the static front page. if ( $canonical && $data['url'] ) { printf( '' . "\n", esc_url( $data['url'], array( 'http', 'https' ) ) ); } } /** * Encode the business entity as JSON-LD text. * * The JSON_HEX_* flags encode <, >, &, ' and " as \u escapes, so the text can * never close or break out of its script element, whatever the stored values. * * @return string JSON text, or '' when there is no entity. */ function sts_seo_schema_json() { $data = sts_seo_schema_entity(); if ( ! $data ) { return ''; } return (string) wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); } /** * Output one JSON-LD entity, once, without competing with another SEO provider. * * Printed through WordPress's script-tag API rather than a hand-written tag, like * core's own import maps and speculation rules: the `wp_inline_script_attributes` * filter applies (for example a Content Security Policy nonce), and WordPress 7.0+ * refuses any data it cannot embed safely. */ function sts_seo_render_schema() { static $rendered = false; if ( $rendered || is_admin() || is_feed() || is_embed() || is_preview() || is_404() || is_search() || ! sts_seo_feature_enabled( 'schema' ) || ! apply_filters( 'sts_seo_schema_enabled', true ) ) { return; } $json = sts_seo_schema_json(); if ( '' === $json ) { return; } $rendered = true; wp_print_inline_script_tag( $json, array( 'type' => 'application/ld+json' ) ); }