transient_timeout = 60 * 60 * 12; // Define admin page URLs for navigation. $this->plugin_base = 'admin.php?page=analytify-dashboard'; $this->plugin_settings_base = 'admin.php?page=analytify-settings'; // Set authentication date format with timezone. $this->auth_date_format = gmdate( 'l jS F Y h:i:s A' ) . ' ' . date_default_timezone_get(); // Sanitize page parameter for security. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading URL parameter for display purposes $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; if ( $current_page && strpos( $current_page, 'analytify-settings' ) === 0 ) { $this->exception = get_option( 'analytify_profile_exception' ); $this->ga4_exception = get_option( 'analytify_ga4_exceptions' ); } $this->modules = WPANALYTIFY_Utils::get_pro_modules(); // Setup Settings. if ( class_exists( 'WP_Analytify_Settings' ) ) { $this->settings = new WP_Analytify_Settings(); } $this->is_reporting_in_ga4 = 'ga4' === WPANALYTIFY_Utils::get_ga_mode() ? true : false; // Initialize connection on init hook to ensure themes are loaded. add_action( 'init', array( $this, 'init_connection' ) ); } /** * Initialize connection to Google Analytics. * * This method is hooked to 'init' to ensure that themes (functions.php) are loaded * before the connection is attempted. This allows custom hooks to fire correctly. * * @since 7.1.4 */ public function init_connection() { if ( true === $this->is_reporting_in_ga4 ) { // Rankmath Instant Indexing addon Compatibility. // Sanitize page parameter for security. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading URL parameter for display purposes $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; if ( ( $current_page && 'instant-indexing' === $current_page ) || strpos( wp_get_referer(), 'instant-indexing' ) !== false ) { return; } if ( 'on' === $this->settings->get_option( 'user_advanced_keys', 'wp-analytify-advanced', '' ) ) { $this->user_client_id = $this->settings->get_option( 'client_id', 'wp-analytify-advanced' ); $this->user_client_secret = $this->settings->get_option( 'client_secret', 'wp-analytify-advanced' ); } try { $this->analytify_pa_connect_v2(); } catch ( Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { // translators: Reset authentication error message. printf( esc_html__( '%1$s Oops, Try to %2$s Reset %3$s Authentication. %4$s %7$s %4$s %5$s Don\'t worry, This error message is only visible to Administrators. %6$s %2$s', 'wp-analytify' ), '

', '', '', '
', '', '', esc_textarea( $e->getMessage() ) ); } } } // Set cache time directly since after_setup_theme has already fired before init. $this->set_cache_time(); $this->analytify_set_tracking_mode(); } /** * This function grabs the data from Google Analytics for individual posts/pages. * * @param string $metrics The metrics to retrieve. * @param string $start_date The start date for the report. * @param string $end_date The end date for the report. * @param boolean $dimensions Optional dimensions for the report. * @param boolean $sort Optional sorting for the report. * @param boolean $filter Optional filters for the report. * @param boolean $limit Optional limit for the report. * @param string $name Optional name for caching. * @return void */ public function pa_get_analytics( $metrics, $start_date, $end_date, $dimensions = false, $sort = false, $filter = false, $limit = false, $name = '' ) { if ( $this->is_reporting_in_ga4 ) { return; } try { $this->service = new Analytify_Google_Service_Analytics( $this->client ); $params = array(); if ( $dimensions ) { $params['dimensions'] = $dimensions; } if ( $sort ) { $params['sort'] = $sort; } if ( $filter ) { $params['filters'] = $filter; } if ( $limit ) { $params['max-results'] = $limit; } $profile_id = $this->settings->get_option( 'profile_for_posts', 'wp-analytify-profile' ); if ( ! $profile_id ) { return false; } $transient_key = 'analytify_transient_'; $cache_result = get_transient( $transient_key . md5( $name . $profile_id . $start_date . $end_date . $filter ) ); // Note: This hard coded setting should be removed in future versions. $is_custom_api = $this->settings->get_option( 'user_advanced_keys', 'wp-analytify-advanced' ); if ( 'on' !== $is_custom_api ) { // If exception, return if the cache result else return the error. $exception = get_transient( 'analytify_quota_exception' ); if ( $exception ) { return $this->tackle_exception( $exception, $cache_result ); } } // If custom keys set. Fetch fresh result always. if ( 'on' === $is_custom_api || false === $cache_result ) { $result = $this->service->data_ga->get( 'ga:' . $profile_id, $start_date, $end_date, $metrics, $params ); set_transient( $transient_key . md5( $name . $profile_id . $start_date . $end_date . $filter ), $result, $this->get_cache_time() ); return $result; } else { return $cache_result; } } catch ( Analytify_Google_Service_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { echo "
"; // translators: Error message for logged in users. printf( esc_html__( '%1$s Oops, Something went wrong. %2$s %5$s %2$s %3$s Don\'t worry, This error message is only visible to Administrators. %4$s %2$s ', 'wp-analytify' ), '

