diff --git a/themes/flavian-shop/functions.php b/themes/flavian-shop/functions.php index 03e0327..65d1a52 100644 --- a/themes/flavian-shop/functions.php +++ b/themes/flavian-shop/functions.php @@ -13,6 +13,9 @@ define( 'FLAVIAN_SHOP_VERSION', '0.1.0' ); } +// Front-end SEO metadata (meta description, Open Graph, Twitter Card, JSON-LD). +require get_template_directory() . '/inc/seo.php'; + /** * Theme setup. * @@ -22,6 +25,7 @@ function flavian_shop_setup() { load_theme_textdomain( 'flavian-shop', get_template_directory() . '/languages' ); + add_theme_support( 'title-tag' ); add_theme_support( 'wp-block-styles' ); add_theme_support( 'editor-styles' ); add_theme_support( 'responsive-embeds' ); diff --git a/themes/flavian-shop/inc/seo.php b/themes/flavian-shop/inc/seo.php new file mode 100644 index 0000000..814152e --- /dev/null +++ b/themes/flavian-shop/inc/seo.php @@ -0,0 +1,168 @@ + is left to + * WordPress (title-tag support), so this only adds what core doesn't. + * + * @package Flavian_Shop + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +/** + * Whether a dedicated SEO plugin is already managing head output. + * + * @return bool + */ +function flavian_shop_seo_plugin_active() { + return defined( 'WPSEO_VERSION' ) // Yoast SEO. + || defined( 'RANK_MATH_VERSION' ) // Rank Math. + || defined( 'AIOSEO_VERSION' ) // All in One SEO. + || defined( 'SEOPRESS_VERSION' ); // SEOPress. +} + +/** + * Resolve a clean, length-capped description for the current view. + * + * @return string + */ +function flavian_shop_seo_description() { + $description = ''; + + if ( is_singular() ) { + $post = get_queried_object(); + if ( $post instanceof WP_Post ) { + $description = has_excerpt( $post ) ? get_the_excerpt( $post ) : $post->post_content; + } + } elseif ( is_category() || is_tag() || is_tax() ) { + $description = term_description(); + } + + if ( '' === trim( $description ) ) { + $description = get_bloginfo( 'description', 'display' ); + } + + $description = trim( (string) preg_replace( '/\s+/', ' ', wp_strip_all_tags( $description ) ) ); + + return wp_html_excerpt( $description, 160, '' ); +} + +/** + * Canonical URL for the current view. + * + * @return string + */ +function flavian_shop_seo_url() { + $canonical = is_singular() ? wp_get_canonical_url() : false; + + return $canonical ? $canonical : home_url( '/' ); +} + +/** + * Featured-image URL for the current singular view, or '' when none. + * + * @return string + */ +function flavian_shop_seo_image() { + if ( is_singular() && has_post_thumbnail() ) { + $src = get_the_post_thumbnail_url( get_queried_object_id(), 'full' ); + if ( $src ) { + return $src; + } + } + + return ''; +} + +/** + * Output meta description, Open Graph, and Twitter Card tags. + * + * @return void + */ +function flavian_shop_seo_meta_tags() { + if ( flavian_shop_seo_plugin_active() ) { + return; + } + + $title = wp_get_document_title(); + $description = flavian_shop_seo_description(); + $url = flavian_shop_seo_url(); + $image = flavian_shop_seo_image(); + $type = is_singular( 'post' ) ? 'article' : 'website'; + $card = '' !== $image ? 'summary_large_image' : 'summary'; + + printf( '' . "\n", esc_attr( $description ) ); + + printf( '' . "\n", esc_attr( $type ) ); + printf( '' . "\n", esc_attr( $title ) ); + printf( '' . "\n", esc_attr( $description ) ); + printf( '' . "\n", esc_url( $url ) ); + printf( '' . "\n", esc_attr( get_bloginfo( 'name' ) ) ); + + printf( '' . "\n", esc_attr( $card ) ); + printf( '' . "\n", esc_attr( $title ) ); + printf( '' . "\n", esc_attr( $description ) ); + + if ( '' !== $image ) { + printf( '' . "\n", esc_url( $image ) ); + printf( '' . "\n", esc_url( $image ) ); + } +} +add_action( 'wp_head', 'flavian_shop_seo_meta_tags', 5 ); + +/** + * Output JSON-LD structured data for the current view (Article for posts, + * WebSite otherwise). + * + * @return void + */ +function flavian_shop_seo_jsonld() { + if ( flavian_shop_seo_plugin_active() ) { + return; + } + + if ( is_singular( 'post' ) ) { + $post = get_queried_object(); + if ( ! $post instanceof WP_Post ) { + return; + } + $data = array( + '@context' => 'https://schema.org', + '@type' => 'Article', + 'headline' => wp_strip_all_tags( get_the_title( $post ) ), + 'datePublished' => get_post_time( 'c', true, $post ), + 'dateModified' => get_post_modified_time( 'c', true, $post ), + 'author' => array( + '@type' => 'Person', + 'name' => get_the_author_meta( 'display_name', (int) $post->post_author ), + ), + 'mainEntityOfPage' => flavian_shop_seo_url(), + ); + + $image = flavian_shop_seo_image(); + if ( '' !== $image ) { + $data['image'] = $image; + } + } else { + $data = array( + '@context' => 'https://schema.org', + '@type' => 'WebSite', + 'name' => get_bloginfo( 'name' ), + 'url' => home_url( '/' ), + ); + } + + $json = wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); + if ( false === $json ) { + return; + } + + // wp_json_encode with JSON_HEX_* escapes <, >, &, ', " so the payload is safe inside ' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +} +add_action( 'wp_head', 'flavian_shop_seo_jsonld', 6 );