get_opts(); $client_id = $o['gsc_client_id'] ?? ''; $client_secret = $o['gsc_client_secret'] ?? ''; $redirect_uri = admin_url( 'tools.php?page=aije-lite&tab=gsc&gsc_callback=1' ); $resp = wp_remote_post( 'https://oauth2.googleapis.com/token', array( 'body' => array( 'code' => sanitize_text_field( $_GET['code'] ), 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'grant_type' => 'authorization_code' ), ) ); if ( ! is_wp_error( $resp ) ) { $token = json_decode( wp_remote_retrieve_body( $resp ), true ); if ( ! empty( $token['access_token'] ) ) { update_option( 'aije_gsc_access_token', $token['access_token'] ); update_option( 'aije_gsc_refresh_token', $token['refresh_token'] ?? '' ); update_option( 'aije_gsc_token_expiry', time() + ( $token['expires_in'] ?? 3600 ) ); wp_safe_redirect( admin_url( 'tools.php?page=aije-lite&tab=gsc&gsc_connected=1' ) ); exit; } } wp_safe_redirect( admin_url( 'tools.php?page=aije-lite&tab=gsc&gsc_error=1' ) ); exit; } if ( isset( $_GET['gsc_disconnect'] ) ) { delete_option( 'aije_gsc_access_token' ); delete_option( 'aije_gsc_refresh_token' ); delete_option( 'aije_gsc_token_expiry' ); wp_safe_redirect( admin_url( 'tools.php?page=aije-lite&tab=gsc' ) ); exit; } } private function refresh_gsc_token() { $o = $this->get_opts(); $client_id = $o['gsc_client_id'] ?? ''; $client_secret = $o['gsc_client_secret'] ?? ''; $refresh_token = get_option( 'aije_gsc_refresh_token', '' ); if ( ! $refresh_token || ! $client_id || ! $client_secret ) { return false; } $resp = wp_remote_post( 'https://oauth2.googleapis.com/token', array( 'body' => array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'refresh_token' => $refresh_token, 'grant_type' => 'refresh_token' ), 'timeout' => 10 ) ); if ( is_wp_error( $resp ) ) { return false; } $token = json_decode( wp_remote_retrieve_body( $resp ), true ); if ( empty( $token['access_token'] ) ) { return false; } update_option( 'aije_gsc_access_token', $token['access_token'] ); update_option( 'aije_gsc_token_expiry', time() + ( $token['expires_in'] ?? 3600 ) ); return $token['access_token']; } private function get_valid_gsc_token() { $access_token = get_option( 'aije_gsc_access_token', '' ); $expiry = (int) get_option( 'aije_gsc_token_expiry', 0 ); if ( $expiry && time() > $expiry - 300 ) { $new_token = $this->refresh_gsc_token(); if ( $new_token ) { return $new_token; } delete_option( 'aije_gsc_access_token' ); return ''; } return $access_token; } /** * Ruft GSC Search Analytics ab und gibt Rohdaten zurück. * Wird von tab-gsc.php (Seitenansicht) und render_gsc_data() genutzt. */ private function gsc_query( $access_token, $property, $body_array, $timeout = 15 ) { $resp = wp_remote_post( 'https://www.googleapis.com/webmasters/v3/sites/' . urlencode( $property ) . '/searchAnalytics/query', array( 'headers' => array( 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body_array ), 'timeout' => $timeout ) ); if ( is_wp_error( $resp ) ) { return array(); } return json_decode( wp_remote_retrieve_body( $resp ), true )['rows'] ?? array(); } private function gsc_get_sites( $access_token ) { $resp = wp_remote_get( 'https://www.googleapis.com/webmasters/v3/sites', array( 'headers' => array( 'Authorization' => 'Bearer ' . $access_token ), 'timeout' => 10 ) ); if ( is_wp_error( $resp ) ) { return array(); } if ( wp_remote_retrieve_response_code( $resp ) === 401 ) { $new = $this->refresh_gsc_token(); if ( ! $new ) { delete_option( 'aije_gsc_access_token' ); return array(); } $resp = wp_remote_get( 'https://www.googleapis.com/webmasters/v3/sites', array( 'headers' => array( 'Authorization' => 'Bearer ' . $new ), 'timeout' => 10 ) ); if ( is_wp_error( $resp ) ) { return array(); } } return json_decode( wp_remote_retrieve_body( $resp ), true )['siteEntry'] ?? array(); } /** * Rendert die GSC-Datentabelle (aufgerufen aus tab-gsc.php wenn verbunden). */ private function render_gsc_data( $access_token ) { $access_token = $this->get_valid_gsc_token(); if ( ! $access_token ) { echo '

⚠ Google Token abgelaufen. Bitte Verbindung trennen und neu verbinden.

'; return; } $entries = $this->gsc_get_sites( $access_token ); if ( empty( $entries ) ) { echo '

Keine Search Console Properties gefunden.

'; return; } $selected = get_option( 'aije_gsc_property', $entries[0]['siteUrl'] ?? '' ); if ( isset( $_POST['gsc_property'] ) ) { $selected = sanitize_text_field( $_POST['gsc_property'] ); update_option( 'aije_gsc_property', $selected ); } // Property-Auswahl echo '
'; echo ''; $form_url = add_query_arg( array_filter( array( 'page' => 'aije-lite', 'tab' => 'gsc', 'gsc_days' => $_GET['gsc_days'] ?? null, 'gsc_sort' => $_GET['gsc_sort'] ?? null, 'gsc_dir' => $_GET['gsc_dir'] ?? null ) ), admin_url( 'tools.php' ) ); echo '
'; $days_map = array( '7' => 7, '28' => 28, '90' => 90 ); $days = isset( $_GET['gsc_days'] ) && isset( $days_map[ $_GET['gsc_days'] ] ) ? (int) $_GET['gsc_days'] : 28; $end = date( 'Y-m-d', strtotime( '-2 days' ) ); $start = date( 'Y-m-d', strtotime( '-' . ( $days + 2 ) . ' days' ) ); $cmp_end = date( 'Y-m-d', strtotime( '-' . ( $days + 2 ) . ' days' ) ); $cmp_start = date( 'Y-m-d', strtotime( '-' . ( $days * 2 + 2 ) . ' days' ) ); $rows = $this->gsc_query( $access_token, $selected, array( 'startDate' => $start, 'endDate' => $end, 'dimensions' => array( 'page' ), 'rowLimit' => 500, 'orderBy' => array( array( 'fieldName' => 'clicks', 'sortOrder' => 'DESCENDING' ) ) ) ); $cmp_rows = $this->gsc_query( $access_token, $selected, array( 'startDate' => $cmp_start, 'endDate' => $cmp_end, 'dimensions' => array( 'page' ), 'rowLimit' => 500 ) ); $cmp_index = array(); foreach ( $cmp_rows as $cr ) { $cmp_index[ $cr['keys'][0] ] = $cr; } // Zeitraum-Umschalter echo '
Zeitraum:'; foreach ( array( '7' => '7 Tage', '28' => '28 Tage', '90' => '90 Tage' ) as $d => $label ) { $active = ( $days == $d ); $url_d = add_query_arg( array( 'gsc_days' => $d, 'gsc_sort' => $_GET['gsc_sort'] ?? 'clicks', 'gsc_dir' => $_GET['gsc_dir'] ?? 'desc' ), admin_url( 'tools.php?page=aije-lite&tab=gsc' ) ); echo '' . esc_html( $label ) . ''; } echo '' . count( $rows ) . ' Seiten · ' . esc_html( $start ) . ' – ' . esc_html( $end ) . '
'; if ( empty( $rows ) ) { echo '

Keine Daten gefunden.

'; return; } // KPI-Zeile $total_clicks = array_sum( array_column( $rows, 'clicks' ) ); $total_impr = array_sum( array_column( $rows, 'impressions' ) ); $cmp_clicks = array_sum( array_column( $cmp_rows, 'clicks' ) ); $cmp_impr = array_sum( array_column( $cmp_rows, 'impressions' ) ); $ti_avg = $total_impr > 0 ? $total_impr : 1; $w_ctr = 0; $w_pos = 0; foreach ( $rows as $r ) { $iw = ( $r['impressions'] ?? 0 ) / $ti_avg; $w_ctr += ( $r['ctr'] ?? 0 ) * $iw; $w_pos += ( $r['position'] ?? 0 ) * $iw; } $avg_ctr = round( $w_ctr * 100, 1 ); $avg_pos = round( $w_pos, 1 ); $dc = $total_clicks - $cmp_clicks; $di = $total_impr - $cmp_impr; $fmt = function( $v, $inv = false ) { if ( $v == 0 ) return '±0'; $pos = $inv ? $v < 0 : $v > 0; $c = $pos ? '#22c55e' : '#ef4444'; return '' . ( $v > 0 ? '+' : '' ) . round( $v, 1 ) . ''; }; echo '
'; foreach ( array( array( 'Klicks', number_format_i18n( $total_clicks ), $fmt( $dc ), '' ), array( 'Impressionen', number_format_i18n( $total_impr ), $fmt( $di ), '' ), array( 'Ø CTR', $avg_ctr . '%', '', '' ), array( 'Ø Position', $avg_pos, '', '' ) ) as $s ) { echo '
' . esc_html( $s[0] ) . '
' . esc_html( $s[1] ) . '
' . ( $s[2] ? '
vs. Vorperiode: ' . $s[2] . '
' : '' ) . '
'; } echo '
'; // SEO-Issues aus Post-Daten $o = $this->get_opts(); $all_posts = get_posts( array( 'post_type' => $o['enabled_post_types'] ?? array( 'post' ), 'posts_per_page' => -1, 'post_status' => 'publish' ) ); $url_issues = array(); foreach ( $all_posts as $p ) { $issues = array(); if ( ! has_post_thumbnail( $p->ID ) ) { $issues[] = array( 'msg' => 'Kein Beitragsbild', 'severity' => 'medium' ); } $meta = get_post_meta( $p->ID, '_aije_metadesc', true ) ?: get_post_meta( $p->ID, '_yoast_wpseo_metadesc', true ) ?: get_post_meta( $p->ID, 'rank_math_description', true ); if ( empty( $meta ) ) { $issues[] = array( 'msg' => 'Keine Meta-Description', 'severity' => 'high' ); } if ( str_word_count( wp_strip_all_tags( $p->post_content ) ) < 300 ) { $issues[] = array( 'msg' => 'Text zu kurz', 'severity' => 'medium' ); } if ( ! preg_match( '/

post_content ) ) { $issues[] = array( 'msg' => 'Kein H2', 'severity' => 'medium' ); } if ( empty( get_user_meta( $p->post_author, 'aije_job_title', true ) ) ) { $issues[] = array( 'msg' => 'Kein E-E-A-T Profil', 'severity' => 'high' ); } if ( ! empty( $issues ) ) { $url_issues[ trailingslashit( get_permalink( $p->ID ) ) ] = $issues; } } // Suchfeld + Tabelle echo ''; echo '
'; echo ''; foreach ( array( array( 'page', 'Seite', 'left' ), array( 'tasks', 'Tasks', 'left' ), array( 'clicks', 'Klicks', 'right' ), array( 'clicks_d', '±', 'right' ), array( 'impr', 'Impressionen', 'right' ), array( 'ctr', 'CTR', 'right' ), array( 'pos', 'Ø Position', 'right' ), array( 'pos_d', '±', 'right' ) ) as $col ) { $no_sort = in_array( $col[0], array( 'tasks' ) ); $onclick = ! $no_sort ? ' onclick="aije_gsc_sort(\'' . esc_js( $col[0] ) . '\',this)"' : ''; $cursor = ! $no_sort ? 'cursor:pointer;user-select:none;' : ''; echo ''; } echo ''; foreach ( $rows as $row ) { $url = $row['keys'][0] ?? ''; $path = str_replace( home_url(), '', $url ); $clicks = (int) ( $row['clicks'] ?? 0 ); $impr = (int) ( $row['impressions'] ?? 0 ); $ctr_raw = round( ( $row['ctr'] ?? 0 ) * 100, 1 ); $pos = round( $row['position'] ?? 0, 1 ); $pos_c = $pos <= 3 ? '#22c55e' : ( $pos <= 10 ? '#f59e0b' : '#ef4444' ); $cmp = $cmp_index[ $url ] ?? null; $d_clicks = $cmp ? $clicks - (int) ( $cmp['clicks'] ?? 0 ) : null; $d_pos = $cmp ? $pos - round( $cmp['position'] ?? 0, 1 ) : null; $issues = $url_issues[ trailingslashit( $url ) ] ?? array(); $task_html = ''; foreach ( $issues as $issue ) { $sev_c = $issue['severity'] === 'high' ? '#ef4444' : '#f59e0b'; $task_html .= '' . esc_html( mb_substr( $issue['msg'], 0, 22 ) ) . ''; } $dc_html = $d_clicks !== null ? '' . ( $d_clicks > 0 ? '+' : '' ) . $d_clicks . '' : '–'; $dp_html = ( $d_pos !== null && $d_pos != 0 ) ? '' . ( $d_pos > 0 ? '+' : '' ) . round( $d_pos, 1 ) . '' : '–'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html( $col[1] ) . ( ! $no_sort ? ' ↕' : '' ) . '
' . esc_html( strlen( $path ) > 40 ? substr( $path, 0, 37 ) . '…' : $path ) . '' . ( $task_html ? '
' . $task_html . '
' : '✓ OK' ) . '
' . number_format_i18n( $clicks ) . '' . $dc_html . '' . number_format_i18n( $impr ) . '' . $ctr_raw . '%' . $pos . '' . $dp_html . '
'; ?>