', '
', '', '', esc_html( $e->getMessage() ) ); echo '
'; } } catch ( Analytify_Google_Auth_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { echo "
"; // translators: Reset authentication error message. printf( esc_html__( '%1$s Oops, Try to %3$s Reset %4$s Authentication. %2$s %7$s %2$s %5$s Don\'t worry, This error message is only visible to Administrators. %6$s %2$s', 'wp-analytify' ), '

', '
', '', '', '', '', esc_textarea( $e->getMessage() ) ); echo '
'; } } catch ( Analytify_Google_IO_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { echo "
"; // translators: Error message. printf( esc_html__( '%1$s Oops! %2$s %5$s %2$s %3$s Don\'t worry, This error message is only visible to Administrators. %4$s %2$s', 'wp-analytify' ), '

', '
', '', '', esc_html( $e->getMessage() ) ); echo '
'; } } } /** * Mock Function to resist GA3 removal conflicts. * * @param string $metrics The metrics to retrieve. * @param string $start_date The start date for the report. * @param string $end_date The end date for the report. * @param boolean $dimensions Optional dimensions for the report. * @param boolean $sort Optional sorting for the report. * @param boolean $filter Optional filters for the report. * @param boolean $limit Optional limit for the report. * @param string $name Optional name for caching. * @return null|false */ public function pa_get_analytics_dashboard( $metrics, $start_date, $end_date, $dimensions = false, $sort = false, $filter = false, $limit = false, $name = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed if ( $this->is_reporting_in_ga4 ) { return null; } return false; } /** * Mock Function to resist GA3 removal conflicts. * * @param string $metrics The metrics to retrieve. * @param string $start_date The start date for the report. * @param string $end_date The end date for the report. * @param boolean $dimensions Optional dimensions for the report. * @param boolean $sort Optional sorting for the report. * @param boolean $filter Optional filters for the report. * @param boolean $limit Optional limit for the report. * @param string $name Optional name for caching. * @return null|false */ public function pa_get_analytics_dashboard_via_rest( $metrics, $start_date, $end_date, $dimensions = false, $sort = false, $filter = false, $limit = false, $name = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed if ( $this->is_reporting_in_ga4 ) { return null; } return false; } /** * This function grabs the data from Google Analytics For dashboard. * * @param string $profile Google Analytic Profile Id. * @param string $metrics Metrics. * @param string $start_date Start date of stats. * @param string $end_date End date of stats. * @param string $dimensions Dimensions. * @param string $sort Sort. * @param string $filter Filter. * @param string $limit How many stats to show. * * @return array Return array of stats. */ public function analytify_get_analytics( $profile, $metrics, $start_date, $end_date, $dimensions = false, $sort = false, $filter = false, $limit = false ) { if ( $this->is_reporting_in_ga4 ) { return null; } try { if ( class_exists( 'Analytify_Google_Service_Analytics' ) ) { $this->service = new Analytify_Google_Service_Analytics( $this->client ); } $params = array(); if ( $dimensions ) { $params['dimensions'] = $dimensions; } if ( $sort ) { $params['sort'] = $sort; } if ( $filter ) { $params['filters'] = $filter; } if ( $limit ) { $params['max-results'] = $limit; } if ( 'single' === $profile ) { $profile_id = $this->settings->get_option( 'profile_for_posts', 'wp-analytify-profile' ); } else { $profile_id = $this->settings->get_option( 'profile_for_dashboard', 'wp-analytify-profile' ); } if ( ! $profile_id ) { return false; } return $this->service->data_ga->get( 'ga:' . $profile_id, $start_date, $end_date, $metrics, $params ); } catch ( Analytify_Google_Service_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { // translators: Error message. printf( esc_html__( '%1$s Oops, Something went wrong. %2$s %5$s %2$s %3$s Don\'t worry, This error message is only visible to Administrators. %4$s %2$s ', 'wp-analytify' ), '

