diff --git a/modules/gapi/config.xml b/modules/gapi/config.xml
new file mode 100644
index 000000000..acc350fbd
--- /dev/null
+++ b/modules/gapi/config.xml
@@ -0,0 +1,12 @@
+
+
+ gapi
+
+
+
+
+
+ 1
+ 0
+
+
\ No newline at end of file
diff --git a/modules/gapi/gapi.php b/modules/gapi/gapi.php
new file mode 100644
index 000000000..31e5276a6
--- /dev/null
+++ b/modules/gapi/gapi.php
@@ -0,0 +1,407 @@
+
+* @copyright 2007-2013 PrestaShop SA
+* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
+* International Registered Trademark & Property of PrestaShop SA
+*/
+
+if (!defined('_PS_VERSION_'))
+ exit;
+
+class Gapi extends Module
+{
+ public function __construct($type = null)
+ {
+ $this->name = 'gapi';
+ $this->tab = 'administration';
+ $this->version = 0.9;
+ $this->author = 'PrestaShop';
+ $this->need_instance = 0;
+
+ parent::__construct();
+
+ $this->displayName = $this->l('Google Analytics API');
+ }
+
+ public function getContent()
+ {
+ // You can switch to the 1.3 API by replacing the following function call by $this->api_1_3_getContent()
+ return $this->api_3_0_getContent();
+ }
+
+ public function api_3_0_authenticate()
+ {
+ // https://developers.google.com/accounts/docs/OAuth2WebServer
+ $params = array(
+ 'response_type' => 'code',
+ 'client_id' => Configuration::get('PS_GAPI30_CLIENT_ID_TMP'),
+ 'scope' => 'https://www.googleapis.com/auth/analytics.readonly',
+ 'redirect_uri' => Tools::getShopDomain(true, false).__PS_BASE_URI__.'modules/'.$this->name.'/oauth2callback.php',
+ 'state' => $this->context->employee->id.'-'.Tools::encrypt($this->context->employee->id.Configuration::get('PS_GAPI30_CLIENT_ID_TMP')),
+ 'approval_prompt' => 'force',
+ 'access_type' => 'offline'
+ );
+ Tools::redirectLink('https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
+ }
+
+ public function api_3_0_refreshtoken()
+ {
+ $params = array(
+ 'client_id' => Configuration::get('PS_GAPI30_CLIENT_ID'),
+ 'client_secret' => Configuration::get('PS_GAPI30_CLIENT_SECRET'),
+ 'grant_type' => 'authorization_code'
+ );
+
+ // https://developers.google.com/accounts/docs/OAuth2WebServer#offline
+ if (Configuration::get('PS_GAPI30_REFRESH_TOKEN'))
+ $params['refresh_token'] = Configuration::get('PS_GAPI30_REFRESH_TOKEN');
+ else
+ {
+ $params['code'] = Configuration::get('PS_GAPI30_AUTHORIZATION_CODE');
+ $params['redirect_uri'] = Tools::getShopDomain(true, false).__PS_BASE_URI__.'modules/'.$this->name.'/oauth2callback.php';
+ }
+
+ $stream_context = stream_context_create(array(
+ 'http' => array(
+ 'method'=> 'POST',
+ 'content' => http_build_query($params),
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ 'timeout' => 5,
+ )
+ ));
+
+ if (!$response_json = Tools::file_get_contents('https://accounts.google.com/o/oauth2/token', false, $stream_context))
+ return false;
+
+ $response = Tools::jsonDecode($response_json, true);
+ Configuration::updateValue('PS_GAPI30_ACCESS_TOKEN', $response['access_token']);
+ Configuration::updateValue('PS_GAPI30_TOKEN_EXPIRATION', time() + (int)$response['expires_in']);
+ if (isset($response['refresh_token']))
+ Configuration::updateValue('PS_GAPI30_REFRESH_TOKEN', $response['refresh_token']);
+ return true;
+ }
+
+ public function api_3_0_getContent()
+ {
+ $html = '';
+ if (Tools::isSubmit('PS_GAPI30_CLIENT_ID'))
+ {
+ Configuration::updateValue('PS_GAPI30_REQUEST_URI_TMP', dirname($_SERVER['REQUEST_URI']).'/'.AdminController::$currentIndex.'&configure='.$this->name.'&token='.Tools::getAdminTokenLite('AdminModules'));
+ Configuration::updateValue('PS_GAPI30_CLIENT_ID_TMP', trim(Tools::getValue('PS_GAPI30_CLIENT_ID')));
+ Configuration::updateValue('PS_GAPI30_CLIENT_SECRET_TMP', trim(Tools::getValue('PS_GAPI30_CLIENT_SECRET')));
+ Configuration::updateValue('PS_GAPI30_PROFILE_TMP', trim(Tools::getValue('PS_GAPI30_PROFILE')));
+ // This will redirect the user to Google API authentication page
+ $this->api_3_0_authenticate();
+ }
+ elseif (Tools::getValue('oauth2callback') == 'error')
+ $html .= $this->displayError('Google API: Access denied');
+ elseif (Tools::getValue('oauth2callback') == 'undefined')
+ $html .= $this->displayError('Something wrong happened with Google API authorization');
+ elseif (Tools::getValue('oauth2callback') == 'success')
+ {
+ if ($this->api_3_0_refreshtoken())
+ $html .= $this->displayConfirmation('Google API Authorization granted');
+ else
+ $html .= $this->displayError('Google API Authorization granted but access token cannot be retrieved');
+ }
+
+ if (Configuration::get('PS_GAPI30_CLIENT_ID') && Configuration::get('PS_GAPI30_CLIENT_SECRET') && Configuration::get('PS_GAPI30_PROFILE'))
+ {
+ $result_test = $this->api_3_0_requestReportData('', 'ga:visits,ga:uniquePageviews', date('Y-m-d', strtotime('-1 day')), date('Y-m-d', strtotime('-1 day')), null, null, 1, 1);
+ if (!$result_test)
+ $html .= $this->displayError('Cannot retrieve test results');
+ else
+ $html .= $this->displayConfirmation(sprintf($this->l('Yesterday, your store received the visit of %d people for a total of %d unique page views.'), $result_test[0]['metrics']['visits'], $result_test[0]['metrics']['uniquePageviews']));
+ }
+
+ $helper = new HelperOptions($this);
+ $helper->id = $this->id;
+ $helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
+ $helper->token = Tools::getAdminTokenLite('AdminModules');
+ $helper->module = $this;
+
+ $fields_options = array(
+ 'general' => array(
+ 'title' => $this->l('Google Analytics API'),
+ 'icon' => $this->_path.'logo.png',
+ 'fields' => $fields = array(
+ 'PS_GAPI30_CLIENT_ID' => array(
+ 'title' => $this->l('Client ID'),
+ 'validation' => 'isEmail',
+ 'type' => 'text'
+ ),
+ 'PS_GAPI30_CLIENT_SECRET' => array(
+ 'title' => $this->l('Client Secret'),
+ 'validation' => 'isNothing',
+ 'type' => 'text'
+ ),
+ 'PS_GAPI30_PROFILE' => array(
+ 'title' => $this->l('Profile'),
+ 'validation' => 'isUnsignedInt',
+ 'type' => 'text',
+ 'desc' => sprintf($this->l('You can find your profile ID in the address bar of your browser while accessing Analytics report: %s.'), ''.$this->l('see screenshot').'')
+ )
+ ),
+ 'submit' => array('title' => $this->l('Authenticate')),
+ )
+ );
+
+ return $html.$helper->generateOptions($fields_options);
+ }
+
+ public function api_3_0_oauth2callback()
+ {
+ if (!Tools::getValue('state'))
+ die ('token missing');
+ $state = explode('-', Tools::getValue('state'));
+ if (count($state) != 2)
+ die ('token malformed');
+ if ($state[1] != Tools::encrypt($state[0].Configuration::get('PS_GAPI30_CLIENT_ID_TMP')))
+ die ('token not valid');
+
+ $oauth2callback = 'undefined';
+ $url = Configuration::get('PS_GAPI30_REQUEST_URI_TMP');
+ if (Tools::getValue('error'))
+ $oauth2callback = 'error';
+ elseif (Tools::getValue('code'))
+ {
+ Configuration::updateValue('PS_GAPI30_CLIENT_ID', Configuration::get('PS_GAPI30_CLIENT_ID_TMP'));
+ Configuration::updateValue('PS_GAPI30_CLIENT_SECRET', Configuration::get('PS_GAPI30_CLIENT_SECRET_TMP'));
+ Configuration::updateValue('PS_GAPI30_PROFILE', Configuration::get('PS_GAPI30_PROFILE_TMP'));
+ Configuration::updateValue('PS_GAPI30_AUTHORIZATION_CODE', Tools::getValue('code'));
+ $oauth2callback = 'success';
+ }
+
+ Configuration::deleteByName('PS_GAPI30_CLIENT_ID_TMP');
+ Configuration::deleteByName('PS_GAPI30_CLIENT_SECRET_TMP');
+ Configuration::deleteByName('PS_GAPI30_PROFILE_TMP');
+ Configuration::deleteByName('PS_GAPI30_REQUEST_URI_TMP');
+ Configuration::deleteByName('PS_GAPI30_REFRESH_TOKEN');
+
+ Tools::redirectAdmin($url.'&oauth2callback='.$oauth2callback);
+ }
+
+ // https://developers.google.com/analytics/devguides/reporting/core/dimsmets
+ // requestReportData('ga:country', 'ga:visits', '2013-08-25', '2013-08-25', null, null, 1, 1000);
+ protected function api_3_0_requestReportData($dimensions, $metrics, $date_from, $date_to, $sort = null, $filters = null, $start = 1, $limit = 30)
+ {
+ if (Configuration::get('PS_GAPI30_TOKEN_EXPIRATION') < time() + 30 && !$this->api_3_0_refreshtoken())
+ return false;
+ $bearer = Configuration::get('PS_GAPI30_ACCESS_TOKEN');
+
+ $params = array(
+ 'ids' => 'ga:'.Configuration::get('PS_GAPI30_PROFILE'),
+ 'dimensions' => $dimensions,
+ 'metrics' => $metrics,
+ 'sort' => $sort ? $sort : $metrics,
+ 'start-date' => $date_from,
+ 'end-date' => $date_to,
+ 'start-index' => $start,
+ 'max-results' => $limit,
+ );
+ if ($filters !== null)
+ $params['filters'] = $filters;
+ $content = str_replace('&', '&', urldecode(http_build_query($params)));
+
+ $stream_context = stream_context_create(array(
+ 'http' => array(
+ 'method'=> 'GET',
+ 'header' => 'Authorization: Bearer '.$bearer."\r\n",
+ 'timeout' => 5,
+ )
+ ));
+ if (!$response_json = Tools::file_get_contents('https://www.googleapis.com/analytics/v3/data/ga?'.$content, false, $stream_context))
+ return false;
+
+ // https://developers.google.com/analytics/devguides/reporting/core/v3/reference
+ $response = Tools::jsonDecode($response_json, true);
+
+ $result = array();
+ foreach ($response['rows'] as $row)
+ {
+ $metrics = array();
+ $dimensions = array();
+ foreach($row as $key => $value)
+ if ($response['columnHeaders'][$key]['columnType'] == 'DIMENSION')
+ $dimensions[str_replace('ga:', '', $response['columnHeaders'][$key]['name'])] = $value;
+ elseif ($response['columnHeaders'][$key]['columnType'] == 'METRIC')
+ $metrics[str_replace('ga:', '', $response['columnHeaders'][$key]['name'])] = $value;
+ $result[] = array('metrics' => $metrics, 'dimensions' => $dimensions);
+ }
+ return $result;
+ }
+
+ public function api_1_3_getContent()
+ {
+ $html = '';
+ if (Tools::isSubmit('PS_GAPI13_EMAIL'))
+ {
+ if ($this->api_1_3_authenticate(Tools::getValue('PS_GAPI13_EMAIL'), Tools::getValue('PS_GAPI13_PASSWORD')))
+ {
+ Configuration::updateValue('PS_GAPI13_EMAIL', Tools::getValue('PS_GAPI13_EMAIL'));
+ Configuration::updateValue('PS_GAPI13_PASSWORD', Tools::getValue('PS_GAPI13_PASSWORD'));
+ Configuration::updateValue('PS_GAPI13_PROFILE', Tools::getValue('PS_GAPI13_PROFILE'));
+ }
+ else
+ $html .= $this->displayError($this->l('Authentication failed'));
+ }
+
+ if (Configuration::get('PS_GAPI13_EMAIL') && Configuration::get('PS_GAPI13_PASSWORD') && Configuration::get('PS_GAPI13_PROFILE'))
+ {
+ $result_test = $this->api_3_0_requestReportData('', 'ga:visits,ga:uniquePageviews', date('Y-m-d', strtotime('-1 day')), date('Y-m-d', strtotime('-1 day')), null, null, 1, 1);
+ if (!$result_test)
+ $html .= $this->displayError('Cannot retrieve test results');
+ else
+ $html .= $this->displayConfirmation(sprintf($this->l('Yesterday, your store received the visit of %d people for a total of %d unique page views.'), $result_test[0]['metrics']['visits'], $result_test[0]['metrics']['uniquePageviews']));
+ }
+
+ $helper = new HelperOptions($this);
+ $helper->id = $this->id;
+ $helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
+ $helper->token = Tools::getAdminTokenLite('AdminModules');
+ $helper->module = $this;
+
+ $fields_options = array(
+ 'general' => array(
+ 'title' => $this->l('Google Analytics API'),
+ 'icon' => $this->_path.'logo.png',
+ 'fields' => $fields = array(
+ 'PS_GAPI13_EMAIL' => array(
+ 'title' => $this->l('Email'),
+ 'validation' => 'isEmail',
+ 'type' => 'text'
+ ),
+ 'PS_GAPI13_PASSWORD' => array(
+ 'title' => $this->l('Password'),
+ 'validation' => 'isNothing',
+ 'type' => 'password'
+ ),
+ 'PS_GAPI13_PROFILE' => array(
+ 'title' => $this->l('Profile'),
+ 'validation' => 'isUnsignedInt',
+ 'type' => 'text',
+ 'desc' => sprintf($this->l('You can find your profile ID in the address bar of your browser while accessing Analytics report: %s.'), ''.$this->l('see screenshot').'')
+ )
+ ),
+ 'submit' => array('title' => $this->l('Authenticate'), 'class' => 'button'),
+ )
+ );
+
+ return $html.$helper->generateOptions($fields_options);
+ }
+
+ protected function api_1_3_authenticate($email, $password)
+ {
+ $stream_context = stream_context_create(array(
+ 'http' => array(
+ 'method'=> 'POST',
+ 'content' => 'accountType=GOOGLE&Email='.urlencode($email).'&Passwd='.urlencode($password).'&source=GAPI-1.3&service=analytics',
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ 'timeout' => 5,
+ )
+ ));
+
+ if (!$response = Tools::file_get_contents('https://www.google.com/accounts/ClientLogin', false, $stream_context))
+ return false;
+
+ parse_str(str_replace(array("\n", "\r\n"), '&', $response), $response_array);
+ if (!is_array($response_array) || !isset($response_array['Auth']) || empty($response_array['Auth']))
+ return false;
+
+ $this->auth_token = $response_array['Auth'];
+ return true;
+ }
+
+ // requestReportData('ga:country', 'ga:visits', '2013-08-25', '2013-08-25', null, null, 1, 1000);
+ protected function api_1_3_requestReportData($dimensions, $metrics, $date_from, $date_to, $sort = null, $filters = null, $start = 1, $limit = 30)
+ {
+ if (!$this->api_1_3_authenticate(Configuration::get('PS_GAPI13_EMAIL'), Configuration::get('PS_GAPI13_PASSWORD')))
+ return false;
+
+ $params = array(
+ 'ids' => 'ga:'.Configuration::get('PS_GAPI13_PROFILE'),
+ 'dimensions' => $dimensions,
+ 'metrics' => $metrics,
+ 'sort' => $sort ? $sort : $metrics,
+ 'start-date' => $date_from,
+ 'end-date' => $date_to,
+ 'start-index' => $start,
+ 'max-results' => $limit,
+ );
+ if ($filters !== null)
+ $params['filters'] = $filters;
+ $content = str_replace('&', '&', urldecode(http_build_query($params)));
+
+ $stream_context = stream_context_create(array(
+ 'http' => array(
+ 'method'=> 'GET',
+ 'header' => "Authorization: GoogleLogin auth=".$this->auth_token."\r\n",
+ 'timeout' => 5,
+ )
+ ));
+ if (!$response = Tools::file_get_contents('https://www.google.com/analytics/feeds/data?'.$content, false, $stream_context))
+ return false;
+
+ $xml = simplexml_load_string($response);
+
+ /* Meta data not useful at this time */
+ /*
+ $report_root_parameters = array();
+ $report_aggregate_metrics = array();
+ $google_results = $xml->children('http://schemas.google.com/analytics/2009');
+ foreach($google_results->dataSource->property as $property_attributes)
+ $report_root_parameters[str_replace('ga:', '', $property_attributes->attributes()->name)] = strval($property_attributes->attributes()->value);
+ foreach($google_results->aggregates->metric as $aggregate_metric)
+ {
+ $key = str_replace('ga:', '', $aggregate_metric->attributes()->name);
+ $metric_value = strval($aggregate_metric->attributes()->value);
+ if (preg_match('/^(\d+\.\d+)|(\d+E\d+)|(\d+.\d+E\d+)$/', $metric_value))
+ $report_aggregate_metrics[$key] = floatval($metric_value);
+ else
+ $report_aggregate_metrics[$key] = intval($metric_value);
+ }
+ */
+
+ $result = array();
+ foreach($xml->entry as $entry)
+ {
+ $metrics = array();
+ foreach ($entry->children('http://schemas.google.com/analytics/2009')->metric as $metric)
+ {
+ $key = str_replace('ga:','',$metric->attributes()->name);
+ $metric_value = strval($metric->attributes()->value);
+ if (preg_match('/^(\d+\.\d+)|(\d+E\d+)|(\d+.\d+E\d+)$/',$metric_value))
+ $metrics[$key] = floatval($metric_value);
+ else
+ $metrics[$key] = intval($metric_value);
+ }
+
+ $dimensions = array();
+ foreach($entry->children('http://schemas.google.com/analytics/2009')->dimension as $dimension)
+ $dimensions[str_replace('ga:', '', $dimension->attributes()->name)] = strval($dimension->attributes()->value);
+
+ $result[] = array('metrics' => $metrics, 'dimensions' => $dimensions);
+ }
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/modules/gapi/index.php b/modules/gapi/index.php
new file mode 100644
index 000000000..05972f21b
--- /dev/null
+++ b/modules/gapi/index.php
@@ -0,0 +1,33 @@
+
+* @copyright 2007-2013 PrestaShop SA
+* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
+* International Registered Trademark & Property of PrestaShop SA
+*/
+
+header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+header("Location: ../");
+exit;
\ No newline at end of file
diff --git a/modules/gapi/logo.gif b/modules/gapi/logo.gif
new file mode 100644
index 000000000..73538897c
Binary files /dev/null and b/modules/gapi/logo.gif differ
diff --git a/modules/gapi/logo.png b/modules/gapi/logo.png
new file mode 100644
index 000000000..5e6d77799
Binary files /dev/null and b/modules/gapi/logo.png differ
diff --git a/modules/gapi/oauth2callback.php b/modules/gapi/oauth2callback.php
new file mode 100644
index 000000000..397759e6e
--- /dev/null
+++ b/modules/gapi/oauth2callback.php
@@ -0,0 +1,7 @@
+api_3_0_oauth2callback();
diff --git a/modules/gapi/screenshots/Google API - 01 - Start.png b/modules/gapi/screenshots/Google API - 01 - Start.png
new file mode 100644
index 000000000..012d078c7
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 01 - Start.png differ
diff --git a/modules/gapi/screenshots/Google API - 02 - Services.png b/modules/gapi/screenshots/Google API - 02 - Services.png
new file mode 100644
index 000000000..5b45d4494
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 02 - Services.png differ
diff --git a/modules/gapi/screenshots/Google API - 03 - Terms.png b/modules/gapi/screenshots/Google API - 03 - Terms.png
new file mode 100644
index 000000000..a6c889e4f
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 03 - Terms.png differ
diff --git a/modules/gapi/screenshots/Google API - 04 - Terms.png b/modules/gapi/screenshots/Google API - 04 - Terms.png
new file mode 100644
index 000000000..c2a9c4582
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 04 - Terms.png differ
diff --git a/modules/gapi/screenshots/Google API - 05 - Services OK.png b/modules/gapi/screenshots/Google API - 05 - Services OK.png
new file mode 100644
index 000000000..158634aba
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 05 - Services OK.png differ
diff --git a/modules/gapi/screenshots/Google API - 06 - API Access.png b/modules/gapi/screenshots/Google API - 06 - API Access.png
new file mode 100644
index 000000000..f7a8235ea
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 06 - API Access.png differ
diff --git a/modules/gapi/screenshots/Google API - 07 - Create Client ID.png b/modules/gapi/screenshots/Google API - 07 - Create Client ID.png
new file mode 100644
index 000000000..1936f57cc
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 07 - Create Client ID.png differ
diff --git a/modules/gapi/screenshots/Google API - 08 - Create Client ID.png b/modules/gapi/screenshots/Google API - 08 - Create Client ID.png
new file mode 100644
index 000000000..d263b30ba
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 08 - Create Client ID.png differ
diff --git a/modules/gapi/screenshots/Google API - 09 - API Access created.png b/modules/gapi/screenshots/Google API - 09 - API Access created.png
new file mode 100644
index 000000000..570f7cacb
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 09 - API Access created.png differ
diff --git a/modules/gapi/screenshots/Google API - 10 - Profile ID.png b/modules/gapi/screenshots/Google API - 10 - Profile ID.png
new file mode 100644
index 000000000..baa3d9e28
Binary files /dev/null and b/modules/gapi/screenshots/Google API - 10 - Profile ID.png differ