post_type, ThreeRB_Settings::value( 'post_types' ), true ) ) { return false; } if ( 'publish' === $post->post_status && '' === $post->post_password ) { return true; } if ( '' !== $post->post_password ) { return current_user_can( 'edit_post', $post->ID ); } return current_user_can( 'read_post', $post->ID ); } /** * Ist die Box öffentlich (für Suchmaschinen und Caches)? * * @param int $post_id Beitrag. * @return bool */ public static function is_public( $post_id ) { $post = get_post( (int) $post_id ); return $post && 'publish' === $post->post_status && '' === $post->post_password && in_array( $post->post_type, ThreeRB_Settings::value( 'post_types' ), true ); } /** * Volle oder kompakte Box. * * @param int $post_id Beitrag. * @param array $args variant (full|compact), display (bars|stars), style (theme|night). * @return string */ public static function box( $post_id, array $args = array() ) { if ( ! self::visible( $post_id ) ) { return ''; } $box = ThreeRB_Box::get( $post_id ); if ( ! ThreeRB_Box::has_rating( $box ) ) { return ''; } return self::html( (int) $post_id, $box, $args, true ); } /** * Vorschau für den Reiter Design: dieselbe Vorlage mit Beispieldaten. * * @param string $display bars, stars oder profile. * @param string $style theme oder night, leer = Einstellung. * @return string */ public static function preview( $display, $style = '' ) { $box = ThreeRB_Box::clean( array( 'item' => array( 'type' => 'product', 'sub' => __( 'Product · Tools', '3task-review-box' ), ), 'scale' => 'p10', 'step' => 0.1, 'criteria' => array( array( 'label' => __( 'Torque', '3task-review-box' ), 'weight' => 30, 'score' => 9, 'measure' => '62 Nm', ), array( 'label' => __( 'Battery', '3task-review-box' ), 'weight' => 25, 'score' => 8, 'measure' => '41 min', ), array( 'label' => __( 'Handling', '3task-review-box' ), 'weight' => 25, 'score' => 8.5, ), array( 'label' => __( 'Value for money', '3task-review-box' ), 'weight' => 20, 'score' => 7, ), ), 'pros' => array( __( 'Drives 8 mm screws without pilot holes', '3task-review-box' ) ), 'cons' => array( __( 'LED dazzles', '3task-review-box' ) ), 'tested' => __( 'Two weeks on a building site', '3task-review-box' ), ) )['box']; $box['score'] = null; $box['item']['name'] = __( 'Cordless drill X18', '3task-review-box' ); $box['summary'] = ''; $box['place'] = array( 'top' => false, 'bottom' => false, 'sidebar' => false, ); return self::html( 0, $box, array( 'display' => $display, 'style' => $style, ), false ); } /** * HTML der vollen oder kompakten Box aus Box-Daten. * * @param int $post_id Beitrag (0 in der Vorschau). * @param array $box Box. * @param array $args variant, display, style. * @param bool $template Theme-Vorlage erlauben. * @return string */ private static function html( $post_id, array $box, array $args, $template ) { self::assets(); $s = ThreeRB_Settings::get(); $display = self::wahl( isset( $args['display'] ) ? $args['display'] : '', $box['display'], $s['display'], array( 'bars', 'stars', 'profile' ) ); $style = self::wahl( isset( $args['style'] ) ? $args['style'] : '', $box['style'], $s['style'], array( 'theme', 'night' ) ); $gesamt = ThreeRB_Score::total( $box ); $klassen = array( 'trb' ); if ( 'stars' === $display ) { $klassen[] = 'trb--sterne'; } if ( 'profile' === $display ) { $klassen[] = 'trb--profil'; } if ( 'night' === $style ) { $klassen[] = 'trb--nacht'; } $farbe = self::accent( $style ); $stil = $farbe ? sprintf( '--trb-accent:%1$s;--trb-on-accent:%2$s;--trb-accent-text:%3$s', $farbe, self::on_accent( $farbe ), '' !== self::accent_text( $farbe ) ? $farbe : 'currentColor' ) : ''; $stil .= sprintf( '%1$s--trb-radius:%2$dpx;--trb-stern:%3$s', '' !== $stil ? ';' : '', (int) $s['radius'], $s['star_color'] ); $ebene = in_array( $box['heading'], array( 'h2', 'h3', 'h4' ), true ) ? $box['heading'] : $s['heading']; $name = '' !== $box['item']['name'] ? $box['item']['name'] : get_the_title( $post_id ); if ( isset( $args['variant'] ) && 'compact' === $args['variant'] ) { return self::kompakt( $box, $gesamt, $name, $klassen, $stil, $display ); } $id = 'trb-' . (int) $post_id . '-' . wp_rand( 100, 999 ); $html = sprintf( '
', esc_attr( implode( ' ', $klassen ) ), esc_attr( $stil ), esc_attr( $id . '-t' ), esc_attr( (string) wp_json_encode( self::daten( $box ) ) ) ); // Kriterien mit Note. $kriterien = array_values( array_filter( $box['criteria'], function ( $k ) { return null !== $k['score']; } ) ); // Kopf mit Notenkachel, im Profil mit Ring und Netzplan. $titel = sprintf( '<%1$s id="%2$s">%3$s', $ebene, esc_attr( $id . '-t' ), esc_html( $name ) ); if ( '' !== $box['item']['sub'] ) { $titel .= '

' . esc_html( $box['item']['sub'] ) . '

'; } if ( '' !== $box['tested'] ) { /* translators: %s: how the item was tested. */ $titel .= '

' . self::icon( 'test' ) . '' . esc_html( sprintf( __( 'How we tested: %s', '3task-review-box' ), $box['tested'] ) ) . '

'; } if ( 'profile' === $display ) { $html .= '
' . self::ring( $box, $gesamt ) . '
' . $titel . '
'; if ( count( $kriterien ) >= 3 ) { $html .= '
' . self::netz( $kriterien, $box ) . '
'; } $html .= '
'; } else { $html .= '
' . $titel . '
'; $html .= self::kachel( $box, $gesamt, $display ); $html .= '
'; } if ( $kriterien ) { $html .= '
'; foreach ( $kriterien as $k ) { $anteil = $k['score'] / ThreeRB_Score::max( $box['scale'] ); $html .= '
' . esc_html( $k['label'] ) . ''; if ( count( $kriterien ) > 1 ) { $html .= '' . esc_html( (string) $k['weight'] ) . ' %'; } $html .= ''; $html .= 'stars' === $display ? self::sterne( $anteil, $box['scale'] ) : ''; $html .= '' . esc_html( self::zahl( $k['score'], $box ) ) . ''; $html .= '' . esc_html( $k['measure'] ) . '
'; } $html .= '
'; if ( count( $kriterien ) > 1 ) { $html .= self::gewichtung( $id, $kriterien, $box ); } } if ( $gesamt['overridden'] ) { /* translators: %s: reason for the score set by hand. */ $html .= '

' . esc_html( sprintf( __( 'Total score set by the editors: %s', '3task-review-box' ), $box['override']['reason'] ) ) . '

'; } // Stark und Schwach. if ( $box['pros'] || $box['cons'] ) { $html .= '
'; $html .= '

' . esc_html__( 'Strengths', '3task-review-box' ) . '

    '; foreach ( $box['pros'] as $pro ) { $html .= '
  • ' . self::icon( 'ja' ) . '' . esc_html( $pro ) . '
  • '; } $html .= '

' . esc_html__( 'Weaknesses', '3task-review-box' ) . '

    '; foreach ( $box['cons'] as $con ) { $html .= '
  • ' . self::icon( 'nein' ) . '' . esc_html( $con ) . '
  • '; } $html .= '
'; } // Fazit, Für wen, Links. if ( '' !== $box['verdict'] || $box['good_for'] || $box['bad_for'] || $box['links'] || $box['alternatives'] ) { $html .= '
'; if ( '' !== $box['verdict'] ) { $html .= '
' . esc_html__( 'Verdict:', '3task-review-box' ) . ' ' . wp_kses( $box['verdict'], array( 'p' => array(), 'br' => array(), 'strong' => array(), 'em' => array(), 'a' => array( 'href' => true ), ) ) . '
'; } if ( $box['good_for'] || $box['bad_for'] ) { $html .= '
'; foreach ( $box['good_for'] as $x ) { /* translators: %s: audience the item suits. */ $html .= '' . self::icon( 'ja' ) . esc_html( $x ) . ''; } foreach ( $box['bad_for'] as $x ) { $html .= '' . self::icon( 'nein' ) . esc_html( $x ) . ''; } $html .= '
'; } if ( $box['alternatives'] ) { $namen = array(); foreach ( $box['alternatives'] as $alt ) { $namen[] = '' !== $alt['url'] ? sprintf( '%2$s', esc_url( $alt['url'] ), esc_html( $alt['label'] ) ) : esc_html( $alt['label'] ); } $html .= '

' . esc_html__( 'Alternatives:', '3task-review-box' ) . ' ' . implode( ', ', $namen ) . '

'; } if ( $box['links'] ) { $html .= ''; } $html .= '
'; } $fuss = self::hinweis( $box ); if ( '' !== $fuss ) { $html .= '

' . esc_html( $fuss ) . '

'; } $html .= '
'; $html = '
' . $html . '
'; return $template ? self::theme_template( $post_id, $box, $gesamt, $html ) : $html; } /** * Vorlage aus dem Theme (3task-review-box/box.php), falls vorhanden. Der Pfad kommt nur aus locate_template() * mit festem Namen, nie aus Daten. Die Vorlage bekommt $threerb_post_id, $threerb_box, $threerb_score und * $threerb_html (die Standardausgabe, schon escaped). * * @param int $post_id Beitrag. * @param array $box Box. * @param array $gesamt Ergebnis. * @param string $html Standardausgabe. * @return string */ private static function theme_template( $post_id, array $box, array $gesamt, $html ) { $datei = locate_template( array( '3task-review-box/box.php' ), false, false ); if ( '' === $datei ) { return $html; } $threerb_post_id = (int) $post_id; $threerb_box = $box; $threerb_score = array( 'value' => $gesamt['value'], 'share' => $gesamt['share'], 'best' => ThreeRB_Score::max( $box['scale'] ), 'display' => self::zahl( $gesamt['value'], $box ), 'word' => self::wort( $gesamt['share'] ), ); $threerb_html = $html; ob_start(); include $datei; return (string) ob_get_clean(); } /** * Kurzurteil mit Note für den Anfang des Beitrags (Weg C). * * @param int $post_id Beitrag. * @return string */ public static function summary( $post_id ) { if ( ! self::visible( $post_id ) ) { return ''; } $box = ThreeRB_Box::get( $post_id ); if ( ! ThreeRB_Box::has_rating( $box ) ) { return ''; } self::assets(); $gesamt = ThreeRB_Score::total( $box ); $text = '' !== $box['summary'] ? $box['summary'] : wp_html_excerpt( wp_strip_all_tags( $box['verdict'] ), 220, '…' ); $farbe = self::accent( self::wahl( '', $box['style'], ThreeRB_Settings::value( 'style' ), array( 'theme', 'night' ) ) ); $vars = $farbe ? '--trb-accent:' . $farbe . ';--trb-accent-text:' . ( '' !== self::accent_text( $farbe ) ? $farbe : 'currentColor' ) : ''; $html = '
'; $html .= '
' . esc_html( self::zahl( $gesamt['value'], $box ) ) . '' . esc_html( self::von( $box ) ) . '
'; $html .= '

' . esc_html__( 'In short:', '3task-review-box' ) . ' ' . esc_html( $text ); if ( $box['place']['bottom'] ) { $html .= ' ' . esc_html__( 'Jump to verdict', '3task-review-box' ) . ' ↓'; } $html .= '

'; return $html; } /** * Nur die Note als Plakette. * * @param int $post_id Beitrag. * @return string */ public static function score( $post_id ) { if ( ! self::visible( $post_id ) ) { return ''; } $box = ThreeRB_Box::get( $post_id ); if ( ! ThreeRB_Box::has_rating( $box ) ) { return ''; } self::assets(); $gesamt = ThreeRB_Score::total( $box ); $wort = self::wort( $gesamt['share'] ); return '' . esc_html( self::zahl( $gesamt['value'], $box ) ) . ( '' !== $wort ? ' ' . esc_html( $wort ) . '' : '' ) . ''; } /** * Rangliste: veröffentlichte Beiträge ohne Passwort, nach Gesamtnote oder nach einem Kriterium, * beste oder schwächste zuerst, als Liste, Tabelle oder Karten, wahlweise mit Seitenzahlen. * Gleiche Note bedeutet gleichen Platz (1, 2, 3, 3, 5). Verglichen wird immer der Anteil an der eigenen Skala. * * @param array $args count 3 bis 20 (je Seite), postType, term (Kategorie), tag (Schlagwort), order desc|asc, * criterion (Name eines Kriteriums), display list|table|cards, stars, paged. * @return string */ public static function toplist( array $args ) { $a = self::toplist_args( $args ); if ( '' === $a['postType'] ) { return ''; } $abfrage = array( 'post_type' => $a['postType'], 'post_status' => 'publish', 'has_password' => false, 'ignore_sticky_posts' => true, 'meta_key' => ThreeRB_Box::META_SCORE, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Sortierung über den kleinen Spiegel. 'update_post_term_cache' => false, ); $steuer = array(); if ( $a['term'] > 0 ) { $steuer[] = array( 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => $a['term'], ); } if ( $a['tag'] > 0 ) { $steuer[] = array( 'taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => $a['tag'], ); } if ( $steuer ) { $abfrage['tax_query'] = $steuer; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Filter der Rangliste. } // Alle Kandidaten mit Anteil holen (höchstens 500), sortieren, Plätze vergeben, dann die Seite schneiden. $abfrage['posts_per_page'] = 500; // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- nur IDs und Spiegel, Rangliste braucht die Plätze davor. $abfrage['fields'] = 'ids'; $abfrage['no_found_rows'] = true; $abfrage['orderby'] = array( 'meta_value_num' => 'asc' === $a['order'] ? 'ASC' : 'DESC', 'date' => 'DESC', ); $eintraege = array(); foreach ( ( new WP_Query( $abfrage ) )->posts as $pos => $id ) { $id = (int) $id; $box = ThreeRB_Box::get( $id ); if ( ! ThreeRB_Box::has_rating( $box ) || ! self::is_public( $id ) ) { continue; } $gesamt = ThreeRB_Score::total( $box ); $wert = $gesamt['value']; $anteil = $gesamt['share']; if ( '' !== $a['criterion'] ) { $treffer = null; foreach ( $box['criteria'] as $k ) { if ( null !== $k['score'] && 0 === strcasecmp( trim( $k['label'] ), $a['criterion'] ) ) { $treffer = $k; break; } } if ( null === $treffer ) { continue; } $wert = (float) $treffer['score']; $anteil = $wert / ThreeRB_Score::max( $box['scale'] ); } $eintraege[] = array( 'id' => $id, 'box' => $box, 'wert' => $wert, 'anteil' => (int) round( $anteil * 1000 ), 'pos' => $pos, ); } if ( ! $eintraege ) { return ''; } $richtung = 'asc' === $a['order'] ? 1 : -1; usort( $eintraege, function ( $x, $y ) use ( $richtung ) { if ( $x['anteil'] !== $y['anteil'] ) { return $richtung * ( $x['anteil'] <=> $y['anteil'] ); } return $x['pos'] <=> $y['pos']; } ); $platz = 0; $vor = null; foreach ( $eintraege as $i => $e ) { if ( $e['anteil'] !== $vor ) { $platz = $i + 1; $vor = $e['anteil']; } $eintraege[ $i ]['platz'] = $platz; } $seiten = (int) ceil( count( $eintraege ) / $a['count'] ); $seite = 1; if ( $a['paged'] && isset( $_GET['trb_seite'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nur Blättern in einer öffentlichen Liste. $roh = sanitize_text_field( wp_unslash( $_GET['trb_seite'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $seite = ctype_digit( $roh ) ? max( 1, min( $seiten, (int) $roh ) ) : 1; } $teil = array_slice( $eintraege, ( $seite - 1 ) * $a['count'], $a['count'] ); self::assets(); $html = self::toplist_html( $teil, $a ); if ( $a['paged'] && $seiten > 1 ) { $links = paginate_links( array( 'base' => esc_url_raw( add_query_arg( 'trb_seite', '%#%' ) ), 'format' => '', 'current' => $seite, 'total' => $seiten, 'prev_text' => __( 'Previous', '3task-review-box' ), 'next_text' => __( 'Next', '3task-review-box' ), ) ); $html .= ''; } return '
' . $html . '
'; } /** * Geprüfte Argumente der Rangliste (feste Listen, Ganzzahlen, Längen). * * @param array $args Roh. * @return array */ private static function toplist_args( array $args ) { $typen = (array) ThreeRB_Settings::value( 'post_types' ); $typ = isset( $args['postType'] ) && in_array( $args['postType'], $typen, true ) ? $args['postType'] : ( in_array( 'post', $typen, true ) ? 'post' : (string) reset( $typen ) ); $krit = isset( $args['criterion'] ) && is_scalar( $args['criterion'] ) ? mb_substr( trim( sanitize_text_field( (string) $args['criterion'] ) ), 0, 60 ) : ''; return array( 'count' => max( 3, min( 20, isset( $args['count'] ) ? (int) $args['count'] : 5 ) ), 'postType' => $typ, 'term' => isset( $args['term'] ) ? max( 0, (int) $args['term'] ) : 0, 'tag' => isset( $args['tag'] ) ? max( 0, (int) $args['tag'] ) : 0, 'order' => isset( $args['order'] ) && 'asc' === $args['order'] ? 'asc' : 'desc', 'criterion' => $krit, 'display' => isset( $args['display'] ) && in_array( $args['display'], array( 'list', 'table', 'cards' ), true ) ? $args['display'] : 'list', 'stars' => ! empty( $args['stars'] ) && ThreeRB_Settings::bool( $args['stars'] ), 'paged' => ! empty( $args['paged'] ) && ThreeRB_Settings::bool( $args['paged'] ), ); } /** * HTML der Rangliste in der gewählten Darstellung. * * @param array $teil Einträge dieser Seite. * @param array $a Argumente. * @return string */ private static function toplist_html( array $teil, array $a ) { $note = function ( $e ) use ( $a ) { $box = $e['box']; $html = '' . esc_html( self::zahl( $e['wert'], $box ) ) . ( '' !== self::von( $box ) ? ' ' . esc_html( self::von( $box ) ) . '' : '' ) . ''; if ( $a['stars'] ) { $html .= self::sterne( $e['anteil'] / 1000, $box['scale'] ); } return $html; }; $name = function ( $e ) { return '' !== $e['box']['item']['name'] ? $e['box']['item']['name'] : get_the_title( $e['id'] ); }; $kopf = '' !== $a['criterion'] /* translators: %s: criterion name. */ ? sprintf( __( 'Score for %s', '3task-review-box' ), $a['criterion'] ) : __( 'Score', '3task-review-box' ); if ( 'table' === $a['display'] ) { $html = ''; foreach ( $teil as $e ) { $html .= ''; } return $html . '
' . esc_html__( 'Rank', '3task-review-box' ) . '' . esc_html__( 'Item', '3task-review-box' ) . '' . esc_html( $kopf ) . '
' . (int) $e['platz'] . '' . esc_html( $name( $e ) ) . '' . $note( $e ) . '
'; } if ( 'cards' === $a['display'] ) { $html = '
    '; foreach ( $teil as $e ) { $box = $e['box']; $text = '' !== $box['summary'] ? $box['summary'] : wp_html_excerpt( wp_strip_all_tags( $box['verdict'] ), 160, '…' ); $bild = get_the_post_thumbnail( $e['id'], 'medium', array( 'class' => 'trb-top-bild', 'loading' => 'lazy', 'alt' => '', ) ); $html .= '
  1. ' . $bild; $html .= '
    '; /* translators: %d: rank. */ $html .= '

    ' . esc_html( sprintf( __( 'Rank %d:', '3task-review-box' ), $e['platz'] ) ) . ' ' . esc_html( $name( $e ) ) . '

    '; $html .= '

    ' . $note( $e ) . '

    '; if ( '' !== $text ) { $html .= '

    ' . esc_html( $text ) . '

    '; } $html .= '
  2. '; } return $html . '
