|
@@ -0,0 +1,146 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+/**
|
|
|
|
|
+ * SEO / social head metadata — favicon, OpenGraph, Twitter cards, JSON-LD.
|
|
|
|
|
+ *
|
|
|
|
|
+ * There's no SEO plugin, so the theme emits this itself. dw_seo_head() is hooked to
|
|
|
|
|
+ * wp_head (covers every block/classic page, post, and archive). The bespoke
|
|
|
|
|
+ * front-page.php deliberately skips wp_head(), so it calls dw_seo_head() directly.
|
|
|
|
|
+ *
|
|
|
|
|
+ * URLs are built from home_url()/get_permalink() so they stay correct through the
|
|
|
|
|
+ * davidawindham.com -> davidwindham.com move. Favicon links are root-relative so every
|
|
|
|
|
+ * property (WP, /work, /rtc, /photo) can point at the same shared /favicon.* at the root.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+if ( ! defined( 'DW_SITE_NAME' ) ) { define( 'DW_SITE_NAME', 'David A. Windham' ); }
|
|
|
|
|
+if ( ! defined( 'DW_TWITTER' ) ) { define( 'DW_TWITTER', '@windhamdavid' ); }
|
|
|
|
|
+// Canonical brand domain (the consolidation target). The JSON-LD *identity* nodes point
|
|
|
|
|
+// here. Per-page og:url / <link rel=canonical> stay on the serving domain (home_url) so
|
|
|
|
|
+// they don't point at not-yet-migrated pages; they flip automatically when siteurl moves.
|
|
|
|
|
+if ( ! defined( 'DW_CANONICAL' ) ) { define( 'DW_CANONICAL', 'https://davidwindham.com' ); }
|
|
|
|
|
+
|
|
|
|
|
+/** Shared favicon links (root-hosted; same files served for every property). */
|
|
|
|
|
+function dw_seo_favicon() {
|
|
|
|
|
+ echo "<link rel=\"icon\" href=\"/favicon.ico\" sizes=\"any\">\n";
|
|
|
|
|
+ echo "<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n";
|
|
|
|
|
+ echo "<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n";
|
|
|
|
|
+ echo "<link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\">\n";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Page description: post meta_desc field -> excerpt -> site tagline. Returns a string. */
|
|
|
|
|
+function dw_seo_description() {
|
|
|
|
|
+ if ( is_singular() ) {
|
|
|
|
|
+ $id = get_queried_object_id();
|
|
|
|
|
+ $d = trim( (string) get_post_meta( $id, 'meta_desc', true ) );
|
|
|
|
|
+ if ( $d === '' && function_exists( 'dw_good_excerpt' ) ) {
|
|
|
|
|
+ ob_start();
|
|
|
|
|
+ dw_good_excerpt( 300 );
|
|
|
|
|
+ $d = trim( (string) ob_get_clean() );
|
|
|
|
|
+ }
|
|
|
|
|
+ if ( $d !== '' ) {
|
|
|
|
|
+ return $d;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ $tagline = trim( (string) get_bloginfo( 'description' ) );
|
|
|
|
|
+ return $tagline !== '' ? $tagline : DW_SITE_NAME;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** OG image: the post's featured image on singulars, else the shared default. Absolute. */
|
|
|
|
|
+function dw_seo_image() {
|
|
|
|
|
+ if ( is_singular() && has_post_thumbnail() ) {
|
|
|
|
|
+ $src = wp_get_attachment_image_src( get_post_thumbnail_id(), 'large' );
|
|
|
|
|
+ if ( $src ) {
|
|
|
|
|
+ return $src[0];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return home_url( '/og-image.jpg' ); // shared image at the web root, same-origin
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Absolute URL for the current view (og:url). */
|
|
|
|
|
+function dw_seo_url() {
|
|
|
|
|
+ if ( is_front_page() ) {
|
|
|
|
|
+ return home_url( '/' );
|
|
|
|
|
+ }
|
|
|
|
|
+ if ( is_singular() ) {
|
|
|
|
|
+ return get_permalink();
|
|
|
|
|
+ }
|
|
|
|
|
+ if ( is_category() || is_tag() || is_tax() ) {
|
|
|
|
|
+ $link = get_term_link( get_queried_object() );
|
|
|
|
|
+ if ( ! is_wp_error( $link ) ) {
|
|
|
|
|
+ return $link;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ global $wp;
|
|
|
|
|
+ return home_url( '/' . ltrim( $wp->request, '/' ) );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** OpenGraph + Twitter card tags for pages / archives / front page.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Single POSTS are intentionally skipped — dw_opengraph() in inc/tweaks.php owns their
|
|
|
|
|
+ * OG, including the featured-video setup (og:video + twitter:player -> /container/ ->
|
|
|
|
|
+ * single-container.php). Doubling up here would emit conflicting tags and break that. */
|
|
|
|
|
+function dw_seo_opengraph() {
|
|
|
|
|
+ if ( is_single() ) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $title = wp_get_document_title();
|
|
|
|
|
+ $desc = dw_seo_description();
|
|
|
|
|
+ $url = dw_seo_url();
|
|
|
|
|
+ $img = dw_seo_image();
|
|
|
|
|
+ $type = is_single() ? 'article' : 'website';
|
|
|
|
|
+
|
|
|
|
|
+ printf( "<meta property=\"og:type\" content=\"%s\">\n", esc_attr( $type ) );
|
|
|
|
|
+ printf( "<meta property=\"og:site_name\" content=\"%s\">\n", esc_attr( DW_SITE_NAME ) );
|
|
|
|
|
+ printf( "<meta property=\"og:title\" content=\"%s\">\n", esc_attr( $title ) );
|
|
|
|
|
+ printf( "<meta property=\"og:description\" content=\"%s\">\n", esc_attr( $desc ) );
|
|
|
|
|
+ printf( "<meta property=\"og:url\" content=\"%s\">\n", esc_url( $url ) );
|
|
|
|
|
+ printf( "<meta property=\"og:image\" content=\"%s\">\n", esc_url( $img ) );
|
|
|
|
|
+
|
|
|
|
|
+ printf( "<meta name=\"twitter:card\" content=\"summary_large_image\">\n" );
|
|
|
|
|
+ printf( "<meta name=\"twitter:creator\" content=\"%s\">\n", esc_attr( DW_TWITTER ) );
|
|
|
|
|
+ printf( "<meta name=\"twitter:title\" content=\"%s\">\n", esc_attr( $title ) );
|
|
|
|
|
+ printf( "<meta name=\"twitter:description\" content=\"%s\">\n", esc_attr( $desc ) );
|
|
|
|
|
+ printf( "<meta name=\"twitter:image\" content=\"%s\">\n", esc_url( $img ) );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Person + WebSite JSON-LD, site-wide. */
|
|
|
|
|
+function dw_seo_jsonld() {
|
|
|
|
|
+ $person = array(
|
|
|
|
|
+ '@context' => 'https://schema.org',
|
|
|
|
|
+ '@type' => 'Person',
|
|
|
|
|
+ 'name' => DW_SITE_NAME,
|
|
|
|
|
+ 'url' => DW_CANONICAL,
|
|
|
|
|
+ 'image' => DW_CANONICAL . '/og-image.jpg',
|
|
|
|
|
+ 'jobTitle' => 'Web Developer',
|
|
|
|
|
+ 'sameAs' => array(
|
|
|
|
|
+ 'https://davidwindham.com',
|
|
|
|
|
+ 'https://github.com/windhamdavid',
|
|
|
|
|
+ 'https://en.wikipedia.org/wiki/User:Windhamdavid',
|
|
|
|
|
+ 'https://mastodon.social/@windhamdavid',
|
|
|
|
|
+ 'https://www.linkedin.com/in/windhamdavid',
|
|
|
|
|
+ 'https://www.facebook.com/windhamdavid',
|
|
|
|
|
+ 'https://www.reddit.com/user/windhamdavid',
|
|
|
|
|
+ 'https://news.ycombinator.com/user?id=windhamdavid',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ $website = array(
|
|
|
|
|
+ '@context' => 'https://schema.org',
|
|
|
|
|
+ '@type' => 'WebSite',
|
|
|
|
|
+ 'url' => DW_CANONICAL,
|
|
|
|
|
+ 'name' => DW_SITE_NAME,
|
|
|
|
|
+ 'potentialAction' => array(
|
|
|
|
|
+ '@type' => 'SearchAction',
|
|
|
|
|
+ 'target' => DW_CANONICAL . '/?s={search_term_string}',
|
|
|
|
|
+ 'query-input' => 'required name=search_term_string',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ echo '<script type="application/ld+json">' . wp_json_encode( $person, JSON_UNESCAPED_SLASHES ) . "</script>\n";
|
|
|
|
|
+ echo '<script type="application/ld+json">' . wp_json_encode( $website, JSON_UNESCAPED_SLASHES ) . "</script>\n";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Emit the whole head block. Hooked to wp_head; front-page.php calls it directly. */
|
|
|
|
|
+function dw_seo_head() {
|
|
|
|
|
+ dw_seo_favicon();
|
|
|
|
|
+ dw_seo_opengraph();
|
|
|
|
|
+ dw_seo_jsonld();
|
|
|
|
|
+}
|
|
|
|
|
+add_action( 'wp_head', 'dw_seo_head', 5 );
|