' . esc_html__( 'Settings', '3task-review-box' ) . '' ); return $links; } /** * Option mit fester Feldliste registrieren. */ public static function settings() { register_setting( 'threerb', ThreeRB_Settings::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( __CLASS__, 'sanitize' ), 'default' => ThreeRB_Settings::defaults(), ) ); } /** * Zwei Formulare, eine Option: jedes Formular ändert nur seine eigenen Felder. * * @param mixed $eingabe Formular. * @return array */ public static function sanitize( $eingabe ) { $eingabe = is_array( $eingabe ) ? $eingabe : array(); $form = isset( $eingabe['_threerb_form'] ) ? (string) $eingabe['_threerb_form'] : ''; $felder = array( 'design' => array( 'display', 'style', 'night_accent', 'accent', 'star_color', 'radius', 'heading', 'words', 'half_stars' ), 'settings' => array( 'post_types', 'position', 'author', 'summary_top', 'scale', 'step', 'delete_data' ), ); if ( ! isset( $felder[ $form ] ) ) { return ThreeRB_Settings::clean( $eingabe ); } $neu = ThreeRB_Settings::get(); foreach ( $felder[ $form ] as $feld ) { if ( array_key_exists( $feld, $eingabe ) ) { $neu[ $feld ] = $eingabe[ $feld ]; } } if ( 'settings' === $form && isset( $eingabe['post_types_sent'] ) && ! isset( $eingabe['post_types'] ) ) { $neu['post_types'] = array(); } return ThreeRB_Settings::clean( $neu ); } /** * Spalte „Score“ in den Listen der gewählten Beitragstypen. */ public static function columns() { foreach ( ThreeRB_Settings::value( 'post_types' ) as $typ ) { add_filter( "manage_{$typ}_posts_columns", array( __CLASS__, 'column_head' ) ); add_action( "manage_{$typ}_posts_custom_column", array( __CLASS__, 'column_cell' ), 10, 2 ); } } /** * Spaltenkopf. * * @param array $spalten Spalten. * @return array */ public static function column_head( $spalten ) { $spalten['threerb'] = __( 'Score', '3task-review-box' ); return $spalten; } /** * Spalteninhalt. * * @param string $spalte Spalte. * @param int $post_id Beitrag. */ public static function column_cell( $spalte, $post_id ) { if ( 'threerb' !== $spalte ) { return; } $box = ThreeRB_Box::get( $post_id ); if ( ! ThreeRB_Box::has_rating( $box ) ) { echo '–'; return; } $gesamt = ThreeRB_Score::total( $box ); echo '' . esc_html( ThreeRB_Render::zahl( $gesamt['value'], $box ) ) . ''; } /** * CSS und Skript nur auf der eigenen Seite. * * @param string $hook Seite. */ public static function assets( $hook ) { if ( 'toplevel_page_threerb' !== $hook ) { return; } $v = function ( $datei ) { return THREERB_VERSION . '.' . (int) filemtime( THREERB_DIR . $datei ); }; wp_enqueue_style( 'threerb-admin', THREERB_URL . 'assets/css/admin.css', array(), $v( 'assets/css/admin.css' ) ); wp_enqueue_style( 'threerb-box' ); wp_enqueue_script( 'threerb-admin', THREERB_URL . 'assets/js/admin.js', array( 'wp-api-fetch', 'wp-i18n' ), $v( 'assets/js/admin.js' ), true ); wp_set_script_translations( 'threerb-admin', '3task-review-box' ); wp_add_inline_script( 'threerb-admin', 'window.threerbAdmin=' . wp_json_encode( array( 'csv' => wp_nonce_url( admin_url( 'admin-post.php?action=threerb_dryrun_csv' ), 'threerb_dryrun_csv' ) ) ) . ';', 'before' ); } /** * Adresse eines Reiters. * * @param string $tab Reiter. * @return string */ public static function url( $tab ) { return add_query_arg( array( 'page' => 'threerb', 'tab' => $tab, ), admin_url( 'admin.php' ) ); } /** * Die Seite mit Reitern. */ public static function page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $reiter = array( 'overview' => __( 'Overview', '3task-review-box' ), 'boxes' => __( 'Boxes', '3task-review-box' ), 'migrate' => __( 'Move from WP Review', '3task-review-box' ), 'templates' => __( 'Templates', '3task-review-box' ), 'design' => __( 'Design', '3task-review-box' ), 'settings' => __( 'Settings', '3task-review-box' ), ); $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'overview'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nur Anzeige. if ( ! isset( $reiter[ $tab ] ) ) { $tab = 'overview'; } echo '
'; echo '

