0 && current_user_can( 'manage_options' ) ) { return; } self::$uid = 0; Wp360_Speed_Optimizer_Doctor_Collector::stop(); self::send_cookie( '', time() - HOUR_IN_SECONDS ); } /** * Pruefung des Cookie-Werts "..". * * @param mixed $value * @return array{uid:int,expires:int}|null */ public static function parse_cookie( $value ) { if ( ! is_string( $value ) || ! preg_match( '/^(\d{1,10})\.(\d{10})\.([a-f0-9]{64})$/', $value, $m ) ) { return null; } $uid = (int) $m[1]; $expires = (int) $m[2]; if ( $uid < 1 || $expires <= time() || $expires > time() + self::TTL + 60 ) { return null; } if ( ! hash_equals( self::sign( $uid, $expires ), $m[3] ) ) { return null; } return array( 'uid' => $uid, 'expires' => $expires, ); } private static function sign( $uid, $expires ) { return hash_hmac( 'sha256', self::COOKIE . '|' . (int) $uid . '|' . (int) $expires, wp_salt( 'auth' ) ); } /** * Ablaufzeit der laufenden Messung dieses Browsers, 0 wenn keine laeuft. * * @return int */ public static function active_until() { $parsed = self::parse_cookie( isset( $_COOKIE[ self::COOKIE ] ) ? wp_unslash( $_COOKIE[ self::COOKIE ] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( null === $parsed || get_current_user_id() !== $parsed['uid'] ) { return 0; } return $parsed['expires']; } /** * Umschalten ueber die Admin-Leiste (admin-post.php). */ public static function handle_toggle() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', '360-orbit-speed-optimizer' ), '', array( 'response' => 403 ) ); } check_admin_referer( self::ACTION ); $on = isset( $_REQUEST['mode'] ) && 'on' === $_REQUEST['mode']; if ( $on ) { $expires = time() + self::TTL; self::send_cookie( get_current_user_id() . '.' . $expires . '.' . self::sign( get_current_user_id(), $expires ), $expires ); } else { self::send_cookie( '', time() - HOUR_IN_SECONDS ); } $back = wp_get_referer(); wp_safe_redirect( $back ? $back : admin_url() ); exit; } private static function send_cookie( $value, $expires ) { if ( headers_sent() ) { return; } setcookie( self::COOKIE, $value, array( 'expires' => $expires, 'path' => COOKIEPATH ? COOKIEPATH : '/', 'domain' => COOKIE_DOMAIN, 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax', ) ); } /** * Adresse zum Ein- oder Ausschalten der Messung (mit Nonce). * * @param bool $on true = einschalten. * @return string */ public static function toggle_url( $on ) { return wp_nonce_url( add_query_arg( array( 'action' => self::ACTION, 'mode' => $on ? 'on' : 'off', ), admin_url( 'admin-post.php' ) ), self::ACTION ); } /** * Schalter in der Admin-Leiste (nur Administratoren). * * @param WP_Admin_Bar $bar */ public static function admin_bar_node( $bar ) { if ( ! current_user_can( 'manage_options' ) ) { return; } $active = self::active_until() > 0; $url = self::toggle_url( ! $active ); $bar->add_node( array( 'id' => 'wp360-so-doctor-capture', 'title' => $active ? sprintf( /* translators: %d: number of recorded page views */ esc_html__( 'Doctor: measuring (%d) – stop', '360-orbit-speed-optimizer' ), count( self::entries() ) ) : esc_html__( 'Doctor: measure this browser', '360-orbit-speed-optimizer' ), 'href' => $url, 'meta' => array( 'title' => esc_attr__( 'Records the next page views of this browser for 60 minutes. Only administrators are measured.', '360-orbit-speed-optimizer' ) ), ) ); } /** * Senke des Messkerns: haengt eine kurze Messung an den Ringspeicher. * * @param array $raw */ public static function store( $raw ) { // Die Aufrufe des Doctors selbst (Check starten, Auswerten) gehoeren nicht in die Messung. $action = isset( $_REQUEST['action'] ) && is_string( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 0 === strpos( $action, 'wp360_so_doctor' ) ) { return; } // Heartbeat-Aufrufe werden nur gezaehlt: Sie fluten sonst den Ringspeicher mit Eintraegen ohne Aussage. if ( 'heartbeat' === $action ) { self::count_heartbeat( time() ); return; } $m = Wp360_Speed_Optimizer_Doctor_Measurement::normalize( $raw ); $entry = array( 'origin' => $m['origin'], 'url' => $m['url'], 'time' => $m['time'], 'server_ms' => $m['server_ms'], 'memory_peak' => $m['memory_peak'], 'memory_limit' => $m['memory_limit'], 'queries' => array( 'count' => $m['queries']['count'], 'total_ms' => $m['queries']['total_ms'], 'slowest' => array_slice( $m['queries']['slowest'], 0, 3 ), 'duplicates' => array_slice( $m['queries']['duplicates'], 0, 3 ), 'by_source' => array_slice( $m['queries']['by_source'], 0, 5 ), ), 'sources' => array_slice( $m['sources'], 0, 10 ), ); $buffer = self::entries(); $buffer[] = $entry; update_option( self::OPTION, array_slice( $buffer, -self::MAX ), false ); } /** * @return list> */ public static function entries() { // Ein anderer Prozess schreibt den Ringspeicher: Zwischenspeicher dieses Prozesses (auch // den Vermerk "Option gibt es nicht") verwerfen, sonst bliebe ein frueher Fehlgriff stehen. wp_cache_delete( self::OPTION, 'options' ); wp_cache_delete( 'notoptions', 'options' ); $entries = get_option( self::OPTION, array() ); return is_array( $entries ) ? array_values( $entries ) : array(); } public static function clear() { delete_option( self::OPTION ); delete_option( self::HB_OPTION ); } /** * Zaehlt einen Heartbeat-Aufruf. * * @param int $now Zeitpunkt (Unix-Zeit). */ private static function count_heartbeat( $now ) { $hb = self::heartbeat(); update_option( self::HB_OPTION, array( 'calls' => $hb['calls'] + 1, 'first' => $hb['first'] > 0 ? $hb['first'] : (int) $now, 'last' => (int) $now, ), false ); } /** * Zahl und Zeitraum der gezaehlten Heartbeat-Aufrufe. * * @return array{calls:int,first:int,last:int} */ public static function heartbeat() { wp_cache_delete( self::HB_OPTION, 'options' ); wp_cache_delete( 'notoptions', 'options' ); $hb = get_option( self::HB_OPTION, array() ); $hb = is_array( $hb ) ? $hb : array(); return array( 'calls' => isset( $hb['calls'] ) ? (int) $hb['calls'] : 0, 'first' => isset( $hb['first'] ) ? (int) $hb['first'] : 0, 'last' => isset( $hb['last'] ) ? (int) $hb['last'] : 0, ); } /** * Fasst die mitgeschnittenen Aufrufe fuer den Ratgeber zusammen: Median * der Admin-Seiten, Zahl der Heartbeat-Aufrufe, langsamste Seite. * * @param list>|null $entries Eintraege (Standard: der Ringspeicher). * @return array{measurement:array,count:int,pages:int,slowest:array{url:string,server_ms:float}|null}|null null ohne Aufrufe. */ public static function summarize( $entries = null ) { $entries = null === $entries ? self::entries() : $entries; $hb = self::heartbeat(); if ( array() === $entries && 0 === $hb['calls'] ) { return null; } $pages = array(); $first_time = $hb['first'] > 0 ? $hb['first'] : PHP_INT_MAX; $last_time = $hb['last']; foreach ( $entries as $entry ) { if ( ! is_array( $entry ) ) { continue; } $time = isset( $entry['time'] ) ? (int) $entry['time'] : 0; $first_time = $time > 0 ? min( $first_time, $time ) : $first_time; $last_time = max( $last_time, $time ); if ( isset( $entry['origin'] ) && in_array( $entry['origin'], array( 'backend', 'frontend' ), true ) ) { $pages[] = $entry; } } $minutes = $last_time > $first_time && PHP_INT_MAX !== $first_time ? (int) ceil( ( $last_time - $first_time ) / 60 ) : 0; // Adminpfad auch bei Installationen in einem Unterverzeichnis (z. B. /blog/wp-admin/). $admin_path = trailingslashit( (string) wp_parse_url( admin_url(), PHP_URL_PATH ) ); $dashboard_ms = 0.0; foreach ( $pages as $page ) { if ( isset( $page['url'], $page['server_ms'] ) && preg_match( '#^' . preg_quote( $admin_path, '#' ) . '(index\.php)?(\?|$)#', (string) $page['url'] ) ) { $dashboard_ms = max( $dashboard_ms, (float) $page['server_ms'] ); } } $median = Wp360_Speed_Optimizer_Doctor_Measurement::median( $pages ); $median['dashboard_ms'] = $dashboard_ms; $median['origin'] = 'backend'; $median['heartbeat'] = array( 'calls' => $hb['calls'], 'minutes' => max( 1, $minutes ), ); $slowest = null; foreach ( $pages as $page ) { $normalized = Wp360_Speed_Optimizer_Doctor_Measurement::normalize( $page ); if ( null === $slowest || $normalized['server_ms'] > $slowest['server_ms'] ) { $slowest = array( 'url' => $normalized['url'], 'server_ms' => $normalized['server_ms'], ); } } return array( 'measurement' => Wp360_Speed_Optimizer_Doctor_Measurement::normalize( $median ), 'count' => count( $entries ), 'pages' => count( $pages ), 'slowest' => $slowest, ); } }