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()
]);
}
}
*/