schema.org-Typ (Google: Review-Snippet, Stand 08.09.2026). * * @return array */ public static function types() { return array( 'product' => 'Product', 'movie' => 'Movie', 'series' => 'TVSeries', 'season' => 'CreativeWorkSeason', 'episode' => 'Episode', 'game' => 'VideoGame', 'software' => 'SoftwareApplication', 'book' => 'Book', 'recipe' => 'Recipe', 'course' => 'Course', 'event' => 'Event', 'music' => 'MusicRecording', 'playlist' => 'MusicPlaylist', 'local' => 'LocalBusiness', 'organization' => 'Organization', ); } /** * Baut den Knoten mit dem bewerteten Objekt und verschachteltem Review, oder null. * * Nie ein aggregateRating aus der eigenen Note. worstRating wird immer gesetzt. * * @param array $box Box-Daten. * @param array $ctx Kontext: date (Y-m-d), site_name, author (type, name, url). * @return array|null */ public static function build( array $box, array $ctx ) { if ( isset( $box['schema'] ) && false === $box['schema'] ) { return null; } $item = isset( $box['item'] ) && is_array( $box['item'] ) ? $box['item'] : array(); $typen = self::types(); $typ = isset( $item['type'] ) ? (string) $item['type'] : ''; $name = isset( $item['name'] ) ? trim( (string) $item['name'] ) : ''; if ( ! isset( $typen[ $typ ] ) || '' === $name ) { return null; } if ( in_array( $typ, array( 'local', 'organization' ), true ) ) { $seite = isset( $ctx['site_name'] ) ? trim( (string) $ctx['site_name'] ) : ''; if ( empty( $item['foreign'] ) || 0 === strcasecmp( $name, $seite ) ) { return null; } } if ( 'event' === $typ && empty( $item['date'] ) ) { return null; } $gesamt = ThreeRB_Score::total( $box ); if ( null === $gesamt['value'] ) { return null; } $knoten = array( '@context' => 'https://schema.org', '@type' => $typen[ $typ ], 'name' => $name, ); if ( 'event' === $typ ) { $knoten['startDate'] = (string) $item['date']; } if ( ! empty( $item['url'] ) ) { $knoten['sameAs'] = (string) $item['url']; } $review = array( '@type' => 'Review', 'author' => self::autor( isset( $ctx['author'] ) ? $ctx['author'] : array() ), 'reviewRating' => array( '@type' => 'Rating', 'ratingValue' => $gesamt['value'], 'bestRating' => ThreeRB_Score::max( $box['scale'] ), 'worstRating' => 0, ), ); if ( ! empty( $ctx['date'] ) ) { $review['datePublished'] = (string) $ctx['date']; } $text = isset( $box['summary'] ) ? trim( (string) $box['summary'] ) : ''; if ( '' !== $text ) { $review['reviewBody'] = $text; } // Pro/Contra nur bei Redaktionstests von Produkten, insgesamt mindestens zwei Aussagen, nicht für Verkäufer. $pros = self::aussagen( isset( $box['pros'] ) ? $box['pros'] : array() ); $cons = self::aussagen( isset( $box['cons'] ) ? $box['cons'] : array() ); if ( 'product' === $typ && empty( $item['seller'] ) && count( $pros ) + count( $cons ) >= 2 ) { if ( $pros ) { $review['positiveNotes'] = self::liste( $pros ); } if ( $cons ) { $review['negativeNotes'] = self::liste( $cons ); } } $knoten['review'] = $review; return $knoten; } /** * JSON für ein script-Element, ohne Möglichkeit, aus dem Element auszubrechen. * * @param array $knoten Schema-Knoten. * @return string */ public static function encode( array $knoten ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Klasse läuft auch ohne WordPress (Prüflauf S3), Flags wie wp_json_encode plus HEX-Schutz. return (string) json_encode( $knoten, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); } /** * Autor als Person oder Organisation, nur Name und Adresse. * * @param array $autor type, name, url. * @return array */ private static function autor( array $autor ) { $typ = ( isset( $autor['type'] ) && 'Organization' === $autor['type'] ) ? 'Organization' : 'Person'; $ergebnis = array( '@type' => $typ, 'name' => isset( $autor['name'] ) ? (string) $autor['name'] : '', ); if ( ! empty( $autor['url'] ) ) { $ergebnis['url'] = (string) $autor['url']; } return $ergebnis; } /** * Nicht leere Aussagen. * * @param mixed $liste Liste. * @return array */ private static function aussagen( $liste ) { if ( ! is_array( $liste ) ) { return array(); } $ergebnis = array(); foreach ( $liste as $eintrag ) { $eintrag = trim( (string) $eintrag ); if ( '' !== $eintrag ) { $ergebnis[] = $eintrag; } } return $ergebnis; } /** * ItemList aus Aussagen. * * @param array $aussagen Aussagen. * @return array */ private static function liste( array $aussagen ) { $elemente = array(); foreach ( $aussagen as $i => $aussage ) { $elemente[] = array( '@type' => 'ListItem', 'position' => $i + 1, 'name' => $aussage, ); } return array( '@type' => 'ItemList', 'itemListElement' => $elemente, ); } }