diff --git a/assets/css/custom.css b/assets/css/custom.css index 15326c0..d4980cd 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -222,3 +222,64 @@ body { background-color: #0056b3; border-color: #0056b3; } + +.ga-status-connected { + color: #28A745; + font-weight: bold; + margin-bottom: 16px; +} + +.btn-secondary { + background-color: #6C757D; + color: #FFFFFF; + border: 1px solid #6C757D; +} + +.ga-property-id-section { + background-color: #FFFFFF; + padding: 24px; + border-radius: 8px; + border: 1px solid #DEE2E6; + margin-bottom: 32px; +} + +.ga-data-section { + background-color: #FFFFFF; + padding: 24px; + border-radius: 8px; + border: 1px solid #DEE2E6; + margin-bottom: 32px; +} + +.ga-data-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 24px; +} + +.ga-data-card { + background-color: #F8F9FA; + padding: 24px; + border-radius: 8px; + text-align: center; +} + +.ga-data-card h3 { + font-size: 1rem; + color: #6C757D; + margin-bottom: 8px; +} + +.ga-data-value { + font-size: 2rem; + font-weight: bold; +} + +.demo-notice { + background-color: #FFF3CD; + color: #856404; + padding: 16px; + border: 1px solid #FFEEBA; + border-radius: 4px; + margin-bottom: 24px; +} \ No newline at end of file diff --git a/dashboard.php b/dashboard.php index f1ee517..e424fe9 100644 --- a/dashboard.php +++ b/dashboard.php @@ -1,23 +1,158 @@ ["value" => "12,345", "change" => "+5.2%"], - "conversions" => ["value" => "678", "change" => "+12.8%"], - "ad_spend" => ["value" => "$2,456", "change" => "-3.1%"], - "clicks" => ["value" => "34,567", "change" => "+8.4%"], - "keyword_ranks" => ["value" => "5", "change" => "+2"], - "backlinks" => ["value" => "1,234", "change" => "+50"], - "site_audit_score" => ["value" => "88/100", "change" => "+3"], - "calls" => ["value" => "98", "change" => "+15%"], -]; +// dashboard.php -// Dummy data for the main chart (e.g., sessions over time) +// Start session to store GA data and manage state +session_start(); + +// Configuration and token management +$google_analytics_connected = file_exists('db/google_tokens.json'); +$ga_property_id = null; +$ga_data = null; +$ga_error = null; + +// Handle GA Property ID submission +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ga_property_id'])) { + $property_id = trim($_POST['ga_property_id']); + if (!empty($property_id)) { + file_put_contents('db/ga_property_id.txt', $property_id); + // Clear previous GA data on new ID + if (isset($_SESSION['ga_data'])) unset($_SESSION['ga_data']); + if (isset($_SESSION['ga_error'])) unset($_SESSION['ga_error']); + + // Set a flag to indicate we need to fetch data + $_SESSION['ga_fetch_required'] = true; + + header('Location: dashboard.php'); + exit(); + } +} + +// Load GA Property ID if it exists +if (file_exists('db/ga_property_id.txt')) { + $ga_property_id = file_get_contents('db/ga_property_id.txt'); +} + +// --- GOOGLE ANALYTICS DATA FETCHING --- +// Fetch data only if connected, property ID is set, and a fetch is required +if ($google_analytics_connected && $ga_property_id && isset($_SESSION['ga_fetch_required'])) { + unset($_SESSION['ga_fetch_required']); // Unset the flag to prevent re-fetching on every page load + + require_once 'google-config.php'; + $tokens_json = file_get_contents('db/google_tokens.json'); + $tokens = json_decode($tokens_json, true); + + if (!is_array($tokens) || !isset($tokens['access_token']) || !isset($tokens['refresh_token'])) { + $_SESSION['ga_error'] = "Google token file is corrupt or invalid. Please disconnect and reconnect."; + error_log("Invalid google_tokens.json content: " . $tokens_json); + header('Location: dashboard.php'); + exit(); + } + + $access_token = $tokens['access_token']; + $refresh_token = $tokens['refresh_token']; + + // --- 1. Refresh Access Token --- + $token_url = 'https://oauth2.googleapis.com/token'; + $token_params = [ + 'client_id' => GOOGLE_CLIENT_ID, + 'client_secret' => GOOGLE_CLIENT_SECRET, + 'refresh_token' => $refresh_token, + 'grant_type' => 'refresh_token' + ]; + + $ch = curl_init($token_url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($token_params)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code == 200) { + $new_token_data = json_decode($response, true); + if (isset($new_token_data['access_token'])) { + $tokens['access_token'] = $new_token_data['access_token']; + file_put_contents('db/google_tokens.json', json_encode($tokens)); + $access_token = $new_token_data['access_token']; + + // --- 2. Fetch Data from Google Analytics Data API --- + $analytics_api_url = "https://analyticsdata.googleapis.com/v1beta/properties/{$ga_property_id}:runReport"; + + $api_request_body = [ + 'dateRanges' => [['startDate' => '28daysAgo', 'endDate' => 'today']], + 'metrics' => [ + ['name' => 'sessions'], + ['name' => 'totalUsers'], + ['name' => 'screenPageViews'], + ['name' => 'bounceRate'] + ] + ]; + + $ch_analytics = curl_init($analytics_api_url); + curl_setopt($ch_analytics, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $access_token, + 'Content-Type: application/json' + ]); + curl_setopt($ch_analytics, CURLOPT_POST, true); + curl_setopt($ch_analytics, CURLOPT_POSTFIELDS, json_encode($api_request_body)); + curl_setopt($ch_analytics, CURLOPT_RETURNTRANSFER, true); + + $analytics_response = curl_exec($ch_analytics); + $analytics_http_code = curl_getinfo($ch_analytics, CURLINFO_HTTP_CODE); + curl_close($ch_analytics); + + if ($analytics_http_code == 200) { + $report = json_decode($analytics_response, true); + if (isset($report['rows'][0]['metricValues'])) { + $metricValues = $report['rows'][0]['metricValues']; + $_SESSION['ga_data'] = [ + 'sessions' => $metricValues[0]['value'] ?? '0', + 'users' => $metricValues[1]['value'] ?? '0', + 'page_views' => $metricValues[2]['value'] ?? '0', + 'bounce_rate' => isset($metricValues[3]['value']) ? round($metricValues[3]['value'] * 100, 2) . '%' : '0%', + ]; + } else { + $_SESSION['ga_error'] = "No data returned from Analytics. Check if the property ID is correct and has recent data."; + } + } else { + $error_response = json_decode($analytics_response, true); + $error_message = "HTTP {$analytics_http_code}"; + if (is_array($error_response) && isset($error_response['error']['message'])) { + $error_message = $error_response['error']['message']; + } else { + error_log("Unknown Google API Error. Code: {$analytics_http_code}. Response: " . $analytics_response); + } + $_SESSION['ga_error'] = "Error fetching Analytics data: " . $error_message; + } + } else { + $_SESSION['ga_error'] = "Failed to refresh token, new access token not found."; + } + } else { + $_SESSION['ga_error'] = "Error refreshing Google token. Please try disconnecting and reconnecting."; + } + + // Redirect to clean the URL and prevent re-submission + header('Location: dashboard.php'); + exit(); +} + +// Load data from session if available +if (isset($_SESSION['ga_data'])) { + $ga_data = $_SESSION['ga_data']; + unset($_SESSION['ga_data']); // Clear after loading +} +if (isset($_SESSION['ga_error'])) { + $ga_error = $_SESSION['ga_error']; + unset($_SESSION['ga_error']); // Clear after loading +} + + +// Dummy data for other sections (can be replaced later) +$client_name = "Example Client"; $chart_data = [ "labels" => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], "values" => [5000, 5500, 6200, 7100, 8000, 8500, 9200, 9800, 10500, 11200, 12000, 12345], ]; - ?> @@ -44,32 +179,72 @@ $chart_data = [
- Connect to Google Analytics + +
Connected to Google Analytics
+ Disconnect + + Connect to Google Analytics +

's Dashboard

- +
-
- $metric): ?> -
-

