HEX
Server: Apache
System: Linux vps.teamads.com 4.18.0-553.126.1.el8_10.x86_64 #1 SMP Thu May 28 06:44:09 EDT 2026 x86_64
User: teamadsc (1024)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/teamadsc/public_html/wp-content/plugins/visitors/GoogleSearchConsoleIntegration.php
<?php

/**
 * Google Search Console Integration with Service Account JSON Authentication
 * Fixed version with improved error handling and disconnect functionality
 */

class GoogleSearchConsoleIntegration
{
    private $service_account_json;
    private $site_url;
    private $access_token;

    public function __construct()
    {
        $this->service_account_json = get_option('ahcpro_gsc_service_account_json', '');
        $this->site_url = get_site_url();
    }

    /**
     * Generate JWT token for Google API authentication
     */
    private function generate_jwt_token()
    {
        if (empty($this->service_account_json)) {
            return false;
        }

        $service_account = json_decode($this->service_account_json, true);
        if (!$service_account || !isset($service_account['private_key'])) {
            error_log('GSC: Invalid service account JSON');
            return false;
        }

        // JWT Header
        $header = json_encode([
            'alg' => 'RS256',
            'typ' => 'JWT'
        ]);

        // JWT Payload
        $now = time();
        $payload = json_encode([
            'iss' => $service_account['client_email'],
            'scope' => 'https://www.googleapis.com/auth/webmasters.readonly',
            'aud' => 'https://oauth2.googleapis.com/token',
            'exp' => $now + 3600,
            'iat' => $now
        ]);

        // Encode
        $base64_header = $this->base64url_encode($header);
        $base64_payload = $this->base64url_encode($payload);

        // Create signature
        $signature_input = $base64_header . '.' . $base64_payload;

        $private_key = $service_account['private_key'];
        if (!openssl_sign($signature_input, $signature, $private_key, OPENSSL_ALGO_SHA256)) {
            error_log('GSC: Failed to sign JWT');
            return false;
        }

        $base64_signature = $this->base64url_encode($signature);

        return $base64_header . '.' . $base64_payload . '.' . $base64_signature;
    }

