Article schema, or empty array if not applicable. */ public function generate( \WP_Post $post ): array { $permalink = get_permalink( $post ); if ( ! $permalink ) { return array(); } /** * Filters the schema.org Article type for a post. * * @param string $type The Article type (default 'BlogPosting'). * @param \WP_Post $post The post. */ $type = (string) apply_filters( 'aicommerce_article_type', 'BlogPosting', $post ); $article = array( '@context' => 'https://schema.org', '@type' => $type, 'mainEntityOfPage' => array( '@type' => 'WebPage', '@id' => $permalink, ), 'headline' => $this->headline( $post ), 'url' => $permalink, 'datePublished' => $this->date( $post->post_date_gmt ), 'dateModified' => $this->date( $post->post_modified_gmt ), ); $description = $this->description( $post ); if ( '' !== $description ) { $article['description'] = $description; } $image = $this->image( $post ); if ( ! empty( $image ) ) { $article['image'] = $image; } $author = $this->author( $post ); if ( ! empty( $author ) ) { $article['author'] = $author; } $publisher = $this->publisher(); if ( ! empty( $publisher ) ) { $article['publisher'] = $publisher; } $sections = $this->sections( $post ); if ( ! empty( $sections ) ) { $article['articleSection'] = $sections; } $keywords = $this->keywords( $post ); if ( '' !== $keywords ) { $article['keywords'] = $keywords; } $language = get_bloginfo( 'language' ); if ( ! empty( $language ) ) { $article['inLanguage'] = $language; } /** * Filters the Article schema for a post. * * @param array $article The Article schema. * @param \WP_Post $post The post. */ return (array) apply_filters( 'aicommerce_article_schema', $article, $post ); } /** * Build the headline (Google recommends <= 110 characters). * * @param \WP_Post $post The post. * * @return string */ private function headline( \WP_Post $post ): string { $title = wp_strip_all_tags( get_the_title( $post ) ); if ( mb_strlen( $title ) > 110 ) { $title = mb_substr( $title, 0, 107 ) . '...'; } return $title; } /** * Build a plain-text description from the excerpt or content. * * @param \WP_Post $post The post. * * @return string */ private function description( \WP_Post $post ): string { $text = has_excerpt( $post ) ? get_the_excerpt( $post ) : $post->post_content; $text = wp_strip_all_tags( (string) $text ); $text = trim( preg_replace( '/\s+/', ' ', $text ) ?? '' ); if ( mb_strlen( $text ) > 300 ) { $text = mb_substr( $text, 0, 297 ) . '...'; } return $text; } /** * Build the image array from the featured image. * * @param \WP_Post $post The post. * * @return array */ private function image( \WP_Post $post ): array { $thumb_id = get_post_thumbnail_id( $post ); if ( ! $thumb_id ) { return array(); } $url = wp_get_attachment_image_url( $thumb_id, 'full' ); return $url ? array( $url ) : array(); } /** * Build the author (Person) node. * * @param \WP_Post $post The post. * * @return array */ private function author( \WP_Post $post ): array { $author_id = (int) $post->post_author; if ( $author_id <= 0 ) { return array(); } $name = get_the_author_meta( 'display_name', $author_id ); if ( '' === (string) $name ) { return array(); } $author = array( '@type' => 'Person', 'name' => $name, ); $url = get_author_posts_url( $author_id ); if ( $url ) { $author['url'] = $url; } return $author; } /** * Build the publisher (Organization) node. * * @return array */ private function publisher(): array { $publisher = array( '@type' => 'Organization', 'name' => get_bloginfo( 'name' ), 'url' => home_url( '/' ), ); $logo_id = get_theme_mod( 'custom_logo' ); if ( $logo_id ) { $logo_url = wp_get_attachment_image_url( $logo_id, 'full' ); if ( $logo_url ) { $publisher['logo'] = array( '@type' => 'ImageObject', 'url' => $logo_url, ); } } return $publisher; } /** * Build the list of category names (articleSection). * * @param \WP_Post $post The post. * * @return array */ private function sections( \WP_Post $post ): array { $terms = get_the_terms( $post, 'category' ); if ( ! is_array( $terms ) ) { return array(); } return array_values( array_map( static fn( \WP_Term $term ): string => $term->name, $terms ) ); } /** * Build a comma-separated keyword string from tags. * * @param \WP_Post $post The post. * * @return string */ private function keywords( \WP_Post $post ): string { $terms = get_the_terms( $post, 'post_tag' ); if ( ! is_array( $terms ) ) { return ''; } $names = array_map( static fn( \WP_Term $term ): string => $term->name, $terms ); return implode( ', ', $names ); } /** * Convert a GMT date string to ISO 8601. * * @param string $gmt_date The GMT date (Y-m-d H:i:s). * * @return string */ private function date( string $gmt_date ): string { if ( '' === $gmt_date || '0000-00-00 00:00:00' === $gmt_date ) { return ''; } $timestamp = strtotime( $gmt_date . ' UTC' ); return $timestamp ? gmdate( 'c', $timestamp ) : ''; } }