-

-

- -

-
- -
+ + +
+

Enter your Google Analytics Property ID

+

This is required to fetch data from your Analytics account.

+
+ + +
+
+ +
+

Google Analytics Overview (Property ID: )

+ + +
+ Error: +
+ + + +
+
+

Sessions

+

+
+
+

Users

+

+
+
+

Page Views

+

+
+
+

Bounce Rate

+

+
+
+ +
+ Attempting to fetch live data from Google Analytics... If this message persists, please check your Property ID or try reconnecting. +
+ +
+ +
-

Sessions Over Time

- +

Sessions Over Time (Sample)

- + \ No newline at end of file diff --git a/db/ga_property_id.txt b/db/ga_property_id.txt new file mode 100644 index 0000000..82b27f4 --- /dev/null +++ b/db/ga_property_id.txt @@ -0,0 +1 @@ +G-XE06RVWK6S \ No newline at end of file diff --git a/db/google_tokens.json b/db/google_tokens.json new file mode 100644 index 0000000..02ebf52 --- /dev/null +++ b/db/google_tokens.json @@ -0,0 +1 @@ +{"access_token":"ya29.a0ATi6K2uk0Gb_RDk8IRqUCYlK4xhbAoOR6sM6T9fudqgQtVDahmlHDshpy5sKcqUtpmhCT5HBPWrDfaSb1GALm5iSkbRagO0Sq_drZfhFYA4G22OwS1J418mYgBsI0BS4_SdYO8Biwy-U2nhATQMqkBqxUAnETW9Vp_7qgmgbPYIp01MCqXJ9b_5CJm-673rL4GT2YlcaCgYKAXQSARYSFQHGX2MivzBSL3ClF2zW9KP5SQnJ8g0206","expires_in":3599,"refresh_token":"1\/\/04GWLH1ZGkD_lCgYIARAAGAQSNwF-L9Ir2NCtmqTH8Cxr8O5uXkkX4TvTzC2dXXFTrS7amm1DVPKtlfupCJyvopZHDy2wY5blJoI","scope":"https:\/\/www.googleapis.com\/auth\/analytics.readonly","token_type":"Bearer"} \ No newline at end of file diff --git a/db/perm_test.txt b/db/perm_test.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/db/perm_test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/google-config.php b/google-config.php new file mode 100644 index 0000000..63ac2f1 --- /dev/null +++ b/google-config.php @@ -0,0 +1,5 @@ + GOOGLE_CLIENT_ID, diff --git a/google_callback.php b/google_callback.php index f79f379..60d2b68 100644 --- a/google_callback.php +++ b/google_callback.php @@ -1,7 +1,22 @@ '; - print_r($token_data); - echo ''; - // Store the access token in the session for immediate use + if (json_last_error() !== JSON_ERROR_NONE) { + log_error("Failed to decode JSON response: " . $response); + echo "An error occurred. Please try again later. (Code: 2)"; + exit(); + } + + // Ensure the db directory exists + if (!is_dir(dirname('db/google_tokens.json'))) { + mkdir(dirname('db/google_tokens.json'), 0775, true); + } + + if (file_put_contents('db/google_tokens.json', json_encode($token_data)) === false) { + log_error("Failed to write tokens to db/google_tokens.json"); + echo "An error occurred. Please try again later. (Code: 3)"; + exit(); + } + $_SESSION['google_access_token'] = $token_data['access_token']; - // Redirect back to the dashboard - // header('Location: dashboard.php'); - // exit(); + header('Location: dashboard.php'); + exit(); } else { - echo "Error fetching access token: " . $response; + log_error("Error fetching access token. HTTP Code: " . $http_code . ", Response: " . $response); + echo "An error occurred while trying to connect to Google. Please check your credentials in setup.php and try again."; } } else { + log_error("Authorization code not found in callback."); echo "Authorization code not found."; } diff --git a/google_disconnect.php b/google_disconnect.php new file mode 100644 index 0000000..e6fc7de --- /dev/null +++ b/google_disconnect.php @@ -0,0 +1,7 @@ +