    /**
     * Base64 URL-safe encoding
     */
    private function base64url_encode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * Get OAuth2 access token
     */
    private function get_access_token()
    {
        if (!empty($this->access_token)) {
            return $this->access_token;
        }

        $jwt = $this->generate_jwt_token();
        if (!$jwt) {
            return false;
        }

        $post_data = http_build_query([
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $jwt
        ]);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, 'https://oauth2.googleapis.com/token');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/x-www-form-urlencoded'
        ]);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_code !== 200) {
            error_log('GSC: OAuth token request failed: ' . $response);
            return false;
        }

        $token_data = json_decode($response, true);
        if (!isset($token_data['access_token'])) {
            error_log('GSC: No access token in response');
            return false;
        }

        $this->access_token = $token_data['access_token'];
        return $this->access_token;
    }

    /**
     * Fetch search queries from Google Search Console API
     */
    public function get_search_queries($days = 30)
    {
        $access_token = $this->get_access_token();
        if (!$access_token) {
            error_log('GSC: Failed to get access token');
            return false;
        }

        $end_date = date('Y-m-d');
        $start_date = date('Y-m-d', strtotime("-{$days} days"));

        $api_url = 'https://www.googleapis.com/webmasters/v3/sites/' . urlencode($this->site_url) . '/searchAnalytics/query';

        $request_body = json_encode([
            'startDate' => $start_date,
            'endDate' => $end_date,
            'dimensions' => ['query', 'page'],
            'rowLimit' => 1000,
            'startRow' => 0
        ]);

        $headers = [
            'Authorization: Bearer ' . $access_token,
            'Content-Type: application/json',
            'Content-Length: ' . strlen($request_body)
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $api_url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request_body);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curl_error = curl_error($ch);
        curl_close($ch);

        if ($curl_error) {
            error_log('GSC API Curl Error: ' . $curl_error);
            return false;
        }

        if ($http_code !== 200) {
            error_log('GSC API Error: HTTP ' . $http_code . ' - ' . $response);
            return false;
        }

        $data = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log('GSC API: Invalid JSON response - ' . json_last_error_msg());
            return false;
        }

        if (!isset($data['rows'])) {
            error_log('GSC API: No rows data found in response');
            return array();
        }

        error_log('GSC API: Successfully retrieved ' . count($data['rows']) . ' search query rows');
        return $data['rows'];
    }

    /**
     * Test connection by listing sites
     */
    public function test_connection()
    {
        if (empty($this->service_account_json)) {
            return [
                'success' => false,
                'message' => 'Service Account JSON is missing. Please upload the JSON credentials file.',
                'current_site_found' => false
            ];
        }

        $access_token = $this->get_access_token();
        if (!$access_token) {
            return [
                'success' => false,
                'message' => 'Failed to get access token. Check your Service Account JSON credentials.',
                'current_site_found' => false
            ];
        }

        $api_url = 'https://www.googleapis.com/webmasters/v3/sites';

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $access_token
        ]);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_code !== 200) {
            return [
                'success' => false,
                'message' => 'API connection failed (HTTP ' . $http_code . '). Make sure the service account is added to your Search Console property for: ' . $this->site_url,
                'current_site_found' => false
            ];
        }

        $data = json_decode($response, true);
        $site_count = isset($data['siteEntry']) ? count($data['siteEntry']) : 0;

        $current_site_found = false;
        $normalized_site_url = untrailingslashit(strtolower($this->site_url));

        if (isset($data['siteEntry'])) {
            foreach ($data['siteEntry'] as $site) {
                $normalized_api_url = untrailingslashit(strtolower($site['siteUrl']));
                if ($normalized_api_url === $normalized_site_url) {
                    $current_site_found = true;
                    break;
                }
            }
        }

        if ($current_site_found) {
            return [
                'success' => true,
                'message' => "Your site ({$this->site_url}) is properly configured.",
                'current_site_found' => true
            ];
        }

        $message = "Successfully connected to Google Search Console API.\n";
        $message .= "Your site ({$this->site_url}) was not found in the authorized sites.\n";
        $message .= "Please add the service account email to your Search Console property.\n";

        if ($site_count > 0 && isset($data['siteEntry'])) {
            $message .= "Available sites:\n";
            foreach ($data['siteEntry'] as $site) {
                $message .= "- {$site['siteUrl']}\n";
            }
        }

        return [
            'success' => true,
            'message' => $message,
            'sites' => $data,
            'site_count' => $site_count,
            'current_site_found' => false
        ];
    }

    /**
     * Process and store search console data
     */
    public function sync_search_console_data($days = 30)
    {
        $search_data = $this->get_search_queries($days);

        if ($search_data === false) {
            error_log('GSC Sync: Failed to get search data from API');
            return false;
        }

        if (empty($search_data)) {
            error_log('GSC Sync: No search data available');
            return 0;
        }

        error_log('GSC Sync: Retrieved ' . count($search_data) . ' search query rows');

        $success_count = 0;

        foreach ($search_data as $row) {
            try {
                if (!isset($row['keys']) || !is_array($row['keys']) || empty($row['keys'])) {
                    continue;
                }

                $query = $row['keys'][0];
                $page = isset($row['keys'][1]) ? $row['keys'][1] : $this->site_url;
                $clicks = isset($row['clicks']) ? intval($row['clicks']) : 0;

                if ($clicks > 0 && !empty($query)) {
                    if ($this->updateKeywordsFromGSC('GSC.Data', $query, $page)) {
                        $success_count++;
                    }
                }
            } catch (Exception $e) {
                error_log('GSC Sync: Error processing row - ' . $e->getMessage());
                continue;
            }
        }

        error_log('GSC Sync: Successfully processed ' . $success_count . ' keywords');
        return $success_count;
    }

    /**
     * Get sync status
     */
    public function get_sync_status()
    {
        $last_sync = get_option('ahcpro_last_gsc_sync', 0);
        $last_sync_count = get_option('ahcpro_last_gsc_sync_count', 0);

        if ($last_sync) {
            return [
                'last_sync' => date('Y-m-d H:i:s', $last_sync),
                'last_sync_human' => human_time_diff($last_sync, time()) . ' ago',
                'last_count' => $last_sync_count,
                'has_synced' => true
            ];
        }

        return [
            'last_sync' => 'Never',
            'last_sync_human' => 'Never',
            'last_count' => 0,
            'has_synced' => false
        ];
    }

    /**
     * Check if GSC is connected
     */
    public function is_connected()
    {
        if (empty($this->service_account_json)) {
            return false;
        }

        $test = $this->test_connection();
        return !empty($test['success']) && !empty($test['current_site_found']);
    }

    /**
     * Get service account email
     */
    public function get_service_account_email()
    {
        if (empty($this->service_account_json)) {
            return null;
        }

        $service_account = json_decode($this->service_account_json, true);
        return isset($service_account['client_email']) ? $service_account['client_email'] : null;
    }

    /**
     * Store GSC data in keywords table
     */
    protected function updateKeywordsFromGSC($ip_address, $keywords, $referer_url)
    {
        global $wpdb;

        $today = gmdate("Y-m-d");
        $existing = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM `{$wpdb->prefix}ahc_keywords` 
             WHERE kwd_keywords = %s AND kwd_date = %s AND kwd_ip_address = %s AND site_id = %d",
            $keywords,
            $today,
            $ip_address,
            get_current_blog_id()
        ));

        if ($existing > 0) {
            return true;
        }

        $srh_id = $this->getSearchEngineId('Google', 'google.png');
        $bsr_id = $this->getBrowserId('Google Search Console', 'gsc.png');
        $ctr_id = $this->getCountryId('Global', 'gl');

        $sql = "INSERT INTO `ahc_keywords` (kwd_ip_address, kwd_keywords, kwd_referer, srh_id, ctr_id, bsr_id, kwd_date, kwd_time, site_id)
                VALUES (%s, %s, %s, %d, %d, %d, %s, %s, %d)";

        return $wpdb->query($wpdb->prepare(
            $sql,
            $ip_address,
            $keywords,
            $referer_url,
            $srh_id,
            $ctr_id,
            $bsr_id,
            gmdate("Y-m-d"),
            gmdate("H:i:s"),
            get_current_blog_id()
        )) !== false;
    }

    private function getSearchEngineId($name, $icon)
    {
        global $wpdb;
        $result = $wpdb->get_var($wpdb->prepare(
            "SELECT srh_id FROM `{$wpdb->prefix}ahc_search_engines` WHERE srh_name = %s",
            $name
        ));
        if (!$result) {
            $wpdb->insert('ahc_search_engines', ['srh_name' => $name, 'srh_icon' => $icon]);
            return $wpdb->insert_id;
        }
        return $result;
    }

    private function getBrowserId($name, $icon)
    {
        global $wpdb;
        $result = $wpdb->get_var($wpdb->prepare(
            "SELECT bsr_id FROM `{$wpdb->prefix}ahc_browsers` WHERE bsr_name = %s",
            $name
        ));
        if (!$result) {
            $wpdb->insert('ahc_browsers', ['bsr_name' => $name, 'bsr_icon' => $icon]);
            return $wpdb->insert_id;
        }
        return $result;
    }

    private function getCountryId($name, $code)
    {
        global $wpdb;
        $result = $wpdb->get_var($wpdb->prepare(
            "SELECT ctr_id FROM `{$wpdb->prefix}ahc_countries` WHERE ctr_name = %s",
            $name
        ));
        if (!$result) {
            $wpdb->insert('ahc_countries', [
                'ctr_name' => $name,
                'ctr_internet_code' => strtoupper($code)
            ]);
            return $wpdb->insert_id;
        }
        return $result;
    }
}