' . esc_html__( '3task Review Box', '3task-review-box' ) . '

'; /* translators: %s: version. */ echo '

' . esc_html( sprintf( __( 'Version %s · everything runs on your server, no calls to other services', '3task-review-box' ), THREERB_VERSION ) ) . '

'; echo '
'; settings_errors(); echo ThreeRB_Thanks::card( $tab ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Karte escaped jeden Wert selbst. call_user_func( array( __CLASS__, 'tab_' . $tab ) ); echo '
'; } /** * Reiter Übersicht: Kennzahlen, Sicherheits-Cockpit, Schema-Lage, Reparatur. */ private static function tab_overview() { $daten = self::boxes( 500 ); $anz = count( $daten ); $gut = 0; $gelb = 0; $aff = 0; $summe = 0.0; $lage = array(); $typen = self::type_labels(); foreach ( $daten as $d ) { $summe += (float) $d['share']; $gelb += $d['yellow'] > 0 ? 1 : 0; $aff += in_array( 'affiliate', $d['codes'], true ) ? 1 : 0; $typ = $d['type']; if ( ! isset( $lage[ $typ ] ) ) { $lage[ $typ ] = array( 'n' => 0, 'valid' => 0, ); } ++$lage[ $typ ]['n']; if ( $d['schema'] ) { ++$gut; ++$lage[ $typ ]['valid']; } } $schnitt = $anz ? number_format_i18n( $summe / $anz * 10, 1 ) : '–'; echo '
'; self::kpi( (string) $anz, __( 'boxes', '3task-review-box' ) ); self::kpi( (string) $gut, __( 'with valid schema', '3task-review-box' ) ); self::kpi( (string) $gelb, __( 'with yellow check points', '3task-review-box' ) ); self::kpi( $schnitt, __( 'average score out of 10', '3task-review-box' ) ); echo '
'; $fremd = ThreeRB_Diagnose::foreign( 0 ); $alle = (int) wp_count_posts( 'post' )->publish + (int) wp_count_posts( 'page' )->publish; echo '

' . esc_html__( 'Security cockpit', '3task-review-box' ) . '

' . esc_html__( 'The same checks run as tests under Tools › Site Health.', '3task-review-box' ) . '

'; echo '

' . esc_html__( 'Schema status', '3task-review-box' ) . '

'; if ( ! $lage ) { echo ''; } foreach ( $lage as $typ => $z ) { printf( '', esc_html( isset( $typen[ $typ ] ) ? $typen[ $typ ] : $typ ), (int) $z['n'], (int) $z['valid'] ); } echo '
' . esc_html__( 'Type', '3task-review-box' ) . '' . esc_html__( 'Boxes', '3task-review-box' ) . '' . esc_html__( 'Valid schema', '3task-review-box' ) . '
' . esc_html__( 'No ratings yet.', '3task-review-box' ) . '
%1$s%2$d%3$d
'; echo '

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

' . esc_html__( 'Recalculates the scores used for lists, sorting and the score column. Your ratings themselves are never changed.', '3task-review-box' ) . '

'; echo '

'; echo '

' . esc_html__( 'Check a page', '3task-review-box' ) . '

' . esc_html__( 'Loads a published page from your own server and counts the review markup in it. Two reviews or a star summary from another plugin are reported.', '3task-review-box' ) . '

'; echo '