', '
', '', '', esc_textarea( $e->getMessage() ) ); } } catch ( Analytify_Google_Auth_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { // translators: Error message. printf( esc_html__( '%1$s Oops, Try to %3$s Reset %4$s Authentication. %2$s %7$s %2$s %5$s Don\'t worry, This error message is only visible to Administrators. %6$s %2$s', 'wp-analytify' ), '

', '
', '', '', '', '', esc_textarea( $e->getMessage() ) ); } } catch ( Analytify_Google_IO_Exception $e ) { // Show error message only for logged in users. if ( current_user_can( 'manage_options' ) ) { // translators: Error message. printf( esc_html__( '%1$s Oops! %2$s %5$s %2$s %3$s Don\'t worry, This error message is only visible to Administrators. %4$s %2$s', 'wp-analytify' ), '

', '
', '', '', esc_html( $e->getMessage() ) ); echo ''; } } } /** * Fetch reports from Google Analytics Data API. * * @param string $name 'test-report-name' Its the key used to store reports in transient as cache. * @param array $metrics Array of metrics to fetch. * @param array $date_range Date range for the report. * @param array $dimensions Array of dimensions. * @param array $order_by Sorting configuration. * @param array $filters Filter configuration. * @param integer array $limit Positive integer to limit report rows. * @param boolean $cached Whether to use cached results. * * @return array { * 'headers' => { * ... * }, * 'rows' => { * ... * } * } * @version 7.0.1 * @throws Exception When API request fails. */ public function get_reports( $name, $metrics, $date_range, $dimensions = array(), $order_by = array(), $filters = array(), $limit = 0, $cached = true ) { $logger = function_exists( 'analytify_get_logger' ) ? analytify_get_logger() : null; $property_id = WPANALYTIFY_Utils::get_reporting_property(); // Don't use cache if custom API keys are in use. if ( 'on' === $this->settings->get_option( 'user_advanced_keys', 'wp-analytify-advanced' ) ) { $cached = false; } // To override the caching. $cached = apply_filters( 'analytify_set_caching_to', $cached ); if ( $cached ) { $cache_key = 'analytify_transient_' . md5( $name . $property_id . $date_range['start'] . $date_range['end'] ); $report_cache = get_transient( $cache_key ); if ( $report_cache ) { return $report_cache; } } $reports = array(); $dimension_filters = array(); // Default response array. $default_response = array( 'headers' => array(), 'rows' => array(), 'error' => array(), 'aggregations' => array(), ); try { // Main request body for the report. $request_body = array( 'dateRanges' => array( array( 'startDate' => isset( $date_range['start'] ) ? $date_range['start'] : 'today', 'endDate' => isset( $date_range['end'] ) ? $date_range['end'] : 'today', ), ), 'metricAggregations' => array( 1 ), // TOTAL = 1; COUNT = 4; MINIMUM = 5; MAXIMUM = 6. ); // Set metrics. if ( $metrics ) { $send_metrics = array(); foreach ( $metrics as $value ) { $send_metrics[] = array( 'name' => $value ); } $request_body['metrics'] = $send_metrics; } // Add dimensions. if ( $dimensions ) { $send_dimensions = array(); foreach ( $dimensions as $value ) { $send_dimensions[] = array( 'name' => $value ); } $request_body['dimensions'] = $send_dimensions; } // Order report by metric or dimension. if ( $order_by ) { $order_by_request = array(); $is_desc = ( empty( $order_by['order'] ) || 'desc' !== $order_by['order'] ) ? false : true; if ( 'metric' === $order_by['type'] ) { $order_by_request = array( 'metric' => array( 'metric_name' => isset( $order_by['name'] ) ? $order_by['name'] : '', ), 'desc' => $is_desc, ); } elseif ( 'dimension' === $order_by['type'] ) { $order_by_request = array( 'dimension' => array( 'dimension_name' => $order_by['name'], ), 'desc' => $is_desc, ); } $request_body['orderBys'] = array( $order_by_request ); } // Filters for the report. if ( $filters ) { $dimension_filters = array(); // Initialize an empty array for filters. foreach ( $filters['filters'] as $filter_data ) { if ( 'dimension' === $filter_data['type'] ) { if ( isset( $filter_data['not_expression'] ) && $filter_data['not_expression'] ) { // Handle 'not_expression' logic. $dimension_filters[] = array( 'not_expression' => array( 'filter' => array( 'field_name' => $filter_data['name'], 'string_filter' => array( 'match_type' => $filter_data['match_type'], 'value' => $filter_data['value'], 'case_sensitive' => true, ), ), ), ); } else { // Standard dimension filter. $dimension_filters[] = array( 'filter' => array( 'field_name' => $filter_data['name'], 'string_filter' => array( 'match_type' => $filter_data['match_type'], 'value' => $filter_data['value'], 'case_sensitive' => true, ), ), ); } } elseif ( 'metric' === $filter_data['type'] ) { // Note: Add metric filter handling here. // Currently no implementation for metric filters. // This is intentionally left empty for future implementation. // No action needed for metric filters at this time. // Skip metric filters without affecting dimension_filters array. continue; } } if ( $dimension_filters ) { $group_type = ( isset( $filters['logic'] ) && 'OR' === $filters['logic'] ) ? 'or_group' : 'and_group'; $dimension_filter_construct = array( $group_type => array( 'expressions' => $dimension_filters, ), ); $request_body['dimensionFilter'] = $dimension_filter_construct; } } // Set limit. if ( 0 < $limit ) { $request_body['limit'] = $limit; } // Get access token (this function should be implemented by you). $token = $this->analytify_get_google_token(); // Validate that token is an array and has the expected structure. if ( ! is_array( $token ) || ! isset( $token['access_token'] ) ) { if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( 'Invalid or missing Google Analytics token in get_reports.', array( 'source' => 'get_reports', 'report_name' => $name, 'token_type' => gettype( $token ), 'has_access_token' => isset( $token['access_token'] ), ) ); } return array(); } $access_token = $token['access_token']; // Prepare the cURL request URL for GA4 API. $url = 'https://analyticsdata.googleapis.com/v1beta/properties/' . $property_id . ':runReport'; // Send the request using wp_remote_post. $response = wp_remote_post( $url, array( 'headers' => array( 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $request_body ), ) ); // Check for errors in the response. if ( is_wp_error( $response ) ) { throw new Exception( $response->get_error_message() ); } // Parse the response body. $reports = json_decode( wp_remote_retrieve_body( $response ), true ); // If the response doesn't contain rows, handle it accordingly. if ( ! isset( $reports['rows'] ) ) { return $default_response; } } catch ( \Throwable $th ) { if ( method_exists( $th, 'getStatus' ) && method_exists( $th, 'getBasicMessage' ) ) { $default_response['error'] = array( 'status' => $th->getStatus(), 'message' => $th->getBasicMessage(), ); if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( 'Exception in get_reports API call.', array( 'source' => 'get_reports', 'report_name' => $name, 'status' => $th->getStatus(), 'message' => $th->getBasicMessage(), 'exception_type' => get_class( $th ), ) ); } } elseif ( method_exists( $th, 'getMessage' ) ) { $default_response['error'] = array( 'status' => 'Token Expired', 'message' => $th->getMessage(), ); if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( 'Exception in get_reports API call - token expired.', array( 'source' => 'get_reports', 'report_name' => $name, 'message' => $th->getMessage(), 'exception_type' => get_class( $th ), ) ); } } return $default_response; } // Format the reports using your existing function. $formatted_reports = $this->analytify_format_ga_reports( $reports ); if ( empty( $formatted_reports ) ) { return $default_response; } // Cache the response if caching is enabled. if ( $cached ) { $this->analytify_handle_report_cache( $cache_key, $formatted_reports, $name, $cached ); } return $formatted_reports; } /** * Format reports data fetched from Google Analytics Data API. * * For references check folder for class definitions: lib\Google\vendor\google\analytics-data\src\V1beta. * * @param array $reports The reports data to format. * @return array */ public function analytify_format_ga_reports( $reports ) { $metric_header_data = array(); $dimension_header_data = array(); $aggregations = array(); $rows = array(); // Get metric headers. if ( isset( $reports['metricHeaders'] ) ) { foreach ( $reports['metricHeaders'] as $metric_header ) { $metric_header_data[] = $metric_header['name']; } } // Get dimension headers. if ( isset( $reports['dimensionHeaders'] ) ) { foreach ( $reports['dimensionHeaders'] as $dimension_header ) { $dimension_header_data[] = $dimension_header['name']; } } $headers = array_merge( $metric_header_data, $dimension_header_data ); // Bind metrics and dimensions to rows. if ( isset( $reports['rows'] ) ) { foreach ( $reports['rows'] as $row ) { $metric_data = array(); $dimension_data = array(); // Process metric values. if ( isset( $row['metricValues'] ) ) { $index_metric = 0; foreach ( $row['metricValues'] as $value ) { $metric_data[ $metric_header_data[ $index_metric ] ] = $value['value']; ++$index_metric; } } // Process dimension values. if ( isset( $row['dimensionValues'] ) ) { $index_dimension = 0; foreach ( $row['dimensionValues'] as $value ) { $dimension_data[ $dimension_header_data[ $index_dimension ] ] = $value['value']; ++$index_dimension; } } // Combine metric and dimension data. $rows[] = array_merge( $metric_data, $dimension_data ); } } // Get metric aggregations (totals). if ( isset( $reports['totals'] ) ) { foreach ( $reports['totals'] as $total ) { $index_metric = 0; if ( isset( $total['metricValues'] ) ) { foreach ( $total['metricValues'] as $value ) { $aggregations[ $metric_header_data[ $index_metric ] ] = $value['value']; ++$index_metric; } } } } // Format and return the data. $formatted_data = array( 'headers' => $headers, 'rows' => $rows, 'aggregations' => $aggregations, ); return $formatted_data; } /** * Query the search console api and return the response. * Since SC can have two types of domain properties. * We will first go with the sc-domain prefix with property * if it fails we will use the second domain type using 'https://' * * @param string $transient_name The transient name for caching. * @param array $dates The date range for the query. * @param int $limit The limit for the results. * * @since 5.0.0 * @version 9.0.0 */ public function get_search_console_stats( $transient_name, $dates = array(), $limit = 10 ) { $logger = function_exists( 'analytify_get_logger' ) ? analytify_get_logger() : null; if ( class_exists( 'QM' ) ) { QM::info( 'Analytify: Getting Google Analytics token for Search Console stats.' ); } $token = $this->analytify_get_google_token(); if ( ! is_array( $token ) || ! isset( $token['access_token'] ) ) { return array( 'error' => array( 'Invalid or missing Google Analytics token.' ) ); } $access_token = $token['access_token']; $tracking_stream_info = get_option( 'analytify_tracking_property_info' ); try { $stream_url = ( isset( $tracking_stream_info['url'] ) && ! empty( $tracking_stream_info['url'] ) ) ? $tracking_stream_info['url'] : null; } catch ( \Throwable $th ) { if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( 'Error fetching stream URL', array( 'source' => 'analytify_fetch_stream_url', 'message' => $th->getMessage(), ) ); } if ( empty( $stream_url ) ) { return array( 'error' => array( 'status' => 'No Stats Available', 'message' => __( 'No URL found for the selected stream', 'wp-analytify' ), ), ); } } // Validate stream URL. if ( empty( $stream_url ) ) { return array( 'error' => array( 'status' => 'No Stats Available', 'message' => __( 'No URL found for the selected stream', 'wp-analytify' ), ), ); } // Sanitize URL. $stream_url = trim( $stream_url ); $stream_url = esc_url_raw( $stream_url ); // Extract domain (handles ports and IPv6). $domain_stream_url_filtered = preg_replace( '/^(https?:\/\/)?(www\.)?([^\/\s:]+(?::\d+)?|\[[^\]]+\])(\/.*)?$/i', '$3', $stream_url ); $domain_stream_url_filtered = preg_replace( '/:\d+$/', '', $domain_stream_url_filtered ); // Remove port. $domain_stream_url_filtered = str_replace( array( '[', ']' ), '', $domain_stream_url_filtered ); // Remove IPv6 brackets. // Build candidate URLs for Search Console API. $urls = array( 'sc-domain:' . $domain_stream_url_filtered, 'https://' . $domain_stream_url_filtered, 'https://www.' . $domain_stream_url_filtered, 'http://' . $domain_stream_url_filtered, 'http://www.' . $domain_stream_url_filtered, 'https://' . rtrim( $domain_stream_url_filtered, '/' ) . '/', // URL-prefix format. ); // Remove duplicates to avoid redundant API calls. $urls = array_unique( $urls ); $base_url = ANALYTIFY_GOOGLE_SEARCH_CONSOLE_API_URL; $start_date = $dates['start'] ?? 'yesterday'; $end_date = $dates['end'] ?? 'today'; // Track responses: prefer domains with data, fallback to any accepted domain. $accepted_domains_with_data = array(); $accepted_domains_no_data = array(); foreach ( $urls as $url ) { try { $query_data = array( 'startDate' => $start_date, 'endDate' => $end_date, 'dimensions' => array( 'query' ), 'rowLimit' => $limit, ); // Make request to Search Console API using WordPress HTTP API. $http_response = wp_remote_post( $base_url . rawurlencode( $url ) . '/searchAnalytics/query', array( 'headers' => array( 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $query_data ), 'timeout' => 30, 'sslverify' => true, // Explicitly ensure SSL verification. ) ); if ( is_wp_error( $http_response ) ) { if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( sprintf( 'HTTP request failed for domain "%s": %s', $url, $http_response->get_error_message() ), array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, ) ); } continue; // Continue to next URL. } $http_code = wp_remote_retrieve_response_code( $http_response ); $response_body = wp_remote_retrieve_body( $http_response ); // Log all HTTP responses for debugging, but categorize them. if ( 200 === $http_code ) { $decoded = json_decode( $response_body, true ); // Validate JSON decode result. if ( json_last_error() !== JSON_ERROR_NONE ) { if ( $logger && method_exists( $logger, 'error' ) ) { $logger->error( sprintf( 'JSON decode failed for domain "%s": %s', $url, json_last_error_msg() ), array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, ) ); } continue; } // Ensure decoded result is an array. if ( ! is_array( $decoded ) ) { if ( $logger && method_exists( $logger, 'error' ) ) { $logger->error( sprintf( 'Unexpected JSON response for domain "%s": not an array', $url ), array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, ) ); } continue; } $row_count = count( $decoded['rows'] ?? array() ); // Log domain check result for debugging. if ( $logger && method_exists( $logger, 'info' ) ) { $logger->info( sprintf( 'Domain "%s" - HTTP %d, %d rows found', $url, $http_code, $row_count ), array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, 'http_code' => $http_code, 'row_count' => $row_count, ) ); } // Store this response - prefer domains with data. if ( $row_count > 0 ) { $accepted_domains_with_data[] = array( 'url' => $url, 'data' => $decoded, ); if ( $logger && method_exists( $logger, 'info' ) ) { $logger->info( 'Domain categorized as HAVING DATA', array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, 'row_count' => $row_count, ) ); } } else { $accepted_domains_no_data[] = array( 'url' => $url, 'data' => $decoded, ); if ( $logger && method_exists( $logger, 'info' ) ) { $logger->info( 'Domain categorized as NO DATA', array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, 'row_count' => $row_count, ) ); } } } elseif ( 200 !== $http_code ) { // Log non-200 with code only; do not log response body (may contain sensitive data). if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( sprintf( 'Domain "%s" returned HTTP %d', $url, $http_code ), array( 'source' => 'analytify_fetch_search_console_stats', 'domain' => $url, 'http_code' => $http_code, ) ); } } } catch ( \Throwable $th ) { // Continue to next URL on exception. continue; } } // Choose the best domain after checking ALL URLs. $chosen_domain = null; // Priority 1: Any domain with actual keyword data (prefer first one found). if ( ! empty( $accepted_domains_with_data ) ) { $chosen_domain = $accepted_domains_with_data[0]; // Use first domain that has data. } // phpcs:ignore Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace // Priority 2: If NO domains have data, use first accepted domain (fallback). elseif ( ! empty( $accepted_domains_no_data ) ) { $chosen_domain = $accepted_domains_no_data[0]; // Use first accepted domain as fallback. } // Return the chosen domain's data. if ( $chosen_domain ) { return array( 'response' => $chosen_domain['data'], ); } // No domains were accepted at all. if ( $logger && method_exists( $logger, 'warning' ) ) { $logger->warning( 'FINAL FAILURE: No domain accepted', array( 'source' => 'analytify_fetch_search_console_stats', 'site' => $domain_stream_url_filtered, ) ); } return array( 'error' => array( 'status' => "No Stats Available for $domain_stream_url_filtered", 'message' => __( 'Analytify gets GA4 keyword stats from Search Console. Make sure the site is verified and you have owner access.', 'wp-analytify' ), ), ); } } // End of class } // End of if class exists