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 / 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 "\n"; echo "\n"; echo "\n"; echo "\n"; } /** Plain-text description from a post's content WITHOUT rendering dynamic blocks. * * The old path (dw_good_excerpt) ran the full the_content filter here, which re-renders any * embedded Gravity Forms block during wp_head — wasteful, and it trips a GF Post-Category * warning on the guestbook/contact pages. excerpt_remove_blocks() drops the form (and other * dynamic blocks) first, so only static text blocks get rendered. Returns a trimmed string. */ function dw_seo_excerpt_from_content( $id ) { $post = get_post( $id ); if ( ! $post ) { return ''; } $text = trim( (string) $post->post_excerpt ); if ( $text === '' ) { $content = (string) $post->post_content; if ( function_exists( 'excerpt_remove_blocks' ) ) { $content = excerpt_remove_blocks( $content ); // strip forms + other dynamic blocks } $text = do_blocks( $content ); } $text = wp_strip_all_tags( strip_shortcodes( $text ) ); $text = trim( preg_replace( '/\s+/', ' ', $text ) ); if ( mb_strlen( $text ) > 300 ) { $text = rtrim( mb_substr( $text, 0, 300 ) ) . '…'; } return $text; } /** 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 === '' ) { $d = dw_seo_excerpt_from_content( $id ); } 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( "\n", esc_attr( $type ) ); printf( "\n", esc_attr( DW_SITE_NAME ) ); printf( "\n", esc_attr( $title ) ); printf( "\n", esc_attr( $desc ) ); printf( "\n", esc_url( $url ) ); printf( "\n", esc_url( $img ) ); printf( "\n" ); printf( "\n", esc_attr( DW_TWITTER ) ); printf( "\n", esc_attr( $title ) ); printf( "\n", esc_attr( $desc ) ); printf( "\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 '\n"; echo '\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 );