'; } /** * Reiter Boxen: alle Beiträge mit Bewertung. */ private static function tab_boxes() { $daten = self::boxes( 500 ); $typen = self::type_labels(); echo ''; if ( ! $daten ) { echo ''; } foreach ( $daten as $d ) { if ( $d['red'] ) { /* translators: %d: number of red check points. */ $stand = '' . esc_html( sprintf( _n( '%d red', '%d red', $d['red'], '3task-review-box' ), $d['red'] ) ); } elseif ( $d['yellow'] ) { /* translators: %d: number of yellow check points. */ $stand = '' . esc_html( sprintf( _n( '%d yellow', '%d yellow', $d['yellow'], '3task-review-box' ), $d['yellow'] ) ); } else { $stand = '' . esc_html__( 'all green', '3task-review-box' ); } printf( '', esc_url( (string) get_edit_post_link( $d['id'] ) ), esc_html( $d['title'] ), 'publish' === $d['status'] ? '' : ' (' . esc_html( $d['status'] ) . ')', esc_html( $d['display'] ), esc_html( isset( $typen[ $d['type'] ] ) ? $typen[ $d['type'] ] : $d['type'] ), $stand, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oben aus esc_html() zusammengesetzt. esc_html( get_the_modified_date( '', $d['id'] ) ) ); } echo '
' . esc_html__( 'Post', '3task-review-box' ) . '' . esc_html__( 'Score', '3task-review-box' ) . '' . esc_html__( 'Type', '3task-review-box' ) . '' . esc_html__( 'Check', '3task-review-box' ) . '' . esc_html__( 'Modified', '3task-review-box' ) . '
' . esc_html__( 'No ratings yet. Open a post and click the star at the top of the editor.', '3task-review-box' ) . '
%2$s%3$s%4$s%5$s%6$s%7$s
'; } /** * Reiter Umzug aus WP Review. */ private static function tab_migrate() { $scan = ThreeRB_Migrate::scan(); $stufe = array( /* translators: %d: number of posts. */ array( __( 'Find', '3task-review-box' ), sprintf( _n( 'WP Review: %d post with a rating', 'WP Review: %d posts with a rating', $scan['total'], '3task-review-box' ), $scan['total'] ) ), array( __( 'Dry run', '3task-review-box' ), __( 'Preview old against new, nothing is written', '3task-review-box' ) ), array( __( 'Move', '3task-review-box' ), __( 'In batches of 50, can be resumed', '3task-review-box' ) ), array( __( 'Undo', '3task-review-box' ), __( 'Any time, based on the log', '3task-review-box' ) ), ); echo '
    '; foreach ( $stufe as $i => $s ) { printf( '
  1. %2$d %3$s%4$s
  2. ', 0 === $i ? ' fertig' : '', (int) $i + 1, esc_html( $s[0] ), esc_html( $s[1] ) ); } echo '
'; echo '

' . esc_html__( 'Important:', '3task-review-box' ) . ' ' . esc_html__( 'Only deactivate WP Review, do not delete it while you may need to undo. Its own uninstall routine can remove the old data. 3task Review Box never changes WP Review data.', '3task-review-box' ) . '

'; if ( $scan['wp_review_active'] ) { echo '

' . esc_html__( 'WP Review is still active. Move first, check the posts, then deactivate WP Review. Until then both plugins show a box.', '3task-review-box' ) . '

'; } if ( 0 === $scan['total'] ) { echo '

' . esc_html__( 'No posts with WP Review data were found. Nothing to move.', '3task-review-box' ) . '

'; return; } /* translators: 1: posts ready to move, 2: all posts found. */ echo '

' . esc_html( sprintf( __( '%1$d of %2$d posts are ready to move. Posts that already have a 3task rating are skipped.', '3task-review-box' ), $scan['ready'], $scan['total'] ) ) . '

'; echo '

'; echo '' . esc_html__( 'Save dry run as list (CSV)', '3task-review-box' ) . ' '; /* translators: %d: number of posts. */ echo ' '; echo '

'; echo '

'; echo ''; } /** * Reiter Vorlagen. */ private static function tab_templates() { $typen = self::type_labels(); echo '