// External functions
function ahcpro_sync_search_console()
{
    $gsc = new GoogleSearchConsoleIntegration();
    $result = $gsc->sync_search_console_data(30);
    return $result !== false && $result > 0;
}

function ahcpro_test_gsc_connection()
{
    $gsc = new GoogleSearchConsoleIntegration();
    return $gsc->test_connection();
}

// AJAX handlers
add_action('wp_ajax_ahcpro_manual_gsc_sync', 'ahcpro_handle_manual_gsc_sync');
function ahcpro_handle_manual_gsc_sync()
{
    if (!wp_verify_nonce($_POST['nonce'], 'ahcpro_manual_gsc_sync')) {
        wp_send_json_error('Security check failed');
        return;
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error('Insufficient permissions');
        return;
    }

    try {
        $gsc = new GoogleSearchConsoleIntegration();
        $test_result = $gsc->test_connection();

        if (!$test_result['success'] || !$test_result['current_site_found']) {
            wp_send_json_error('Connection failed: ' . $test_result['message']);
            return;
        }

        $result = $gsc->sync_search_console_data(30);

        update_option('ahcpro_last_gsc_sync', time());
        update_option('ahcpro_last_gsc_sync_count', $result);

        if ($result !== false) {
            $message = $result > 0
                ? "Successfully synced {$result} search queries from Google Search Console"
                : "Sync completed successfully but no new data was found (normal for new sites)";

            wp_send_json_success([
                'message' => $message,
                'count' => $result,
                'last_sync' => date('Y-m-d H:i:s')
            ]);
        } else {
            wp_send_json_error('Sync failed - please check your API credentials');
        }
    } catch (Exception $e) {
        error_log('GSC Manual Sync Error: ' . $e->getMessage());
        wp_send_json_error('Sync failed: ' . $e->getMessage());
    }
}