'; } $html = '
    '; foreach ( $teil as $e ) { $wort = '' === $a['criterion'] ? self::wort( $e['anteil'] / 1000 ) : ''; $html .= '
  1. '; $html .= '' . esc_html( $name( $e ) ) . ''; $html .= '' . $note( $e ) . ''; $html .= '' !== $wort ? '' . esc_html( $wort ) . '' : '' . esc_html( $a['criterion'] ) . ''; $html .= '
  2. '; } return $html . '
'; } /** * Autoren-Rangliste: wer hat die meisten veröffentlichten Bewertungen, mit mittlerer Note (Anteil × 10). * * @param array $args count 3 bis 20, postType. * @return string */ public static function reviewers( array $args ) { $anzahl = max( 3, min( 20, isset( $args['count'] ) ? (int) $args['count'] : 5 ) ); $typen = (array) ThreeRB_Settings::value( 'post_types' ); if ( ! $typen ) { return ''; } $ids = get_posts( array( 'post_type' => $typen, 'post_status' => 'publish', 'has_password' => false, 'posts_per_page' => 2000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- nur IDs mit Spiegel, für die Zählung je Autor. 'fields' => 'ids', 'no_found_rows' => true, 'meta_key' => ThreeRB_Box::META_SCORE, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- nur Beiträge mit Bewertung. ) ); $autoren = array(); foreach ( $ids as $id ) { $autor = (int) get_post_field( 'post_author', (int) $id ); if ( $autor < 1 ) { continue; } if ( ! isset( $autoren[ $autor ] ) ) { $autoren[ $autor ] = array( 'n' => 0, 'summe' => 0, ); } ++$autoren[ $autor ]['n']; $autoren[ $autor ]['summe'] += (int) get_post_meta( (int) $id, ThreeRB_Box::META_SCORE, true ); } if ( ! $autoren ) { return ''; } uasort( $autoren, function ( $x, $y ) { return $y['n'] <=> $x['n']; } ); self::assets(); $html = '
    '; $platz = 0; foreach ( array_slice( $autoren, 0, $anzahl, true ) as $autor => $z ) { $nutzer = get_userdata( $autor ); if ( ! $nutzer ) { continue; } $mittel = number_format_i18n( $z['summe'] / $z['n'] / 100, 1 ); /* translators: %d: number of reviews. */ $text = sprintf( _n( '%d review', '%d reviews', $z['n'], '3task-review-box' ), $z['n'] ); ++$platz; $html .= '
  1. '; $html .= '' . esc_html( $nutzer->display_name ) . ''; $html .= '' . esc_html( $text ) . ''; /* translators: %s: average score out of 10. */ $html .= '' . esc_html( sprintf( __( 'average %s out of 10', '3task-review-box' ), $mittel ) ) . '
  2. '; } return $html . '