© AgencyAnalytics. All rights reserved.

+

+ Privacy Policy · Terms of Service +

diff --git a/logs/debug.log b/logs/debug.log new file mode 100644 index 0000000..2575b22 --- /dev/null +++ b/logs/debug.log @@ -0,0 +1,2 @@ +Callback script started +Callback script started diff --git a/logs/google_auth.log b/logs/google_auth.log new file mode 100644 index 0000000..1287b4a --- /dev/null +++ b/logs/google_auth.log @@ -0,0 +1,4 @@ +[2025-11-18 19:43:05] Error fetching access token. HTTP Code: 400, Response: { + "error": "invalid_grant", + "error_description": "Bad Request" +} diff --git a/logs/perm_test.txt b/logs/perm_test.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/logs/perm_test.txt @@ -0,0 +1 @@ +test diff --git a/privacy.php b/privacy.php new file mode 100644 index 0000000..9f1fe67 --- /dev/null +++ b/privacy.php @@ -0,0 +1,43 @@ + + + + + + Privacy Policy + + + +
+

Privacy Policy

+

Last updated:

+ +
+ Disclaimer: This is a template and not legal advice. You should replace this content with your own privacy policy. +
+ +

Introduction

+

Welcome to our application. We are committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our application.

+ +

Information We Collect

