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