\d+)', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'rest_page' ), 'permission_callback' => function ( $request ) { return current_user_can( 'edit_post', (int) $request['id'] ); }, 'args' => array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), ), ) ); } /** * REST-Antwort der Seitenprüfung. * * @param WP_REST_Request $request Anfrage. * @return WP_REST_Response|WP_Error */ public static function rest_page( $request ) { return rest_ensure_response( self::check_page( (int) $request['id'] ) ); } /** * Test-Check einer Box. * * @param WP_Post $post Beitrag. * @param array|null $box Box. * @return array level: ro rot, ge gelb, gr grün, hi Hinweis. */ public static function checks( $post, $box ) { $c = array(); $add = function ( $level, $code, $text ) use ( &$c ) { $c[] = array( 'level' => $level, 'code' => $code, 'text' => $text, ); }; if ( ! is_array( $box ) ) { $add( 'ro', 'no_score', __( 'No score yet.', '3task-review-box' ) ); return $c; } $gesamt = ThreeRB_Score::total( $box ); if ( null === $gesamt['value'] ) { $add( 'ro', 'no_score', __( 'No score yet.', '3task-review-box' ) ); } $typ = $box['item']['type']; $name = isset( $box['item']['name'] ) ? trim( (string) $box['item']['name'] ) : ''; if ( '' === $name && 'none' !== $typ ) { $add( 'ro', 'no_name', __( 'Name the reviewed item, otherwise Google gets no review.', '3task-review-box' ) ); } if ( in_array( $typ, array( 'local', 'organization' ), true ) ) { if ( 0 === strcasecmp( $name, trim( (string) get_bloginfo( 'name' ) ) ) ) { $add( 'ro', 'own_business', __( 'This looks like your own business. Google does not allow ratings for it, so no schema is written.', '3task-review-box' ) ); } elseif ( empty( $box['item']['foreign'] ) ) { $add( 'ro', 'own_business', __( 'Tick “I review a business that is not mine”, otherwise no schema is written.', '3task-review-box' ) ); } } $affiliate = false; foreach ( $box['links'] as $l ) { $affiliate = $affiliate || $l['affiliate']; } if ( $affiliate && ! $box['disclosure']['affiliate'] && '' === $box['disclosure']['text'] ) { $add( 'ro', 'affiliate', __( 'Affiliate links without disclosure.', '3task-review-box' ) ); } $fremd = self::foreign( $post instanceof WP_Post ? $post->ID : 0 ); if ( $fremd ) { /* translators: %s: plugin names. */ $add( 'ro', 'foreign_markup', sprintf( __( 'Other sources may add review markup to this page, so Google could see two reviews: %s', '3task-review-box' ), implode( ', ', $fremd ) ) ); } $begruendet = false; foreach ( $box['criteria'] as $k ) { $begruendet = $begruendet || '' !== $k['note'] || '' !== $k['measure']; } if ( $box['criteria'] && ( $begruendet || count( $box['criteria'] ) > 1 ) ) { $add( 'gr', 'criteria', __( 'Criteria with weights', '3task-review-box' ) ); } else { $add( 'ge', 'criteria', __( 'Only one score: criteria with reasons or measured values make a review stronger.', '3task-review-box' ) ); } if ( '' !== $box['tested'] ) { $add( 'gr', 'tested', __( 'How you tested is described', '3task-review-box' ) ); } else { $add( 'ge', 'tested', __( 'Describe how you tested.', '3task-review-box' ) ); } if ( count( $box['pros'] ) + count( $box['cons'] ) >= 2 ) { $add( 'gr', 'points', __( 'At least two strengths or weaknesses', '3task-review-box' ) ); } else { $add( 'ge', 'points', __( 'Add at least two strengths or weaknesses.', '3task-review-box' ) ); } $bild = $post instanceof WP_Post && ( has_post_thumbnail( $post ) || false !== strpos( (string) $post->post_content, 'wp-image-' ) ); if ( $bild ) { $add( 'gr', 'images', __( 'Own images in the post', '3task-review-box' ) ); } else { $add( 'ge', 'images', __( 'Add your own photos.', '3task-review-box' ) ); } if ( $box['alternatives'] ) { $add( 'gr', 'alternatives', __( 'Alternatives named', '3task-review-box' ) ); } else { $add( 'ge', 'alternatives', __( 'Name at least one alternative. Readers and Google value a comparison.', '3task-review-box' ) ); } $gewichte = wp_list_pluck( $box['criteria'], 'weight' ); if ( count( $gewichte ) > 1 && 100 !== array_sum( $gewichte ) && count( array_unique( $gewichte ) ) > 1 ) { /* translators: %d: sum of all weights. */ $add( 'hi', 'weights', sprintf( __( 'Weights add up to %d, not 100. The score is calculated proportionally.', '3task-review-box' ), array_sum( $gewichte ) ) ); } if ( 'none' === $typ || ! $box['schema'] ) { $add( 'ge', 'schema_off', __( 'Schema is off for this box: visible, but no review for Google.', '3task-review-box' ) ); } return $c; } /** * Quellen, die auf dieser Seite selbst Bewertungs-Markup schreiben können. * * @param int $post_id Beitrag (0 = nur aktive Plugins). * @return array */ public static function foreign( $post_id ) { $bekannt = array( 'wp-review/wp-review.php' => 'WP Review', 'wp-review-pro/wp-review.php' => 'WP Review Pro', 'kk-star-ratings/index.php' => 'kk Star Ratings', 'yet-another-stars-rating/yet-another-stars-rating.php' => 'YASR', 'site-reviews/site-reviews.php' => 'Site Reviews', 'schema-and-structured-data-for-wp/structured-data-for-wordpress.php' => 'Schema & Structured Data for WP', 'rate-my-post/rate-my-post.php' => 'Rate My Post', 'wp-postratings/wp-postratings.php' => 'WP-PostRatings', 'absolute-reviews/absolute-reviews.php' => 'Absolute Reviews', ); $aktiv = (array) get_option( 'active_plugins', array() ); if ( is_multisite() ) { $aktiv = array_merge( $aktiv, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) ); } $treffer = array(); foreach ( $bekannt as $datei => $name ) { if ( in_array( $datei, $aktiv, true ) ) { $treffer[] = $name; } } if ( $post_id ) { foreach ( array_keys( (array) get_post_meta( (int) $post_id ) ) as $schluessel ) { if ( 0 !== strpos( (string) $schluessel, 'rank_math_schema_' ) ) { continue; } foreach ( (array) get_post_meta( (int) $post_id, (string) $schluessel, false ) as $wert ) { if ( self::has_review( $wert ) ) { $treffer[] = 'Rank Math (review schema on this post)'; break 2; } } } } return array_values( array_unique( $treffer ) ); } /** * Zählt Bewertungs-Markup im ausgelieferten HTML: JSON-LD, Microdata und RDFa. * * @param string $html Seite. * @return array{reviews:int, aggregates:int, microdata:int, double:bool} */ public static function markup( $html ) { $html = (string) $html; $reviews = 0; $aggregat = 0; if ( preg_match_all( '#]*type=["\']application/ld\+json["\'][^>]*>(.*?)#si', $html, $treffer ) ) { foreach ( $treffer[1] as $json ) { $daten = json_decode( trim( $json ), true ); if ( is_array( $daten ) ) { self::zaehlen( $daten, $reviews, $aggregat ); } } } $micro = preg_match_all( '#itemtype=["\']https?://schema\.org/Review["\']#i', $html ) + preg_match_all( '#typeof=["\'](?:schema:)?Review["\']#i', $html ); $aggregat += preg_match_all( '#itemprop=["\']aggregateRating["\']#i', $html ); $reviews += $micro; return array( 'reviews' => $reviews, 'aggregates' => $aggregat, 'microdata' => $micro, 'double' => $reviews > 1 || $aggregat > 0, ); } /** * Ruft die eigene, veröffentlichte Seite ab und zählt das Markup. * * @param int $post_id Beitrag. * @return array|WP_Error */ public static function check_page( $post_id ) { if ( ! ThreeRB_Render::is_public( $post_id ) ) { return new WP_Error( 'threerb_not_public', __( 'Only published posts can be checked.', '3task-review-box' ), array( 'status' => 400 ) ); } $antwort = wp_safe_remote_get( get_permalink( $post_id ), array( 'timeout' => 8, 'redirection' => 2, 'limit_response_size' => 2 * MB_IN_BYTES, ) ); if ( is_wp_error( $antwort ) || 200 !== (int) wp_remote_retrieve_response_code( $antwort ) ) { return new WP_Error( 'threerb_unreachable', __( 'The page could not be loaded from the server. Check it with Google’s Rich Results Test instead.', '3task-review-box' ), array( 'status' => 502 ) ); } $ergebnis = self::markup( wp_remote_retrieve_body( $antwort ) ); $ergebnis['url'] = get_permalink( $post_id ); $ergebnis['plugins'] = self::foreign( $post_id ); return $ergebnis; } /** * Enthält ein Rank-Math-Schema eine Bewertung? * * @param mixed $wert Schema-Daten. * @return bool */ private static function has_review( $wert ) { if ( ! is_array( $wert ) ) { return false; } foreach ( $wert as $k => $v ) { if ( in_array( (string) $k, array( 'review', 'reviewRating', 'aggregateRating' ), true ) ) { return true; } if ( '@type' === $k && in_array( $v, array( 'Review', 'AggregateRating' ), true ) ) { return true; } if ( is_array( $v ) && self::has_review( $v ) ) { return true; } } return false; } /** * Review- und Aggregat-Knoten rekursiv zählen. * * @param array $daten JSON-LD. * @param int $reviews Zähler Reviews. * @param int $aggregat Zähler Aggregate. */ private static function zaehlen( array $daten, &$reviews, &$aggregat ) { $typ = isset( $daten['@type'] ) ? (array) $daten['@type'] : array(); if ( in_array( 'Review', $typ, true ) ) { ++$reviews; } if ( in_array( 'AggregateRating', $typ, true ) ) { ++$aggregat; } foreach ( $daten as $v ) { if ( is_array( $v ) ) { self::zaehlen( $v, $reviews, $aggregat ); } } } }