' . esc_html__( 'A template fills in the type, scale and criteria of a new rating. Pick it in the rating box below the editor.', '3task-review-box' ) . '

'; echo ''; foreach ( ThreeRB_Templates::all() as $v ) { $k = array(); foreach ( $v['criteria'] as $c ) { $k[] = $c['label'] . ' ' . $c['weight'] . ' %'; } printf( '', esc_html( $v['name'] ), $v['builtin'] ? ' (' . esc_html__( 'included', '3task-review-box' ) . ')' : '', esc_html( isset( $typen[ $v['type'] ] ) ? $typen[ $v['type'] ] : $v['type'] ), esc_html( implode( ' · ', $k ) ), $v['builtin'] ? '' : '' ); } echo '
' . esc_html__( 'Template', '3task-review-box' ) . '' . esc_html__( 'Type', '3task-review-box' ) . '' . esc_html__( 'Criteria and weights', '3task-review-box' ) . '' . esc_html__( 'Actions', '3task-review-box' ) . '
%1$s%2$s%3$s%4$s%5$s
'; echo '

' . esc_html__( 'New template', '3task-review-box' ) . '

'; echo '

'; echo '

'; echo '

'; echo '

'; } /** * Reiter Design mit Live-Vorschau. */ private static function tab_design() { $s = ThreeRB_Settings::get(); $n = ThreeRB_Settings::OPTION; echo '
'; settings_fields( 'threerb' ); printf( '', esc_attr( $n ) ); echo ''; self::select( $n . '[display]', __( 'Look', '3task-review-box' ), $s['display'], array( 'bars' => __( 'Bars', '3task-review-box' ), 'stars' => __( 'Stars', '3task-review-box' ), 'profile' => __( 'Profile (score ring and chart)', '3task-review-box' ), ) ); self::select( $n . '[style]', __( 'Style', '3task-review-box' ), $s['style'], array( 'theme' => __( 'Theme colours', '3task-review-box' ), 'night' => __( 'Night', '3task-review-box' ), ) ); self::select( $n . '[night_accent]', __( 'Night colour', '3task-review-box' ), $s['night_accent'], array( '#8b7cf6' => __( 'Violet', '3task-review-box' ), '#5fb3f9' => __( 'Ice blue', '3task-review-box' ), '#e3b341' => __( 'Brass', '3task-review-box' ), ) ); printf( '', esc_html__( 'Main colour', '3task-review-box' ), esc_attr( $n ), checked( '' !== $s['accent'], true, false ), esc_html__( 'Own colour instead of the theme colour', '3task-review-box' ), esc_attr( '' !== $s['accent'] ? $s['accent'] : '#2563eb' ), '' !== $s['accent'] ? '' : 'disabled', esc_html__( 'Contrast is checked for every colour: if white text would be hard to read, the score tile uses dark text.', '3task-review-box' ) ); printf( '', esc_html__( 'Star colour', '3task-review-box' ), esc_attr( $n ), esc_attr( $s['star_color'] ) ); printf( '', esc_html__( 'Rounded corners', '3task-review-box' ), esc_attr( $n ), (int) $s['radius'] ); self::select( $n . '[heading]', __( 'Heading of the box', '3task-review-box' ), $s['heading'], array( 'h2' => 'H2', 'h3' => 'H3', 'h4' => 'H4', ) ); self::schalter( $n, 'words', $s['words'], __( 'Word rating', '3task-review-box' ), __( 'Show “very good”, “good” and so on next to the score', '3task-review-box' ) ); self::schalter( $n, 'half_stars', $s['half_stars'], __( 'Stars', '3task-review-box' ), __( 'Round stars to half stars (the number stays exact)', '3task-review-box' ) ); echo ''; submit_button(); echo '

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

'; foreach ( array( 'bars', 'stars', 'profile' ) as $d ) { echo '
' . ThreeRB_Render::preview( $d ) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Box-Vorlage escaped jeden Wert selbst. } echo '

' . esc_html__( 'The preview uses the real box template. Fonts come from your theme, which the admin area does not load.', '3task-review-box' ) . '