+

We may collect information about you in a variety of ways. The information we may collect via the Application includes:

+
    +
  • Personal Data: Personally identifiable information, such as your name, shipping address, email address, and telephone number, and demographic information, such as your age, gender, hometown, and interests, that you voluntarily give to us when you register with the Application.
  • +
  • Data from Google Analytics: We connect to your Google Analytics account to retrieve and display your website traffic data. We only request read-only access and do not store your data permanently on our servers.
  • +
+ +

Use of Your Information

+

Having accurate information about you permits us to provide you with a smooth, efficient, and customized experience. Specifically, we may use information collected about you via the Application to:

+
    +
  • Create and manage your account.
  • +
  • Display your Google Analytics data on a personal dashboard.
  • +
  • Email you regarding your account or order.
  • +
+ +

Contact Us

+

If you have questions or comments about this Privacy Policy, please contact us.

+ +
+ Back to Home +
+ + diff --git a/setup.php b/setup.php new file mode 100644 index 0000000..d469460 --- /dev/null +++ b/setup.php @@ -0,0 +1,101 @@ + + + + + + + Google API Setup + + + + +
+
+

Google API Setup

+ + +
+ + + +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + diff --git a/terms.php b/terms.php new file mode 100644 index 0000000..4890d0a --- /dev/null +++ b/terms.php @@ -0,0 +1,46 @@ + + + + + + Terms of Service + + + +
+

Terms of Service

+

Last updated:

+ +
+ Disclaimer: This is a template and not legal advice. You should replace this content with your own terms of service. +
+ +

1. Agreement to Terms

+

By using our application, you agree to be bound by these Terms of Service. If you do not agree to these Terms, do not use the application.

+ +

2. License to Use

+

We grant you a limited, non-exclusive, non-transferable, revocable license to use the application for your personal, non-commercial purposes, subject to these Terms.

+ +

3. User Accounts

+

When you create an account with us, you must provide us with information that is accurate, complete, and current at all times. Failure to do so constitutes a breach of the Terms, which may result in immediate termination of your account on our Service.

+ +

4. Intellectual Property

+

The Service and its original content, features, and functionality are and will remain the exclusive property of the application owner and its licensors.

+ +

5. Termination

+

We may terminate or suspend your account immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.

+ +

6. Governing Law

+

These Terms shall be governed and construed in accordance with the laws of the jurisdiction in which the application owner is based, without regard to its conflict of law provisions.

+ +

7. Changes to Terms

+

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. We will provide at least 30 days' notice prior to any new terms taking effect.

+ +

Contact Us

+

If you have any questions about these Terms, please contact us.

+ +
+ Back to Home +
+ +