add_action('wp_ajax_ahcpro_get_gsc_sync_status', 'ahcpro_get_gsc_sync_status');
function ahcpro_get_gsc_sync_status()
{
    if (!current_user_can('manage_options')) {
        wp_send_json_error('Insufficient permissions');
        return;
    }

    $gsc = new GoogleSearchConsoleIntegration();
    $status = $gsc->get_sync_status();
    wp_send_json_success($status);
}

add_action('wp_ajax_test_gsc_connection', function () {
    if (!current_user_can('manage_options')) {
        wp_die();
    }

    try {
        $json_data = stripslashes($_POST['gsc_service_account_json']);
        $decoded = json_decode($json_data, true);

        if (json_last_error() !== JSON_ERROR_NONE || !isset($decoded['private_key']) || !isset($decoded['client_email'])) {
            wp_send_json_error(['message' => 'Invalid JSON format. Please check your Service Account JSON.']);
            return;
        }

        $old_json = get_option('ahcpro_gsc_service_account_json', '');
        update_option('ahcpro_gsc_service_account_json', $json_data);

        $test_result = ahcpro_test_gsc_connection();

        update_option('ahcpro_gsc_service_account_json', $old_json);

        if ($test_result['success']) {
            wp_send_json_success([
                'message' => $test_result['message'],
                'current_site_found' => isset($test_result['current_site_found']) ? $test_result['current_site_found'] : false
            ]);
        } else {
            wp_send_json_error(['message' => $test_result['message']]);
        }
    } catch (Exception $e) {
        wp_send_json_error(['message' => 'Test failed: ' . $e->getMessage()]);
    }
});

