<?php /** * Plugin Name: Hausjournal Structured Data * Description: Yoast-Schema erweitern: Organization, BlogPosting, Kategorien, Autoren, Trust-Seiten. */ declare(strict_types=1); const HJ_SCHEMA_CATEGORY_ITEMLIST_LIMIT = 8; add_filter('wpseo_schema_article_type', 'hj_schema_article_type', 20, 2); add_filter('wpseo_schema_webpage', 'hj_schema_webpage', 11, 2); add_filter('wpseo_schema_webpage_type', 'hj_schema_webpage_type', 11, 1); add_filter('wpseo_schema_article', 'hj_schema_article', 11, 2); add_filter('wpseo_schema_graph', 'hj_schema_enhance_graph', 11, 2); /** * @param string|array<string> $type * @return string|array<string> */ function hj_schema_article_type($type, $indexable) { if (!is_object($indexable) || ($indexable->object_type ?? '') !== 'post' || ($indexable->object_sub_type ?? '') !== 'post') { return $type; } if ($type === 'None' || $type === 'Article') { return 'BlogPosting'; } if (is_array($type) && in_array('None', $type, true)) { return 'BlogPosting'; } return $type; } /** * @param array<string, mixed> $data * @return array<string, mixed> */ function hj_schema_webpage(array $data, $context): array { if (!is_object($context) || empty($context->indexable)) { return $data; } $object_type = (string) ($context->indexable->object_type ?? ''); $object_sub_type = (string) ($context->indexable->object_sub_type ?? ''); if ($object_type === 'post' && $object_sub_type === 'post' && !empty($context->canonical)) { $data['mainEntity'] = ['@id' => $context->canonical . '#article']; } if ($object_type === 'term' && $object_sub_type === 'category') { $description = hj_schema_resolve_description(); if ($description !== '') { $data['description'] = $description; } } if ($object_type === 'user') { $description = hj_schema_resolve_description(); if ($description !== '') { $data['description'] = $description; } } if ($object_type === 'post' && $object_sub_type === 'page') { $slug = (string) get_post_field('post_name', (int) $context->indexable->object_id); $org_id = hj_schema_organization_id(); if (in_array($slug, ['ueber-uns', 'redaktionsprozess'], true)) { $description = hj_schema_resolve_description(); if ($description !== '') { $data['description'] = $description; } $data['about'] = ['@id' => $org_id]; $data['publisher'] = ['@id' => $org_id]; } if ($slug === 'redaktionsprozess') { $data['specialty'] = 'Bau- und Heimwerkerjournalismus mit redaktioneller Qualitaetssicherung'; } } return $data; } /** * @param string|array<string> $type * @return string|array<string> */ function hj_schema_webpage_type($type) { if (is_page()) { $slug = (string) get_post_field('post_name', get_queried_object_id()); if (in_array($slug, ['ueber-uns', 'redaktionsprozess'], true)) { return ['WebPage', 'AboutPage']; } } if (is_author()) { return ['ProfilePage', 'WebPage']; } return $type; } /** * @param array<string, mixed> $data * @return array<string, mixed> */ function hj_schema_article(array $data, $context): array { if (empty($data) || !is_object($context) || empty($context->post) || !($context->post instanceof WP_Post)) { return $data; } if (empty($data['description'])) { $description = hj_schema_post_description($context->post); if ($description !== '') { $data['description'] = $description; } } return $data; } /** * @param array<int, array<string, mixed>> $graph * @return array<int, array<string, mixed>> */ function hj_schema_enhance_graph(array $graph, $context): array { $org_id = hj_schema_organization_id(); foreach ($graph as $index => $piece) { if (($piece['@type'] ?? '') === 'WebSite' && empty($piece['publisher'])) { $graph[$index]['publisher'] = ['@id' => $org_id]; } } if (is_category()) { return hj_schema_enhance_category_graph($graph, $context); } if (is_author()) { return hj_schema_enhance_author_graph($graph, $context); } return $graph; } /** * @param array<int, array<string, mixed>> $graph * @return array<int, array<string, mixed>> */ function hj_schema_enhance_category_graph(array $graph, $context): array { $term = get_queried_object(); if (!$term instanceof WP_Term || empty($context->canonical)) { return $graph; } $canonical = (string) $context->canonical; $image = hj_schema_category_image($term); if ($image !== null) { $graph = hj_schema_replace_primary_image($graph, $canonical, $image); } $description = hj_schema_resolve_description(); if ($description !== '') { $graph = hj_schema_set_piece_property($graph, $canonical, 'description', $description); } $item_list = hj_schema_build_category_item_list($term, $canonical); if ($item_list !== null) { $graph[] = $item_list; $graph = hj_schema_set_piece_property($graph, $canonical, 'mainEntity', ['@id' => $canonical . '#itemlist']); } return $graph; } /** * @param array<int, array<string, mixed>> $graph * @return array<int, array<string, mixed>> */ function hj_schema_enhance_author_graph(array $graph, $context): array { $author = get_queried_object(); if (!$author instanceof WP_User || empty($context->canonical)) { return $graph; } $canonical = (string) $context->canonical; $description = hj_schema_resolve_description(); if ($description !== '') { $graph = hj_schema_set_piece_property($graph, $canonical, 'description', $description); } $fachgebiet = hj_schema_clean_text((string) get_user_meta($author->ID, 'fachgebiet', true)); if ($fachgebiet !== '') { foreach ($graph as $index => $piece) { $type = $piece['@type'] ?? ''; if ($type === 'Person' || (is_array($type) && in_array('Person', $type, true))) { $graph[$index]['jobTitle'] = 'Autor'; $graph[$index]['knowsAbout'] = [$fachgebiet]; if ($description !== '' && empty($graph[$index]['description'])) { $graph[$index]['description'] = $description; } } } } return $graph; } function hj_schema_organization_id(): string { return trailingslashit(home_url('/')) . '#organization'; } function hj_schema_resolve_description(): string { if (function_exists('hj_seo_resolve_metadesc')) { return hj_schema_clean_text((string) hj_seo_resolve_metadesc()); } return ''; } function hj_schema_post_description(WP_Post $post): string { $description = ''; if (function_exists('YoastSEO')) { $presentation = YoastSEO()->meta->for_post($post->ID); if (isset($presentation->open_graph_description)) { $description = (string) $presentation->open_graph_description; } if ($description === '' && isset($presentation->meta_description)) { $description = (string) $presentation->meta_description; } } if ($description === '') { $description = get_the_excerpt($post); } return hj_schema_clean_text($description); } /** * @return array<string, mixed>|null */ function hj_schema_category_image(WP_Term $term): ?array { if (class_exists('WPSEO_Taxonomy_Meta')) { $meta = WPSEO_Taxonomy_Meta::get_term_meta((int) $term->term_id, 'category'); $og_image_id = (int) ($meta['wpseo_opengraph-image-id'] ?? 0); if ($og_image_id > 0) { $image = hj_schema_image_from_attachment($og_image_id, $term->name); if ($image !== null) { return $image; } } } $posts = get_posts([ 'cat' => (int) $term->term_id, 'posts_per_page' => 12, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => 'post', 'post_status' => 'publish', 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ]); foreach ($posts as $post_id) { $post_id = (int) $post_id; if (!has_post_thumbnail($post_id)) { continue; } $image = hj_schema_image_from_attachment((int) get_post_thumbnail_id($post_id), $term->name); if ($image !== null) { return $image; } } return null; } /** * @return array<string, mixed>|null */ function hj_schema_image_from_attachment(int $attachment_id, string $caption): ?array { if ($attachment_id < 1) { return null; } $url = wp_get_attachment_image_url($attachment_id, 'full'); if (!is_string($url) || $url === '') { return null; } $meta = wp_get_attachment_metadata($attachment_id); $width = is_array($meta) ? (int) ($meta['width'] ?? 0) : 0; $height = is_array($meta) ? (int) ($meta['height'] ?? 0) : 0; return [ 'url' => $url, 'contentUrl' => $url, 'width' => $width > 0 ? $width : null, 'height' => $height > 0 ? $height : null, 'caption' => hj_schema_clean_text($caption), ]; } /** * @param array<int, array<string, mixed>> $graph * @param array<string, mixed> $image * @return array<int, array<string, mixed>> */ function hj_schema_replace_primary_image(array $graph, string $canonical, array $image): array { $image_id = $canonical . '#primaryimage'; foreach ($graph as $index => $piece) { $piece_id = (string) ($piece['@id'] ?? ''); if ($piece_id === $canonical) { $graph[$index]['thumbnailUrl'] = $image['url']; continue; } if ($piece_id !== $image_id) { continue; } $graph[$index]['url'] = $image['url']; $graph[$index]['contentUrl'] = $image['contentUrl']; if (!empty($image['width'])) { $graph[$index]['width'] = $image['width']; } if (!empty($image['height'])) { $graph[$index]['height'] = $image['height']; } if (!empty($image['caption'])) { $graph[$index]['caption'] = $image['caption']; } } return $graph; } /** * @param array<int, array<string, mixed>> $graph * @param mixed $value * @return array<int, array<string, mixed>> */ function hj_schema_set_piece_property(array $graph, string $piece_id, string $property, $value): array { foreach ($graph as $index => $piece) { if (($piece['@id'] ?? '') === $piece_id) { $graph[$index][$property] = $value; } } return $graph; } /** * @return array<string, mixed>|null */ function hj_schema_build_category_item_list(WP_Term $term, string $canonical): ?array { $posts = get_posts([ 'cat' => (int) $term->term_id, 'posts_per_page' => HJ_SCHEMA_CATEGORY_ITEMLIST_LIMIT, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => 'post', 'post_status' => 'publish', 'no_found_rows' => true, ]); if ($posts === []) { return null; } $elements = []; $position = 1; foreach ($posts as $post) { if (!$post instanceof WP_Post) { continue; } $name = hj_schema_clean_text(get_the_title($post)); $url = get_permalink($post); if ($name === '' || !is_string($url) || $url === '') { continue; } $elements[] = [ '@type' => 'ListItem', 'position' => $position, 'name' => $name, 'url' => $url, ]; ++$position; } if ($elements === []) { return null; } return [ '@type' => 'ItemList', '@id' => $canonical . '#itemlist', 'name' => hj_schema_clean_text($term->name), 'itemListOrder' => 'https://schema.org/ItemListOrderDescending', 'numberOfItems' => count($elements), 'itemListElement' => $elements, ]; } function hj_schema_clean_text(string $text): string { if (function_exists('hj_seo_clean_text')) { return hj_seo_clean_text($text); } $charset = get_bloginfo('charset') ?: 'UTF-8'; $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, $charset); $text = wp_strip_all_tags($text, true); $text = preg_replace('/\s+/u', ' ', $text) ?? ''; return trim($text); } <?php /** * Plugin Name: Hausjournal Technical SEO * Description: Meta-Fallbacks, Autoren-Policy, Sitemap-Filter und Yoast-Titel-Hilfen. */ declare(strict_types=1); const HJ_SEO_ORG_LOGO_ATTACHMENT_ID = 3644; const HJ_SEO_NOINDEX_PAGE_SLUGS = [ 'links', 'microsite/ms1', 'microsite/ms2', 'microsite/ms3', 'microsite/ms4', 'stefan-raab-doosh-gewinnspiel', ]; add_filter('wpseo_schema_company_name', static function (): string { return 'Hausjournal.net'; }); add_filter('wpseo_schema_company_logo_id', static function (): int { return HJ_SEO_ORG_LOGO_ATTACHMENT_ID; }); add_filter('wpseo_robots', 'hj_seo_robots', 20); add_filter('wpseo_title', 'hj_seo_title_fallback', 20); add_filter('wpseo_metadesc', 'hj_seo_metadesc_fallback', 20); add_filter('wpseo_opengraph_desc', 'hj_seo_metadesc_fallback', 20); add_filter('wpseo_opengraph_title', 'hj_seo_title_fallback', 20); add_filter('wpseo_sitemap_entry', 'hj_seo_filter_sitemap_entry', 10, 3); add_action('wp_head', 'hj_seo_print_description_meta', 1); function hj_seo_robots(string $robots): string { if (is_author() && !hj_seo_author_should_index()) { return 'noindex, follow'; } if (is_page()) { $slug = hj_seo_page_slug(get_queried_object_id()); if (in_array($slug, HJ_SEO_NOINDEX_PAGE_SLUGS, true)) { return 'noindex, follow'; } } return $robots; } function hj_seo_title_fallback(string $title): string { $resolved = hj_seo_resolve_title(); return $resolved !== '' ? $resolved : $title; } function hj_seo_metadesc_fallback(string $description): string { $resolved = hj_seo_resolve_metadesc(); return $resolved !== '' ? $resolved : $description; } function hj_seo_resolve_title(): string { if (is_author()) { $author = get_queried_object(); if ($author instanceof WP_User && hj_seo_author_should_index($author)) { $name = hj_seo_clean_text($author->display_name); return $name !== '' ? $name . ' | Hausjournal.net' : ''; } } if (is_category()) { $term = get_queried_object(); if ($term instanceof WP_Term) { $name = hj_seo_clean_text($term->name); $count = max(0, (int) $term->count); if ($count > 0) { return sprintf('%s: Tipps, Anleitungen & Ratgeber | Hausjournal.net', $name); } return sprintf('%s | Hausjournal.net', $name); } } return ''; } function hj_seo_resolve_metadesc(): string { if (is_author()) { $author = get_queried_object(); if ($author instanceof WP_User) { return hj_seo_build_author_metadesc($author); } } if (is_category()) { $term = get_queried_object(); if ($term instanceof WP_Term) { if (class_exists('WPSEO_Taxonomy_Meta')) { $stored = (string) WPSEO_Taxonomy_Meta::get_term_meta((int) $term->term_id, 'category', 'desc'); if ($stored !== '') { return $stored; } } return hj_seo_build_category_metadesc($term); } } if (is_page()) { $post_id = get_queried_object_id(); $stored = (string) get_post_meta($post_id, '_yoast_wpseo_metadesc', true); if ($stored !== '') { return $stored; } return hj_seo_build_page_metadesc($post_id); } if (is_singular('post')) { $post = get_queried_object(); if ($post instanceof WP_Post) { $stored = (string) get_post_meta($post->ID, '_yoast_wpseo_metadesc', true); if ($stored !== '') { return $stored; } $excerpt = hj_seo_clean_text(get_the_excerpt($post)); if ($excerpt !== '') { return hj_seo_trim_description($excerpt); } } } return ''; } function hj_seo_print_description_meta(): void { if (is_admin() || is_feed()) { return; } $description = hj_seo_resolve_metadesc(); if ($description === '') { return; } echo '<meta name="description" content="' . esc_attr($description) . '" />' . "\n"; } /** * @param array<string, mixed>|false $url * @return array<string, mixed>|false */ function hj_seo_filter_sitemap_entry($url, string $type, $object) { if (!is_array($url) || empty($url['loc']) || !is_string($url['loc'])) { return $url; } if ($type === 'user' && $object instanceof WP_User && !hj_seo_author_should_index($object)) { return false; } if ($type === 'post' && $object instanceof WP_Post && $object->post_type === 'page') { $slug = hj_seo_page_slug((int) $object->ID); if (in_array($slug, HJ_SEO_NOINDEX_PAGE_SLUGS, true)) { return false; } } return $url; } function hj_seo_author_should_index(?WP_User $author = null): bool { if ($author === null) { if (!is_author()) { return false; } $author = get_queried_object(); if (!$author instanceof WP_User) { return false; } } if (hj_seo_is_generic_author_account($author)) { return false; } if (hj_seo_author_published_post_count((int) $author->ID) < 1) { return false; } return hj_seo_author_has_profile((int) $author->ID); } function hj_seo_is_generic_author_account(WP_User $author): bool { $display = hj_seo_clean_text($author->display_name); if (strcasecmp($display, 'Hausjournal.net') === 0) { return true; } $generic_slugs = [ 'admin', 'maybach', 'kr', 'burkhard', 'dominic', 'nathalie', 'kontaktm15internet-de', 'sabine', 'sg', ]; return in_array($author->user_nicename, $generic_slugs, true); } function hj_seo_author_has_profile(int $user_id): bool { $fields = [ 'description', 'fachgebiet', 'schreibt_fur_uns_seit', 'lieblingspflanze', 'mein_heimwerkertipp', ]; foreach ($fields as $field) { $value = trim((string) get_user_meta($user_id, $field, true)); if ($value !== '') { return true; } } return false; } function hj_seo_author_published_post_count(int $user_id): int { $counts = count_user_posts($user_id, 'post', true); return is_numeric($counts) ? (int) $counts : 0; } function hj_seo_build_author_metadesc(WP_User $author): string { $stored = trim((string) get_user_meta($author->ID, 'wpseo_metadesc', true)); if ($stored !== '') { return hj_seo_trim_description(hj_seo_clean_text($stored)); } $parts = []; $bio = hj_seo_clean_text((string) get_user_meta($author->ID, 'description', true)); if ($bio !== '') { $parts[] = $bio; } $fachgebiet = hj_seo_clean_text((string) get_user_meta($author->ID, 'fachgebiet', true)); if ($fachgebiet !== '') { $parts[] = 'Fachgebiet: ' . $fachgebiet; } if ($parts === []) { return ''; } return hj_seo_trim_description(implode(' ', $parts)); } function hj_seo_build_category_metadesc(WP_Term $term): string { $plain_description = hj_seo_clean_text((string) $term->description); if ($plain_description !== '') { return hj_seo_trim_description($plain_description); } $name = hj_seo_clean_text($term->name); $count = max(0, (int) $term->count); if ($count > 0) { return hj_seo_trim_description( sprintf( '%s: Tipps, Anleitungen und Ratgeber zu %s  praxisnah erklrt auf Hausjournal.net.', $name, $name ) ); } return hj_seo_trim_description( sprintf( '%s: Ratgeber und Infos rund ums Bauen, Renovieren und Wohnen auf Hausjournal.net.', $name ) ); } function hj_seo_build_page_metadesc(int $post_id): string { $slug = hj_seo_page_slug($post_id); $defaults = [ 'ueber-uns' => 'Lerne das Team hinter Hausjournal.net kennen  seit 2010 deine verlssliche Quelle fr Bau-, Renovierungs- und Wohntipps.', 'redaktionsprozess' => 'So entstehen unsere Ratgeber: Recherche, Experten, Redaktion und regelmige Aktualisierung fr verlssliches Bau- und Heimwerkerwissen.', 'datenschutz' => 'Datenschutzerklrung von Hausjournal.net: Informationen zur Verarbeitung personenbezogener Daten, Cookies und deinen Rechten.', ]; if (isset($defaults[$slug])) { return $defaults[$slug]; } $excerpt = hj_seo_clean_text(get_the_excerpt($post_id)); if ($excerpt !== '') { return hj_seo_trim_description($excerpt); } return ''; } function hj_seo_page_slug(int $post_id): string { $slug = (string) get_post_field('post_name', $post_id); $parent_id = (int) get_post_field('post_parent', $post_id); if ($parent_id > 0) { $parent_slug = (string) get_post_field('post_name', $parent_id); return $parent_slug !== '' ? $parent_slug . '/' . $slug : $slug; } return $slug; } function hj_seo_clean_text(string $text): string { $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $text = wp_strip_all_tags($text, true); $text = preg_replace('/\s+/u', ' ', $text) ?? ''; return trim($text); } function hj_seo_trim_description(string $text, int $max = 155): string { if (function_exists('mb_strlen') && function_exists('mb_substr')) { if (mb_strlen($text) <= $max) { return $text; } $trimmed = rtrim(mb_substr($text, 0, $max - 1)); $last_space = mb_strrpos($trimmed, ' '); if ($last_space !== false && $last_space > ($max - 40)) { $trimmed = mb_substr($trimmed, 0, $last_space); } return rtrim($trimmed, '.,;:-') . '& '; } if (strlen($text) <= $max) { return $text; } return rtrim(substr($text, 0, $max - 3)) . '...'; } Wasserleitung tropft? » Sofortmaßnahmen & Reparatur