Addsynapse_Search_Api_Client::stats(). * Το ΑΚΡΙΒΕΣ σχήμα είναι στο CLAUDE.md § Η ΣΥΜΒΑΣΗ. Κάθε πεδίο εδώ διαβάζεται * αμυντικά (η Addsynapse_Search_Analytics::normalize() το έχει ήδη γεμίσει με ασφαλή * defaults, αλλά ό,τι κι αν γυρίσει ο server, ΠΟΤΕ fatal εδώ). * * @package Addsynapse_Search_Filter */ if ( ! defined( 'ABSPATH' ) ) { exit; } // Το view αυτό γίνεται πάντα include() μέσα από μέθοδο της Addsynapse_Search_Admin // (βλ. admin/class-addsynapse-search-admin.php), άρα οι παρακάτω μεταβλητές είναι ήδη // τοπικές στην πράξη -- ποτέ πραγματικά globals. Το closure εδώ το κάνει // αληθές και για το static analysis (WordPress.NamingConventions. // PrefixAllGlobals), χωρίς να αλλάζει καμία συμπεριφορά. ( function () { /** * Μικρή, χωρίς εξαρτήσεις, γραμμή-γράφημα SVG για searches/zero_results ανά * ημέρα. ΟΧΙ βιβλιοθήκη, ΟΧΙ build step — απλό, φτιαγμένο στο χέρι SVG. * Native πάνω σε κάθε σημείο δίνει tooltip χωρίς καθόλου JS. * * @param array[] $rows Λίστα {day,searches,zero_results,clicks}. * @return string HTML/SVG, ήδη escaped/ασφαλές. */ $render_timeseries_svg = function ( array $rows ) { $count = count( $rows ); if ( $count < 2 ) { return ''; } $width = max( 360, min( 900, $count * 14 ) ); $height = 160; $pad_l = 34; $pad_r = 10; $pad_t = 10; $pad_b = 24; $plot_w = $width - $pad_l - $pad_r; $plot_h = $height - $pad_t - $pad_b; $max_val = 1; foreach ( $rows as $row ) { $max_val = max( $max_val, (int) $row['searches'], (int) $row['zero_results'] ); } $step = $plot_w / max( 1, $count - 1 ); $points_searches = array(); $points_zero = array(); $dots = array(); foreach ( $rows as $i => $row ) { $x = $pad_l + ( $i * $step ); $y_searches = $pad_t + $plot_h - ( ( (int) $row['searches'] / $max_val ) * $plot_h ); $y_zero = $pad_t + $plot_h - ( ( (int) $row['zero_results'] / $max_val ) * $plot_h ); $points_searches[] = round( $x, 1 ) . ',' . round( $y_searches, 1 ); $points_zero[] = round( $x, 1 ) . ',' . round( $y_zero, 1 ); $dots[] = array( 'x' => round( $x, 1 ), 'y' => round( $y_searches, 1 ), 'title' => sprintf( /* translators: 1: day, 2: searches, 3: no results, 4: clicks */ __( '%1$s — searches: %2$d, no results: %3$d, clicks: %4$d', 'addsynapse-search-filter' ), (string) $row['day'], (int) $row['searches'], (int) $row['zero_results'], (int) $row['clicks'] ), ); } ob_start(); ?> <svg viewBox="0 0 <?php echo esc_attr( $width ); ?> <?php echo esc_attr( $height ); ?>" width="100%" height="<?php echo esc_attr( $height ); ?>" role="img" aria-label="<?php esc_attr_e( 'Searches and searches with no results per day', 'addsynapse-search-filter' ); ?>"> <line x1="<?php echo esc_attr( $pad_l ); ?>" y1="<?php echo esc_attr( $pad_t + $plot_h ); ?>" x2="<?php echo esc_attr( $pad_l + $plot_w ); ?>" y2="<?php echo esc_attr( $pad_t + $plot_h ); ?>" stroke="#dcdcde" stroke-width="1" /> <polyline points="<?php echo esc_attr( implode( ' ', $points_searches ) ); ?>" fill="none" stroke="#2271b1" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" /> <polyline points="<?php echo esc_attr( implode( ' ', $points_zero ) ); ?>" fill="none" stroke="#d63638" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" /> <?php foreach ( $dots as $dot ) : ?> <circle cx="<?php echo esc_attr( $dot['x'] ); ?>" cy="<?php echo esc_attr( $dot['y'] ); ?>" r="3" fill="#2271b1"> <title><?php echo esc_html( $dot['title'] ); ?> Addsynapse_Search_Admin::MENU_SLUG, 'tab' => 'stats' ), admin_url( 'admin.php' ) ); $post_url = esc_url( admin_url( 'admin-post.php' ) ); $syn_url = add_query_arg( array( 'page' => Addsynapse_Search_Admin::MENU_SLUG, 'tab' => 'synonyms' ), admin_url( 'admin.php' ) ); /** * Δείχνει μια ημερομηνία με ανθρώπινη μορφή αν είναι έγκυρη, αλλιώς το * ακατέργαστο κείμενο όπως ήρθε — δεν σπάμε αν το backend αλλάξει format. * Το zero_terms.last_seen έχει ανάλυση ΗΜΕΡΑΣ (όχι ώρας) — δεν δείχνουμε ώρα. */ $addsynapse_search_format_date = function ( $raw ) { $raw = (string) $raw; if ( '' === $raw ) { return ''; } $ts = strtotime( $raw ); return $ts ? date_i18n( 'd/m/Y', $ts ) : $raw; }; ?>

get_error_code() ) ) : ?>

get_error_message() ); ?>

0 || ! empty( $zero_terms ) || ! empty( $top_terms ) || ! empty( $term_clicks ); if ( ! $has_any_data ) : ?>

🔴

$row ) : $term = (string) $row['term']; $count = (int) $row['searches']; $last_seen = $addsynapse_search_format_date( $row['last_seen'] ); if ( '' === $term ) { continue; } $term_key = 'zero-' . $i; $add_url = add_query_arg( 'add_synonym', rawurlencode( $term ), $syn_url ); ?>

⚠️

CTR

0 ) ? get_edit_post_link( $product_id ) : ''; $display_title = $has_title ? $product_title : ( $product_id > 0 /* translators: %d: product id */ ? sprintf( esc_html__( '#%d (deleted product)', 'addsynapse-search-filter' ), $product_id ) : esc_html__( '(deleted product)', 'addsynapse-search-filter' ) ); ?>
—

CTR

= 2 ) : ?>
array( 'viewbox' => true, 'width' => true, 'height' => true, 'role' => true, 'aria-label' => true, ), 'line' => array( 'x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'stroke' => true, 'stroke-width' => true, ), 'polyline' => array( 'points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linejoin' => true, 'stroke-linecap' => true, ), 'circle' => array( 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, ), 'title' => array(), ) ); ?>