'; } /** * Reiter Einstellungen. */ private static function tab_settings() { $s = ThreeRB_Settings::get(); $n = ThreeRB_Settings::OPTION; echo '
'; settings_fields( 'threerb' ); printf( '', esc_attr( $n ) ); echo ''; echo ''; self::select( $n . '[position]', __( 'Position of the box', '3task-review-box' ), $s['position'], array( 'bottom' => __( 'At the end automatically', '3task-review-box' ), 'manual' => __( 'Only where I place the block or shortcode', '3task-review-box' ), ) ); self::select( $n . '[author]', __( 'Author in the review schema', '3task-review-box' ), $s['author'], array( 'person' => __( 'Post author as person', '3task-review-box' ), 'organization' => __( 'Site name as organization', '3task-review-box' ), ) ); self::schalter( $n, 'summary_top', $s['summary_top'], __( 'Short verdict at the top', '3task-review-box' ), __( 'New ratings show the short verdict at the start of the post', '3task-review-box' ) ); self::select( $n . '[scale]', __( 'Default scale for new ratings', '3task-review-box' ), $s['scale'], self::scale_labels() ); self::select( $n . '[step]', __( 'Steps', '3task-review-box' ), (string) $s['step'], array( '0.1' => '0.1', '0.5' => '0.5', '1' => '1', ) ); self::schalter( $n, 'delete_data', $s['delete_data'], __( 'Deleting the plugin', '3task-review-box' ), __( 'Also delete all ratings when the plugin is deleted', '3task-review-box' ), __( 'Off by default: deactivating or deleting the plugin keeps your ratings.', '3task-review-box' ) ); echo ''; submit_button(); echo '

' . esc_html__( 'There are no more switches. Everything else is set per box.', '3task-review-box' ) . '

