File "controller.php"
Full Path: /home/fresvfqn/waterdamagerestorationandrepairsmithtown.com/wp-content/plugins/surerank/inc/google-search-console/controller.php
File size: 17.28 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Google Console Controller Class and Dashboard API Handler
*
* Contains the Controller class for Google Console API processing and the Dashboard class for REST API endpoints.
*
* @since 1.0.0
* @package SureRank
*/
/**
* Controller Class
*/
namespace SureRank\Inc\GoogleSearchConsole;
use DateTime;
use DateTimeZone;
use SureRank\Inc\Functions\Send_Json;
use SureRank\Inc\Traits\Get_Instance;
use WP_REST_Request;
/**
* APIs Class
*
* Responsible for processing APIs for Google Console.
*
* @since 1.0.0
*/
class Controller {
use Get_Instance;
/**
* Google API Base
*/
public const GOOGLE_ANALYTICS_API_BASE = 'https://www.googleapis.com/webmasters/v3/';
/**
* Google User Info API Base
*/
private const GOOGLE_USER_INFO_API_BASE = 'https://www.googleapis.com/oauth2/v2/userinfo';
/**
* Constructor
*
* @since 1.0.0
* @return void
*/
public function __construct() {
}
/**
* Get Sites
*
* @since 1.0.0
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_sites() {
// We are returning Array, because we will use Send_Json::success() to send the response.
return GoogleConsole::get_instance()->call_api( self::GOOGLE_ANALYTICS_API_BASE . 'sites' );
}
/**
* Get Matched Domain or Site URL
*
* Returns the matching site URL from Google Search Console sites list
*
* @return string|null Returns the matched site URL or null if no match found
*/
public function get_matched_site() {
$sites = $this->get_sites();
$sites = $sites['siteEntry'] ?? [];
if ( empty( $sites ) ) {
return null;
}
$current_site_url = get_site_url();
$current_site_url = str_replace( [ 'https://', 'http://', 'www.', '/', '//' ], '', $current_site_url );
foreach ( $sites as $site ) {
$site_url = $site['siteUrl'] ?? '';
$normalized_site_url = $site_url;
if ( str_starts_with( $site_url, 'sc-domain:' ) ) {
$normalized_site_url = str_replace( 'sc-domain:', '', $site_url );
} else {
$normalized_site_url = str_replace( [ 'https://', 'http://', 'www.', '/', '//' ], '', $site_url );
}
if ( $normalized_site_url === $current_site_url ) {
return $site_url;
}
}
return null;
}
/**
* Get Auth Status
*
* @since 1.0.0
* @return bool
*/
public function get_auth_status() {
return Auth::get_instance()->auth_check();
}
/**
* Get User Site URL
*
* @since 1.0.0
* @return string Returns the site URL or an error array.
*/
public function get_user_site_url() {
$site_url = Auth::get_instance()->get_credentials( null )['site_url'] ?? '';
if ( empty( $site_url ) ) {
Send_Json::error(
[
'message' => __( 'No site URL found', 'surerank' ),
'status' => 400,
]
);
}
return $site_url;
}
/**
* Get Site Traffic
*
* @since 1.0.0
* @param WP_REST_Request<array<string, mixed>> $request Request object.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_site_traffic( $request ) {
$site_url = $this->get_user_site_url();
$body = [
'startDate' => $this->get_start_date( $request ),
'endDate' => $this->get_end_date( $request ),
'dimensions' => [ 'date' ],
'rowLimit' => '500',
'dataState' => 'ALL',
];
$url = self::GOOGLE_ANALYTICS_API_BASE . 'sites/' . $this->get_site_url( $site_url ) . '/searchAnalytics/query';
$search_analytics_data = $this->get_search_analytics_data( $url, $body );
if ( isset( $search_analytics_data['rows'] ) && empty( $search_analytics_data['rows'] ) ) {
return [
'success' => false,
'message' => __( 'No data found for the selected date range.', 'surerank' ),
];
}
return $this->process_search_analytics_data( $search_analytics_data );
}
/**
* Process Search Analytics Data
*
* @since 1.0.0
* @param array<string, mixed>|array<int, array<string, mixed>> $search_analytics_data The search analytics data.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function process_search_analytics_data( $search_analytics_data ) {
if ( isset( $search_analytics_data['responseAggregationType'] ) ) {
unset( $search_analytics_data['responseAggregationType'] );
}
if ( ! isset( $search_analytics_data['rows'] ) || ! is_array( $search_analytics_data['rows'] ) ) {
$search_analytics_data['data'] = [];
return $search_analytics_data;
}
$search_analytics_data['data'] = array_map(
static function( $row ) {
if ( isset( $row['keys'][0] ) ) {
$row['date'] = $row['keys'][0];
unset( $row['keys'] );
unset( $row['ctr'] );
unset( $row['position'] );
}
return $row;
},
$search_analytics_data['rows']
);
unset( $search_analytics_data['rows'] );
return $search_analytics_data;
}
/**
* Get Clicks and Impressions
*
* @since 1.0.0
* @param WP_REST_Request<array<string, mixed>> $request The REST request object.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_clicks_and_impressions( $request ) {
$site_url = $this->get_user_site_url();
$current_body = [
'startDate' => $this->get_start_date( $request ),
'endDate' => $this->get_end_date( $request ),
'dataState' => 'ALL',
];
// Calculate previous period dates.
$date_range = ( strtotime( $current_body['endDate'] ) - strtotime( $current_body['startDate'] ) ) / ( 60 * 60 * 24 );
$previous_end_date = gmdate( 'Y-m-d', (int) strtotime( $current_body['startDate'] . ' -1 day' ) );
$previous_start_date = gmdate( 'Y-m-d', (int) strtotime( $previous_end_date . " -{$date_range} days" ) );
$previous_body = [
'startDate' => $previous_start_date,
'endDate' => $previous_end_date,
'dataState' => 'ALL',
];
$url = self::GOOGLE_ANALYTICS_API_BASE . 'sites/' . $this->get_site_url( $site_url ) . '/searchAnalytics/query';
// Get data for both periods.
$current_data = $this->get_search_analytics_data( $url, $current_body );
$previous_data = $this->get_search_analytics_data( $url, $previous_body );
$current_processed = isset( $current_data['rows'] ) && is_array( $current_data['rows'] )
? array_map(
static function( $row ) {
unset( $row['ctr'] );
unset( $row['position'] );
return $row;
},
$current_data['rows']
)
: [];
$previous_processed = isset( $previous_data['rows'] ) && is_array( $previous_data['rows'] )
? array_map(
static function( $row ) {
unset( $row['ctr'] );
unset( $row['position'] );
return $row;
},
$previous_data['rows']
)
: [];
$result = [];
$current_clicks = array_sum( array_column( $current_processed, 'clicks' ) );
$current_impressions = array_sum( array_column( $current_processed, 'impressions' ) );
$previous_clicks = array_sum( array_column( $previous_processed, 'clicks' ) );
$previous_impressions = array_sum( array_column( $previous_processed, 'impressions' ) );
$result['data']['clicks'] = [
'current' => $current_clicks,
'previous' => $previous_clicks,
'percentage' => $previous_clicks > 0
? round( ( $current_clicks - $previous_clicks ) / $previous_clicks * 100, 2 )
: ( $current_clicks > 0 ? 100 : 0 ),
];
$result['data']['impressions'] = [
'current' => $current_impressions,
'previous' => $previous_impressions,
'percentage' => $previous_impressions > 0
? round( ( $current_impressions - $previous_impressions ) / $previous_impressions * 100, 2 )
: ( $current_impressions > 0 ? 100 : 0 ),
];
return $result;
}
/**
* Get User Info
*
* @since 1.0.0
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_user_info() {
return GoogleConsole::get_instance()->call_api( self::GOOGLE_USER_INFO_API_BASE );
}
/**
* Get Search Analytics Data
*
* @since 1.0.0
* @param string $url The URL to get the data from.
* @param array<string, mixed> $body The body of the request.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_search_analytics_data( $url, $body ) {
return GoogleConsole::get_instance()->call_api( $url, 'POST', $body );
}
/**
* Get Start Date
*
* @since 1.0.0
* @param WP_REST_Request<array<string, mixed>> $request The REST request object.
* @return string
*/
public function get_start_date( $request ) {
$timezone = new DateTimeZone( apply_filters( 'surerank_search_console_timezone', 'America/Los_Angeles' ) );
return $request->get_param( 'startDate' )
? ( new DateTime( $request->get_param( 'startDate' ), $timezone ) )->format( 'Y-m-d' )
: ( new DateTime( '-365 days', $timezone ) )->format( 'Y-m-d' );
}
/**
* Get End Date
*
* @since 1.0.0
* @param WP_REST_Request<array<string, mixed>> $request The REST request object.
* @return string
*/
public function get_end_date( $request ) {
$timezone = new DateTimeZone( apply_filters( 'surerank_search_console_timezone', 'America/Los_Angeles' ) );
return $request->get_param( 'endDate' )
? ( new DateTime( $request->get_param( 'endDate' ), $timezone ) )->format( 'Y-m-d' )
: ( new DateTime( '-2 day', $timezone ) )->format( 'Y-m-d' );
}
/**
* Get Content Performance
*
* @since 1.0.0
* @param WP_REST_Request<array<string, mixed>> $request The REST request object.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_content_performance( $request ) {
$site_url = $this->get_user_site_url();
$current_body = [
'startDate' => $this->get_start_date( $request ),
'endDate' => $this->get_end_date( $request ),
'dimensions' => [ 'page' ],
'rowLimit' => '500',
'dataState' => 'ALL',
];
// Calculate previous period dates.
$date_range = ( strtotime( $current_body['endDate'] ) - strtotime( $current_body['startDate'] ) ) / ( 60 * 60 * 24 );
$previous_end_date = gmdate( 'Y-m-d', (int) strtotime( $current_body['startDate'] . ' -1 day' ) );
$previous_start_date = gmdate( 'Y-m-d', (int) strtotime( $previous_end_date . " -{$date_range} days" ) );
$previous_body = [
'startDate' => $previous_start_date,
'endDate' => $previous_end_date,
'dimensions' => [ 'page' ],
'rowLimit' => '500',
'dataState' => 'ALL',
];
$url = self::GOOGLE_ANALYTICS_API_BASE . 'sites/' . $this->get_site_url( $site_url ) . '/searchAnalytics/query';
$current_search_analytics_data = $this->get_search_analytics_data( $url, $current_body );
$previous_search_analytics_data = $this->get_search_analytics_data( $url, $previous_body );
// Process current data with safety checks.
$current_data = [];
if ( isset( $current_search_analytics_data['rows'] ) && is_array( $current_search_analytics_data['rows'] ) ) {
$current_data = array_map(
static function ( $row ) {
$processed_row = $row;
if ( isset( $row['keys'][0] ) ) {
$processed_row['url'] = $row['keys'][0];
unset( $processed_row['keys'] );
}
return $processed_row;
},
$current_search_analytics_data['rows']
);
}
// Process previous data with safety checks.
$previous_data = [];
if ( isset( $previous_search_analytics_data['rows'] ) && is_array( $previous_search_analytics_data['rows'] ) ) {
$previous_data = array_map(
static function ( $row ) {
$processed_row = $row;
if ( isset( $row['keys'][0] ) ) {
$processed_row['url'] = $row['keys'][0];
unset( $processed_row['keys'] );
}
return $processed_row;
},
$previous_search_analytics_data['rows']
);
}
$combined_data = [];
foreach ( $current_data as $current_row ) {
$url = $current_row['url'];
$previous_row = array_filter(
$previous_data,
static function ( $row ) use ( $url ) {
return $row['url'] === $url;
}
);
if ( reset( $previous_row ) === false ) {
$previous_row = [
'clicks' => 0,
'impressions' => 0,
'ctr' => 0,
'position' => 0,
];
} else {
$previous_row = reset( $previous_row );
}
// Calculate clicks change.
$clicks_change = 0.0;
if ( $previous_row['clicks'] > 0 ) {
$clicks_change = round( ( $current_row['clicks'] - $previous_row['clicks'] ) / $previous_row['clicks'] * 100, 2 );
} elseif ( $current_row['clicks'] > 0 ) {
$clicks_change = 100.0;
}
// Calculate impressions change.
$impressions_change = 0.0;
if ( $previous_row['impressions'] > 0 ) {
$impressions_change = round( ( $current_row['impressions'] - $previous_row['impressions'] ) / $previous_row['impressions'] * 100, 2 );
} elseif ( $current_row['impressions'] > 0 ) {
$impressions_change = 100.0;
}
// Calculate CTR change.
$ctr_change = 0.0;
if ( $previous_row['ctr'] > 0 ) {
$ctr_change = round( ( $current_row['ctr'] - $previous_row['ctr'] ) / $previous_row['ctr'] * 100, 2 );
} elseif ( $current_row['ctr'] > 0 ) {
$ctr_change = 100.0;
}
// Calculate position change.
$position_change = 0.0;
if ( $previous_row['position'] > 0 ) {
$position_change = round( ( $current_row['position'] - $previous_row['position'] ) / $previous_row['position'] * 100, 2 );
} elseif ( $current_row['position'] > 0 ) {
$position_change = 100.0;
}
$combined_data[] = [
'url' => $url,
'current' => [
'clicks' => $current_row['clicks'],
'impressions' => $current_row['impressions'],
'ctr' => $current_row['ctr'],
'position' => $current_row['position'],
],
'changes' => [
'clicks' => $clicks_change,
'impressions' => $impressions_change,
'ctr' => $ctr_change,
'position' => $position_change,
],
];
}
return [
'success' => true,
'data' => $combined_data,
];
}
/**
* Get Google Console User Details
*
* @since 1.0.0
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_google_console_user_details() {
return [
'name' => Auth::get_instance()->get_credentials( 'name' ) ?? '',
'email' => Auth::get_instance()->get_credentials( 'email' ) ?? '',
'gravatar' => Auth::get_instance()->get_credentials( 'gravatar' ) ?? '',
];
}
/**
* Get Site URL
*
* @param string $site_url The site URL to get.
* @return string The formatted site URL.
*/
private function get_site_url( $site_url ) {
if ( strpos( $site_url, 'sc-domain' ) !== false ) {
$site_url = $site_url;
} else {
$site_url = urlencode( $site_url );
}
return $site_url;
}
/**
* Auto Create and Verify Property
*
* Creates and verifies a Search Console property following the documented flow
* Simplified version that only handles URL-prefix properties since subdomain logic moved to frontend
*
* @since 1.4.0
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function auto_create_and_verify_property() {
return SiteVerification::get_instance()->auto_create_and_verify_property();
}
/**
* Verify Existing Property
*
* Verifies an existing Search Console property that's already added but not verified
*
* @since 1.4.0
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function verify_existing_property() {
return SiteVerification::get_instance()->verify_existing_property();
}
/**
* Get Verification Token
*
* Gets the HTML tag verification token for a site using Site Verification API
*
* @since 1.4.0
* @param string $site_url The site URL to get verification token for.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function get_verification_token( $site_url ) {
return SiteVerification::get_instance()->get_verification_token( $site_url );
}
/**
* Store Verification Token
*
* Stores the verification token for meta tag injection
*
* @since 1.4.0
* @param string $token The verification token.
* @return void
*/
public function store_verification_token( $token ) {
SiteVerification::get_instance()->store_verification_token( $token );
}
/**
* Get Stored Verification Token
*
* Gets the stored verification token
*
* @since 1.4.0
* @return string|false
*/
public function get_stored_verification_token() {
return SiteVerification::get_instance()->get_stored_verification_token();
}
/**
* Verify Site
*
* Verifies a site using Site Verification API
*
* @since 1.4.0
* @param string $site_url The site URL to verify.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function verify_site( $site_url ) {
return SiteVerification::get_instance()->verify_site( $site_url );
}
/**
* Call Site Verification API
*
* Makes direct wp_remote_request calls to Site Verification API with specific error handling
*
* @since 1.4.0
* @param string $endpoint The API endpoint URL.
* @param string $method HTTP method (GET, POST, PUT, etc.).
* @param array<string, mixed> $args Request arguments.
* @return array<string, mixed> API response or error array.
*/
public function call_site_verification_api( $endpoint, $method = 'GET', $args = [] ) {
return SiteVerification::get_instance()->call_site_verification_api( $endpoint, $method, $args );
}
/**
* Add Site
*
* Adds a site to Google Search Console
*
* @since 1.4.0
* @param string $site_url The site URL to add.
* @return array<string, mixed>|array<int, array<string, mixed>>
*/
public function add_site( $site_url ) {
return SiteVerification::get_instance()->add_site( $site_url );
}
}