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 );