'; } /** * Download: Probelauf als CSV. Recht, dann Nonce. Formeln werden entschärft. */ public static function download_csv() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to do that.', '3task-review-box' ), 403 ); } check_admin_referer( 'threerb_dryrun_csv' ); $items = array(); $offset = 0; do { $probe = ThreeRB_Migrate::dry_run( $offset, 50 ); $items = array_merge( $items, $probe['items'] ); $offset += 50; } while ( $offset < $probe['total'] ); nocache_headers(); header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=wp-review-dry-run.csv' ); echo self::csv( $items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV-Datei, Zellen sind gequotet und gegen Formeln geschützt. exit; } /** * CSV aus Probelauf-Einträgen. Zellen, die mit = + - @ beginnen, bekommen ein Hochkomma. * * @param array $items Einträge aus ThreeRB_Migrate::dry_run(). * @return string */ public static function csv( array $items ) { $zeile = function ( array $zellen ) { $out = array(); foreach ( $zellen as $z ) { $z = (string) $z; if ( '' !== $z && in_array( $z[0], array( '=', '+', '-', '@', "\t", "\r" ), true ) ) { $z = "'" . $z; } $out[] = '"' . str_replace( '"', '""', $z ) . '"'; } return implode( ',', $out ); }; $text = 'ID,Title,Status,Scale,Old score,New score,Old criteria,New criteria,Notes' . "\n"; foreach ( $items as $i ) { $text .= $zeile( array( isset( $i['id'] ) ? (int) $i['id'] : '', isset( $i['title'] ) ? $i['title'] : '', isset( $i['status'] ) ? $i['status'] : '', isset( $i['scale'] ) ? $i['scale'] : '', isset( $i['old_total'] ) ? $i['old_total'] : '', isset( $i['new_total'] ) ? $i['new_total'] : '', isset( $i['old_criteria'] ) ? $i['old_criteria'] : '', isset( $i['criteria'] ) ? $i['criteria'] : '', isset( $i['notes'] ) ? implode( ' ', (array) $i['notes'] ) : '', ) ) . "\n"; } return $text; } /** * Daten aller Boxen für Übersicht und Liste. * * @param int $limit Höchstzahl. * @return array */ private static function boxes( $limit ) { $out = array(); foreach ( ThreeRB_Care::posts( 0, $limit ) as $id ) { $box = ThreeRB_Box::get( $id ); if ( ! ThreeRB_Box::has_rating( $box ) ) { continue; } $post = get_post( $id ); $gesamt = ThreeRB_Score::total( $box ); $codes = array(); $rot = 0; $gelb = 0; foreach ( ThreeRB_Diagnose::checks( $post, $box ) as $c ) { if ( 'ro' === $c['level'] ) { ++$rot; $codes[] = $c['code']; } elseif ( 'ge' === $c['level'] ) { ++$gelb; } } $out[] = array( 'id' => $id, 'title' => get_the_title( $post ), 'status' => $post->post_status, 'type' => $box['item']['type'], 'share' => $gesamt['share'], 'display' => ThreeRB_Render::zahl( $gesamt['value'], $box ), 'red' => $rot, 'yellow' => $gelb, 'codes' => $codes, 'schema' => null !== ThreeRB_Schema::build( $box, array( 'site_name' => get_bloginfo( 'name' ), 'author' => array( 'type' => 'Organization', 'name' => get_bloginfo( 'name' ), ), ) ), ); } return $out; } /** * Namen der Schema-Typen. * * @return array */ public static function type_labels() { return array( 'product' => __( 'Product', '3task-review-box' ), 'movie' => __( 'Movie', '3task-review-box' ), 'series' => __( 'TV series', '3task-review-box' ), 'season' => __( 'Season', '3task-review-box' ), 'episode' => __( 'Episode', '3task-review-box' ), 'game' => __( 'Video game', '3task-review-box' ), 'software' => __( 'Software or app', '3task-review-box' ), 'book' => __( 'Book', '3task-review-box' ), 'recipe' => __( 'Recipe', '3task-review-box' ), 'course' => __( 'Course', '3task-review-box' ), 'event' => __( 'Event', '3task-review-box' ), 'music' => __( 'Music recording', '3task-review-box' ), 'playlist' => __( 'Music playlist', '3task-review-box' ), 'local' => __( 'Local business (not your own)', '3task-review-box' ), 'organization' => __( 'Organization (not your own)', '3task-review-box' ), 'none' => __( 'No schema', '3task-review-box' ), ); } /** * Namen der Skalen. * * @return array */ public static function scale_labels() { return array( 'p10' => __( '0 to 10 points', '3task-review-box' ), 's10' => __( '10 stars', '3task-review-box' ), 's5' => __( '5 stars', '3task-review-box' ), 'pct' => __( '0 to 100 percent', '3task-review-box' ), ); } /** * Kennzahl. * * @param string $zahl Zahl. * @param string $titel Beschriftung. */ private static function kpi( $zahl, $titel ) { echo '
' . esc_html( $zahl ) . '' . esc_html( $titel ) . '
'; } /** * Punkt im Cockpit. * * @param string $stufe gr, ge, ro. * @param string $text Text. */ private static function punkt( $stufe, $text ) { echo '
  • ' . esc_html( $text ) . '
  • '; } /** * Auswahl als Tabellenzeile. * * @param string $name Name. * @param string $titel Beschriftung. * @param string $wert Wert. * @param array $optionen Optionen. */ private static function select( $name, $titel, $wert, array $optionen ) { $id = 'threerb-s-' . sanitize_key( str_replace( array( '[', ']' ), '-', $name ) ); printf( ''; } /** * Schalter als Tabellenzeile (mit verstecktem 0, damit „aus“ gespeichert wird). * * @param string $n Optionsname. * @param string $feld Feld. * @param bool $wert Wert. * @param string $titel Zeilentitel. * @param string $text Beschriftung. * @param string $hinweis Erklärung. */ private static function schalter( $n, $feld, $wert, $titel, $text, $hinweis = '' ) { printf( '%1$s%6$s', esc_html( $titel ), esc_attr( $n ), esc_attr( $feld ), checked( $wert, true, false ), esc_html( $text ), '' !== $hinweis ? '

    ' . esc_html( $hinweis ) . '

    ' : '' ); } }