add_action('wp_ajax_test_gsc_connection_table', function () {
    if (!current_user_can('manage_options')) wp_die();

    $gsc = new GoogleSearchConsoleIntegration();
    $result = $gsc->test_connection();

    if (!empty($result['success']) && !empty($result['current_site_found'])) {
        wp_send_json_success([
            'message' => $result['message'],
            'current_site_found' => true
        ]);
    } else {
        $error_message = 'Search Console is not properly connected.';

        if (!empty($result['success']) && empty($result['current_site_found'])) {
            $error_message = $result['message'] ?: 'Your site was not found in the authorized Search Console properties.';
        } elseif (!empty($result['message'])) {
            $error_message = $result['message'];
        }

        wp_send_json_error([
            'message' => $error_message . ' Please check your <a href="admin.php?page=ahc_hits_counter_settings">API settings</a>.'
        ]);
    }
});

add_action('wp_ajax_ahcpro_disconnect_gsc', 'ahcpro_disconnect_gsc');
function ahcpro_disconnect_gsc()
{
    if (!wp_verify_nonce($_POST['nonce'], 'ahcpro_disconnect_gsc')) {
        wp_send_json_error('Security check failed');
        return;
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error('Insufficient permissions');
        return;
    }

    delete_option('ahcpro_gsc_service_account_json');
    delete_option('ahcpro_last_gsc_sync');
    delete_option('ahcpro_last_gsc_sync_count');

    wp_send_json_success(['message' => 'Successfully disconnected from Google Search Console']);
}

// Note: latest_search_words_callback function is in functions.php
// If it doesn't exist there, uncomment the code below:
/*
add_action("wp_ajax_latest_search_words", "latest_search_words_callback");
function latest_search_words_callback()
{
    $gsc = new GoogleSearchConsoleIntegration();
    $connection_test = $gsc->test_connection();

    if (!$connection_test['success'] || empty($connection_test['current_site_found'])) {
        $error_response = [
            'success' => false,
            'error' => true,
            'message' => 'Search Console data not available. Please check your API connection settings.',
            'detailed_message' => $connection_test['message'] ?? 'Connection test failed'
        ];
        wp_send_json($error_response);
        return;
    }

    try {
        if (isset($_REQUEST['page']) && $_REQUEST['page'] == "all") {
            $res = ahcpro_get_latest_search_key_words_used(1, false, "", "", $_REQUEST['fdt'], $_REQUEST['tdt']);

            if (empty($res) || $res === false) {
                wp_send_json([
                    'success' => false,
                    'error' => true,
                    'message' => 'No search data available yet. This is normal for new sites - Google needs 1-3 days to collect search data.',
                    'data' => []
                ]);
                return;
            }

            wp_send_json([
                'success' => true,
                'data' => $res
            ]);
        } else {
            $cnt = ahcpro_get_latest_search_key_words_used("", true, "", "", $_REQUEST['fdt'], $_REQUEST['tdt']);
            $recentVisitors = ahcpro_get_latest_search_key_words_used("", false, $_REQUEST['start'], $_REQUEST['length'], $_REQUEST['fdt'], $_REQUEST['tdt']);

            if (empty($recentVisitors) || $recentVisitors === false) {
                wp_send_json([
                    'success' => false,
                    'error' => true,
                    'message' => 'No search data available yet. This is normal for new sites - Google needs 1-3 days to collect search data.',
                    'draw' => intval($_REQUEST['draw'] ?? 0),
                    'recordsTotal' => 0,
                    'recordsFiltered' => 0,
                    'data' => []
                ]);
                return;
            }

            $arr = [
                'success' => true,
                'draw' => intval($_REQUEST['draw'] ?? 0),
                'recordsTotal' => $cnt,
                'recordsFiltered' => $cnt,
                'data' => $recentVisitors
            ];
            wp_send_json($arr);
        }
    } catch (Exception $e) {
        wp_send_json([
            'success' => false,
            'error' => true,
            'message' => 'Error retrieving search data: ' . $e->getMessage()
        ]);
    }
}
*/