| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- <?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";
- }
- /** 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( "<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 );
|