cron_heartbeat_runner(); } public function on_admin_activity() { if ( ! current_user_can( 'manage_options' ) ) { return; } $last = (int) get_option( 'aije_last_heartbeat', 0 ); $last_version = get_option( 'aije_last_heartbeat_version', '' ); $current_version = AIJE_PLUGIN_VERSION; if ( $last_version !== $current_version ) { update_option( 'aije_last_heartbeat_version', $current_version ); delete_option( 'aije_last_heartbeat' ); delete_transient( 'aije_pro_status_' . md5( home_url() ) ); $this->cron_heartbeat_runner(); $this->get_pro_status(); return; } if ( time() - $last < DAY_IN_SECONDS ) { return; } $this->cron_heartbeat_runner(); } public function send_first_ping() { global $wp_version; $o = $this->get_opts(); $post_count = 0; foreach ( $o['enabled_post_types'] as $pt ) { $cnt = wp_count_posts( $pt ); $post_count += (int) ( $cnt->publish ?? 0 ); } wp_remote_post( self::STATS_ENDPOINT . 'heartbeat', array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'site_name' => get_bloginfo( 'name' ), 'wp_version' => $wp_version ?? '', 'php_version' => PHP_VERSION, 'plugin_version' => AIJE_PLUGIN_VERSION, 'post_count' => $post_count, 'first_ping' => true, 'features' => $this->get_feature_usage_snapshot() ) ), 'blocking' => false, 'sslverify' => true, ) ); } public function cron_heartbeat_runner() { if ( get_option( self::OPT_STATS_OPTOUT ) ) { return; } global $wp_version; $o = $this->get_opts(); $post_count = 0; foreach ( $o['enabled_post_types'] as $pt ) { $count = wp_count_posts( $pt ); $post_count += (int) ( $count->publish ?? 0 ); } wp_remote_post( self::STATS_ENDPOINT . 'heartbeat', array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'site_name' => get_bloginfo( 'name' ), 'wp_version' => $wp_version ?? '', 'php_version' => PHP_VERSION, 'plugin_version' => AIJE_PLUGIN_VERSION, 'post_count' => $post_count, 'features' => $this->get_feature_usage_snapshot() ) ), 'blocking' => false, 'sslverify' => true, ) ); update_option( 'aije_last_heartbeat', time(), false ); $this->send_score_stats(); } /** * Aggregierte, anonyme Nutzungssignale für konkrete Features — bewusst nur * Zähler/Booleans, keine Inhalte (Job-Titel, Bio-Texte etc. werden NICHT * mitgesendet), damit die Zusage in der readme.txt-FAQ ("keine Inhalte") * weiterhin stimmt. Fließt ins bestehende Heartbeat-Payload mit ein statt * einen eigenen Request zu erzeugen. */ private function get_feature_usage_snapshot() { return array( 'gsc_connected' => (bool) get_option( 'aije_gsc_refresh_token' ), 'eeat_profiles_filled' => $this->count_eeat_profiles_filled(), 'internal_link_uses' => (int) get_option( 'aije_internal_link_uses', 0 ), ); } /** * Zählt Nutzer mit Schreibrechten, die mindestens eines der Experten-Profil- * Felder (Berufsbezeichnung, Fachbiografie, Website, SameAs-Links) ausgefüllt * haben. Nur die Anzahl wird übertragen, nie die Feldinhalte selbst. */ private function count_eeat_profiles_filled() { $users = get_users( array( 'capability' => 'edit_posts', 'fields' => array( 'ID' ) ) ); $count = 0; foreach ( $users as $u ) { $job = get_user_meta( $u->ID, self::USER_META_JOB_TITLE, true ); $bio = get_user_meta( $u->ID, self::USER_META_EXPERTISE, true ); $site = get_user_meta( $u->ID, self::USER_META_WEBSITE, true ); $sameas = get_user_meta( $u->ID, self::USER_META_SAME_AS, true ); if ( ! empty( $job ) || ! empty( $bio ) || ! empty( $site ) || ! empty( $sameas ) ) { $count++; } } return $count; } private function send_score_stats() { $o = $this->get_opts(); $query = new WP_Query( array( 'post_type' => $o['enabled_post_types'], 'posts_per_page' => -1, 'post_status' => 'publish', 'no_found_rows' => true ) ); if ( ! $query->have_posts() ) { return; } $scores = array(); $cat_totals = array( 'content' => 0, 'eeat' => 0, 'technical' => 0, 'linking' => 0 ); $issue_freq = array(); $count = 0; while ( $query->have_posts() ) { $query->the_post(); $analysis = $this->calculate_ai_analysis( get_post() ); $scores[] = $analysis['score']; foreach ( $cat_totals as $k => $_ ) { $cat_totals[ $k ] += $analysis['categories'][ $k ]['score'] ?? 0; } foreach ( array_slice( $analysis['issues'], 0, 3 ) as $issue ) { $key = $issue['msg'] ?? ''; $issue_freq[ $key ] = ( $issue_freq[ $key ] ?? 0 ) + 1; } $count++; } wp_reset_postdata(); if ( $count === 0 ) { return; } arsort( $issue_freq ); $avg_score = round( array_sum( $scores ) / $count ); $cat_avgs = array(); foreach ( $cat_totals as $k => $total ) { $cat_avgs[ $k ] = round( $total / $count ); } wp_remote_post( self::STATS_ENDPOINT . 'score', array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'plugin_version' => AIJE_PLUGIN_VERSION, 'preset' => $o['score_preset'] ?? 'standard', 'avg_score' => $avg_score, 'scores' => $cat_avgs, 'top_issues' => array_keys( array_slice( $issue_freq, 0, 5, true ) ), 'post_count' => $count ) ), 'blocking' => false, 'sslverify' => true, ) ); } /* ===== Pro-Status ===== */ public function get_pro_status() { $cache_key = 'aije_pro_status_' . md5( home_url() ); delete_transient( $cache_key ); $payload = array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'action' => 'status', 'plugin_version' => AIJE_PLUGIN_VERSION ); $response = wp_remote_post( self::AI_PROXY_URL, array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), 'sslverify' => false ) ); if ( ! is_wp_error( $response ) ) { $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! empty( $body['success'] ) ) { set_transient( $cache_key, $body, 5 * MINUTE_IN_SECONDS ); return $body; } } return array( 'tier' => 'free', 'is_pro' => false, 'remaining' => 3 ); } public function aije_check_status_handler() { check_ajax_referer( 'aije_topic_nonce', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error(); } $payload = array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'action' => 'status', 'plugin_version' => AIJE_PLUGIN_VERSION ); $response = wp_remote_post( self::AI_PROXY_URL, array( 'timeout' => 10, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), 'sslverify' => false ) ); if ( is_wp_error( $response ) ) { wp_send_json_error(); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! empty( $body['success'] ) ) { delete_transient( 'aije_pro_status_' . md5( home_url() ) ); set_transient( 'aije_pro_status_' . md5( home_url() ), $body, 5 * MINUTE_IN_SECONDS ); wp_send_json_success( $body ); } wp_send_json_error(); } /* ===== Telemetrie-Einwilligung ===== */ public function stats_consent_notice() { if ( get_option( 'aije_stats_notice_dismissed' ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } $is_optout = (bool) get_option( self::OPT_STATS_OPTOUT ); $optout_url = wp_nonce_url( admin_url( 'admin-post.php?action=aije_stats_optout' ), 'aije_stats_optout' ); $optin_url = wp_nonce_url( admin_url( 'admin-post.php?action=aije_stats_optin' ), 'aije_stats_optin' ); echo '