'; } /** * Zahl im Format der Seite, mit Prozentzeichen bei Prozent-Skala. * * @param float $wert Wert. * @param array $box Box. * @return string */ public static function zahl( $wert, array $box ) { if ( 'pct' === $box['scale'] ) { return number_format_i18n( $wert, 0 ) . ' %'; } return number_format_i18n( $wert, ( 1.0 === (float) $box['step'] ) ? 0 : 1 ); } /** * Wortnote in der Sprache der Seite, leer wenn abgeschaltet. * * @param float $anteil Anteil 0 bis 1. * @return string */ public static function wort( $anteil ) { if ( ! ThreeRB_Settings::value( 'words' ) ) { return ''; } $texte = array( 'outstanding' => __( 'outstanding', '3task-review-box' ), 'very_good' => __( 'very good', '3task-review-box' ), 'good' => __( 'good', '3task-review-box' ), 'satisfactory' => __( 'fair', '3task-review-box' ), 'sufficient' => __( 'sufficient', '3task-review-box' ), 'poor' => __( 'poor', '3task-review-box' ), ); /** * Texte der Wortnoten ändern. * * @param array $texte Schlüssel outstanding, very_good, good, satisfactory, sufficient, poor. */ $texte = apply_filters( 'threerb_word_labels', $texte ); $schluessel = ThreeRB_Score::word( $anteil ); return isset( $texte[ $schluessel ] ) ? (string) $texte[ $schluessel ] : ''; } /** * Hauptfarbe: Nacht aus den Einstellungen, sonst die Hauptfarbe aus theme.json, sonst leer (CSS-Vorgabe). * * @param string $style theme oder night. * @return string Farbe #rrggbb oder leer. */ public static function accent( $style ) { if ( 'night' === $style ) { return (string) ThreeRB_Settings::value( 'night_accent' ); } $eigen = (string) ThreeRB_Settings::value( 'accent' ); if ( '' !== $eigen ) { return $eigen; } if ( function_exists( 'wp_get_global_settings' ) ) { $palette = wp_get_global_settings( array( 'color', 'palette', 'theme' ) ); if ( is_array( $palette ) ) { foreach ( array( 'primary', 'accent', 'accent-1', 'contrast-2' ) as $slug ) { foreach ( $palette as $farbe ) { if ( isset( $farbe['slug'], $farbe['color'] ) && $slug === $farbe['slug'] && preg_match( '/^#[0-9a-fA-F]{6}$/', $farbe['color'] ) ) { return strtolower( $farbe['color'] ); } } } } } return ''; } /** * Schriftfarbe auf der Hauptfarbe: weiß oder dunkel, je nachdem was mehr Kontrast hat (WCAG). * * @param string $farbe #rrggbb. * @return string */ public static function on_accent( $farbe ) { $dunkel = '#0d0f1a'; return self::kontrast( '#ffffff', $farbe ) >= self::kontrast( $dunkel, $farbe ) ? '#ffffff' : $dunkel; } /** * Hauptfarbe als Schriftfarbe auf hellem Grund, nur wenn sie dort lesbar ist (WCAG 4,5:1), sonst leer. * * @param string $farbe #rrggbb. * @return string */ public static function accent_text( $farbe ) { return ( '' !== $farbe && self::kontrast( $farbe, '#ffffff' ) >= 4.5 ) ? $farbe : ''; } /** * Kontrastverhältnis zweier Farben nach WCAG 2. * * @param string $a #rrggbb. * @param string $b #rrggbb. * @return float */ public static function kontrast( $a, $b ) { $la = self::luminanz( $a ); $lb = self::luminanz( $b ); return round( ( max( $la, $lb ) + 0.05 ) / ( min( $la, $lb ) + 0.05 ), 2 ); } /** * Relative Luminanz. * * @param string $farbe #rrggbb. * @return float */ private static function luminanz( $farbe ) { $teile = array( substr( $farbe, 1, 2 ), substr( $farbe, 3, 2 ), substr( $farbe, 5, 2 ) ); $werte = array(); foreach ( $teile as $hex ) { $c = hexdec( $hex ) / 255; $werte[] = $c <= 0.03928 ? $c / 12.92 : pow( ( $c + 0.055 ) / 1.055, 2.4 ); } return 0.2126 * $werte[0] + 0.7152 * $werte[1] + 0.0722 * $werte[2]; } /** * CSS und Skript einreihen. */ public static function assets() { wp_enqueue_style( 'threerb-box' ); wp_enqueue_script( 'threerb-weigh' ); } /** * Notenkachel. * * @param array $box Box. * @param array $gesamt Ergebnis von total(). * @param string $display bars oder stars. * @return string */ private static function kachel( array $box, array $gesamt, $display ) { $wort = self::wort( $gesamt['share'] ); $zahl = self::zahl( $gesamt['value'], $box ); $label = '' !== $wort /* translators: 1: score, 2: scale, 3: word rating. */ ? sprintf( __( 'Editor score %1$s %2$s, %3$s', '3task-review-box' ), $zahl, self::von( $box ), $wort ) /* translators: 1: score, 2: scale. */ : sprintf( __( 'Editor score %1$s %2$s', '3task-review-box' ), $zahl, self::von( $box ) ); $html = ''; return $html; } /** * Kompakte Box für die Stelle im Text. * * @param array $box Box. * @param array $gesamt Ergebnis. * @param string $name Name. * @param array $klassen Klassen. * @param string $stil Inline-Variablen. * @param string $display Darstellung. * @return string */ private static function kompakt( array $box, array $gesamt, $name, array $klassen, $stil, $display ) { $klassen[] = 'trb-kompakt'; $wort = self::wort( $gesamt['share'] ); $text = '' !== $box['summary'] ? $box['summary'] : wp_html_excerpt( wp_strip_all_tags( $box['verdict'] ), 160, '…' ); $html = '
'; $html .= '
' . esc_html( self::zahl( $gesamt['value'], $box ) ) . ''; if ( 'stars' === $display ) { $html .= self::sterne( $gesamt['share'], $box['scale'] ); } $html .= '' . esc_html( self::von( $box ) ) . '
'; $html .= '
' . esc_html( $name ) . ( '' !== $wort ? ' · ' . esc_html( $wort ) : '' ) . ''; if ( '' !== $text ) { $html .= ' ' . esc_html( $text ) . ''; } if ( $box['place']['bottom'] ) { $html .= ' ' . esc_html__( 'Full verdict', '3task-review-box' ) . ' ↓'; } $html .= '
'; return '
' . $html . '
'; } /** * „Deine Gewichtung“: Knopf und Regler, das Skript rechnet im Browser. * * @param string $id Box-ID. * @param array $kriterien Kriterien. * @param array $box Box. * @return string */ private static function gewichtung( $id, array $kriterien, array $box ) { $html = '
'; $html .= '
'; return $html; } /** * Daten für das Skript. * * @param array $box Box. * @return array */ private static function daten( array $box ) { $c = array(); foreach ( $box['criteria'] as $k ) { if ( null !== $k['score'] ) { $c[] = array( (int) $k['weight'], (float) $k['score'] ); } } return array( 'c' => $c, 'max' => ThreeRB_Score::max( $box['scale'] ), 'pct' => 'pct' === $box['scale'], 'dec' => ( 1.0 === (float) $box['step'] || 'pct' === $box['scale'] ) ? 0 : 1, 'sep' => self::dezimal(), ); } /** * Dezimaltrennzeichen der Seite. * * @return string */ private static function dezimal() { global $wp_locale; return ( isset( $wp_locale->number_format['decimal_point'] ) && '' !== $wp_locale->number_format['decimal_point'] ) ? $wp_locale->number_format['decimal_point'] : '.'; } /** * „von 10“, „von 5“ oder leer bei Prozent. * * @param array $box Box. * @return string */ private static function von( array $box ) { if ( 'pct' === $box['scale'] ) { return ''; } /* translators: %d: maximum of the scale, e.g. 10. */ return sprintf( __( 'out of %d', '3task-review-box' ), ThreeRB_Score::max( $box['scale'] ) ); } /** * Notenring für den Look „Profil“. Die Zahl steht als Text darin, der Ring ist Schmuck. * * @param array $box Box. * @param array $gesamt Ergebnis. * @return string */ private static function ring( array $box, array $gesamt ) { $umfang = 2 * M_PI * 42; $wort = self::wort( $gesamt['share'] ); $zahl = self::zahl( $gesamt['value'], $box ); $label = '' !== $wort /* translators: 1: score, 2: scale, 3: word for the score. */ ? sprintf( __( 'Editor score %1$s %2$s, %3$s', '3task-review-box' ), $zahl, self::von( $box ), $wort ) /* translators: 1: score, 2: scale. */ : sprintf( __( 'Editor score %1$s %2$s', '3task-review-box' ), $zahl, self::von( $box ) ); $html = ''; return $html; } /** * Netzplan als SVG, serverseitig, mit Textalternative aus Namen und Noten. * * @param array $kriterien Kriterien mit Note (mindestens drei). * @param array $box Box. * @return string */ private static function netz( array $kriterien, array $box ) { $n = count( $kriterien ); $mitte = 75; $radius = 52; $max = ThreeRB_Score::max( $box['scale'] ); $punkt = function ( $i, $faktor ) use ( $n, $mitte, $radius ) { $winkel = -M_PI / 2 + $i * 2 * M_PI / $n; return array( $mitte + cos( $winkel ) * $radius * $faktor, $mitte + sin( $winkel ) * $radius * $faktor ); }; $fmt = function ( $wert ) { return number_format( $wert, 1, '.', '' ); }; $netz = function ( $faktor ) use ( $n, $punkt, $fmt ) { $p = array(); for ( $i = 0; $i < $n; $i++ ) { list( $x, $y ) = $punkt( $i, $faktor ); $p[] = $fmt( $x ) . ',' . $fmt( $y ); } return ''; }; $achsen = ''; $namen = ''; $form = array(); $alt = array(); foreach ( $kriterien as $i => $k ) { list( $x, $y ) = $punkt( $i, 1 ); $achsen .= ''; list( $fx, $fy ) = $punkt( $i, max( 0, min( 1, $k['score'] / $max ) ) ); $form[] = $fmt( $fx ) . ',' . $fmt( $fy ); $winkel = -M_PI / 2 + $i * 2 * M_PI / $n; $anker = abs( cos( $winkel ) ) < .3 ? 'middle' : ( cos( $winkel ) > 0 ? 'start' : 'end' ); $lx = $mitte + cos( $winkel ) * ( $radius + 12 ) + ( 'start' === $anker ? -6 : ( 'end' === $anker ? 6 : 0 ) ); $ly = $mitte + sin( $winkel ) * ( $radius + 12 ) + 4; $namen .= '' . esc_html( mb_strlen( $k['label'] ) > 18 ? mb_substr( $k['label'], 0, 17 ) . '…' : $k['label'] ) . ''; $alt[] = $k['label'] . ' ' . self::zahl( $k['score'], $box ); } /* translators: %s: list of criteria with scores. */ $label = sprintf( __( 'Profile chart: %s', '3task-review-box' ), implode( ', ', $alt ) ); return '' . $netz( 1 ) . $netz( .5 ) . $achsen . '' . $namen . ''; } /** * Sternreihe, fein gefüllt. Die Zahl steht immer daneben. * * @param float $anteil Anteil 0 bis 1. * @param string $scale Skala. * @return string */ private static function sterne( $anteil, $scale ) { $zehn = 's10' === $scale; if ( ThreeRB_Settings::value( 'half_stars' ) ) { $anzahl = $zehn ? 10 : 5; $anteil = round( $anteil * $anzahl * 2 ) / ( $anzahl * 2 ); } return ''; } /** * Kennzeichnung unter der Box. * * @param array $box Box. * @return string */ private static function hinweis( array $box ) { $h = $box['disclosure']; $saetze = array(); if ( $h['loan'] ) { $saetze[] = __( 'Review unit on loan from the manufacturer. Our verdict is independent.', '3task-review-box' ); } if ( $h['gift'] ) { $saetze[] = __( 'Review unit provided by the manufacturer and kept by us.', '3task-review-box' ); } if ( $h['paid'] ) { $saetze[] = __( 'Paid partnership. The verdict is our own.', '3task-review-box' ); } if ( $h['affiliate'] ) { $saetze[] = __( 'Buy links are affiliate links: we may earn a commission at no extra cost to you.', '3task-review-box' ); } if ( '' !== $h['text'] ) { $saetze[] = $h['text']; } return implode( ' ', $saetze ); } /** * Erste nicht leere Wahl aus Aufruf, Box und Einstellung. * * @param string $aufruf Wert aus Shortcode/Block. * @param string $box Wert der Box. * @param string $einstellung Wert der Einstellungen. * @param array $erlaubt Erlaubte Werte. * @return string */ private static function wahl( $aufruf, $box, $einstellung, array $erlaubt ) { foreach ( array( $aufruf, $box, $einstellung ) as $wert ) { if ( in_array( $wert, $erlaubt, true ) ) { return $wert; } } return $erlaubt[0]; } /** * Inline-SVG (Tabler Icons, MIT), dekorativ. * * @param string $name Icon. * @return string */ private static function icon( $name ) { $pfade = array( 'ja' => '', 'nein' => '', 'test' => '', 'extern' => '', 'regler' => '', ); if ( ! isset( $pfade[ $name ] ) ) { return ''; } return ''; } }