From c90817324337d389e23cdb3fdc006c6583d9d29f Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Mon, 19 May 2014 01:53:35 +0600 Subject: [PATCH 1/6] Followed modern PHP conding style with Namespace, Composer, PSR-0 support --- .gitignore | 4 + GoogleAnalyticsAPI.class.php | 730 -------------------------------- Lib/Google/Api/Analytics.php | 330 +++++++++++++++ Lib/Google/Api/Http.php | 44 ++ Lib/Google/Api/Oauth.php | 35 ++ Lib/Google/Api/OauthService.php | 103 +++++ Lib/Google/Api/OauthWeb.php | 99 +++++ composer.json | 15 + examples/basics.php | 90 ---- index.php | 63 +++ settings_backup.php | 8 + 11 files changed, 701 insertions(+), 820 deletions(-) create mode 100644 .gitignore delete mode 100644 GoogleAnalyticsAPI.class.php create mode 100644 Lib/Google/Api/Analytics.php create mode 100644 Lib/Google/Api/Http.php create mode 100644 Lib/Google/Api/Oauth.php create mode 100644 Lib/Google/Api/OauthService.php create mode 100644 Lib/Google/Api/OauthWeb.php create mode 100644 composer.json delete mode 100644 examples/basics.php create mode 100644 index.php create mode 100644 settings_backup.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebfafa5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor/* +.git/* + +settings.php \ No newline at end of file diff --git a/GoogleAnalyticsAPI.class.php b/GoogleAnalyticsAPI.class.php deleted file mode 100644 index 2762e44..0000000 --- a/GoogleAnalyticsAPI.class.php +++ /dev/null @@ -1,730 +0,0 @@ - - * - * @version 1.1 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -class GoogleAnalyticsAPI { - - const API_URL = 'https://www.googleapis.com/analytics/v3/data/ga'; - const WEBPROPERTIES_URL = 'https://www.googleapis.com/analytics/v3/management/accounts/~all/webproperties'; - const PROFILES_URL = 'https://www.googleapis.com/analytics/v3/management/accounts/~all/webproperties/~all/profiles'; - - public $auth = null; - protected $accessToken = ''; - protected $accountId = ''; - protected $assoc = true; - - /** - * Default query parameters - * - */ - protected $defaultQueryParams = array(); - - - /** - * Constructor - * - * @access public - * @param String $auth (default: 'web') 'web' for Web-applications with end-users involved, 'service' for service applications (server-to-server) - */ - public function __construct($auth='web') { - - if (!function_exists('curl_init')) throw new Exception('The curl extension for PHP is required.'); - $this->auth = ($auth == 'web') ? new GoogleOauthWeb() : new GoogleOauthService(); - $this->defaultQueryParams = array( - 'start-date' => date('Y-m-d', strtotime('-1 month')), - 'end-date' => date('Y-m-d'), - 'metrics' => 'ga:visits', - ); - - } - - public function __set($key, $value) { - - switch ($key) { - case 'auth' : - if (($value instanceof GoogleOauth) == false) { - throw new Exception('auth needs to be a subclass of GoogleOauth'); - } - $this->auth = $value; - break; - case 'defaultQueryParams' : - $this->setDefaultQueryParams($value); - break; - default: - $this->{$key} = $value; - } - - } - - public function setAccessToken($token) { - $this->accessToken = $token; - } - - public function setAccountId($id) { - $this->accountId = $id; - } - - /** - * Set default query parameters - * Useful settings: start-date, end-date, max-results - * - * @access public - * @param array() $params Query parameters - */ - public function setDefaultQueryParams(array $params) { - $params = array_merge($this->defaultQueryParams, $params); - $this->defaultQueryParams = $params; - } - - - /** - * Return objects from json_decode instead of arrays - * - * @access public - * @param mixed $bool true to return objects - */ - public function returnObjects($bool) { - $this->assoc = !$bool; - $this->auth->returnObjects($bool); - } - - - /** - * Query the Google Analytics API - * - * @access public - * @param array $params (default: array()) Query parameters - * @return array data - */ - public function query($params=array()) { - return $this->_query($params); - } - - - /** - * Get all WebProperties - * - * @access public - * @return array data - */ - public function getWebProperties() { - - if (!$this->accessToken) throw new Exception('You must provide an accessToken'); - - $data = Http::curl(self::WEBPROPERTIES_URL, array('access_token' => $this->accessToken)); - return json_decode($data, $this->assoc); - - } - - - /** - * Get all Profiles - * - * @access public - * @return array data - */ - public function getProfiles() { - - if (!$this->accessToken) throw new Exception('You must provide an accessToken'); - - $data = Http::curl(self::PROFILES_URL, array('access_token' => $this->accessToken)); - return json_decode($data, $this->assoc); - - } - - /***************************************************************************************************************************** - * - * The following methods implement queries for the most useful statistics, seperated by topics: Audience/Content/Traffic Sources - * - *****************************************************************************************************************************/ - - /* - * AUDIENCE - * - */ - - public function getVisitsByDate($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:date', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getAudienceStatistics($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visitors,ga:newVisits,ga:percentNewVisits,ga:visits,ga:bounces,ga:pageviews,ga:visitBounceRate,ga:timeOnSite,ga:avgTimeOnSite', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsByCountries($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:country', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsByCities($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:city', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsByLanguages($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:language', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsBySystemBrowsers($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:browser', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsBySystemOs($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:operatingSystem', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - - } - - public function getVisitsBySystemResolutions($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:screenResolution', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsByMobileOs($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:operatingSystem', - 'sort' => '-ga:visits', - 'segment' => 'gaid::-11', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getVisitsByMobileResolutions($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:screenResolution', - 'sort' => '-ga:visits', - 'segment' => 'gaid::-11', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - /* - * CONTENT - * - */ - - public function getPageviewsByDate($params=array()) { - - $defaults = array( - 'metrics' => 'ga:pageviews', - 'dimensions' => 'ga:date', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getContentStatistics($params=array()) { - - $defaults = array( - 'metrics' => 'ga:pageviews,ga:uniquePageviews', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getContentTopPages($params=array()) { - - $defaults = array( - 'metrics' => 'ga:pageviews', - 'dimensions' => 'ga:pagePath', - 'sort' => '-ga:pageviews', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - /* - * TRAFFIC SOURCES - * - */ - - public function getTrafficSources($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:medium', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getKeywords($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:keyword', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - public function getReferralTraffic($params=array()) { - - $defaults = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:source', - 'sort' => '-ga:visits', - ); - $_params = array_merge($defaults, $params); - return $this->_query($_params); - - } - - - protected function _query($params=array()){ - - if (!$this->accessToken || !$this->accountId) { - throw new Exception('You must provide the accessToken and an accountId'); - } - $_params = array_merge($this->defaultQueryParams, array('access_token' => $this->accessToken, 'ids' => $this->accountId)); - $queryParams = array_merge($_params, $params); - $data = Http::curl(self::API_URL, $queryParams); - return json_decode($data, $this->assoc); - - } - -} - - -/** - * Abstract Auth class - * - */ -abstract class GoogleOauth { - - const TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; - const SCOPE_URL = 'https://www.googleapis.com/auth/analytics.readonly'; - - protected $assoc = true; - protected $clientId = ''; - - public function __set($key, $value) { - $this->{$key} = $value; - } - - public function setClientId($id) { - $this->clientId = $id; - } - - public function returnObjects($bool) { - $this->assoc = !$bool; - } - - /** - * To be implemented by the subclasses - * - */ - public function getAccessToken($data=null) {} - -} - - -/** - * Oauth 2.0 for service applications requiring a private key - * openssl extension for PHP is required! - * @extends GoogleOauth - * - */ -class GoogleOauthService extends GoogleOauth { - - const MAX_LIFETIME_SECONDS = 3600; - const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'; - - protected $email = ''; - protected $privateKey = null; - protected $password = 'notasecret'; - - /** - * Constructor - * - * @access public - * @param string $clientId (default: '') Client-ID of your project from the Google APIs console - * @param string $email (default: '') E-Mail address of your project from the Google APIs console - * @param mixed $privateKey (default: null) Path to your private key file (*.p12) - */ - public function __construct($clientId='', $email='', $privateKey=null) { - if (!function_exists('openssl_sign')) throw new Exception('openssl extension for PHP is needed.'); - $this->clientId = $clientId; - $this->email = $email; - $this->privateKey = $privateKey; - } - - - public function setEmail($email) { - $this->email = $email; - } - - public function setPrivateKey($key) { - $this->privateKey = $key; - } - - - /** - * Get the accessToken in exchange with the JWT - * - * @access public - * @param mixed $data (default: null) No data needed in this implementation - * @return array Array with keys: access_token, expires_in - */ - public function getAccessToken($data=null) { - - if (!$this->clientId || !$this->email || !$this->privateKey) { - throw new Exception('You must provide the clientId, email and a path to your private Key'); - } - - $jwt = $this->generateSignedJWT(); - - $params = array( - 'grant_type' => self::GRANT_TYPE, - 'assertion' => $jwt, - ); - - $auth = Http::curl(GoogleOauth::TOKEN_URL, $params, true); - return json_decode($auth, $this->assoc); - - } - - - /** - * Generate and sign a JWT request - * See: https://developers.google.com/accounts/docs/OAuth2ServiceAccount - * - * @access protected - */ - protected function generateSignedJWT() { - - // Check if a valid privateKey file is provided - if (!file_exists($this->privateKey) || !is_file($this->privateKey)) { - throw new Exception('Private key does not exist'); - } - - // Create header, claim and signature - $header = array( - 'alg' => 'RS256', - 'typ' => 'JWT', - ); - - $t = time(); - $params = array( - 'iss' => $this->email, - 'scope' => GoogleOauth::SCOPE_URL, - 'aud' => GoogleOauth::TOKEN_URL, - 'exp' => $t + self::MAX_LIFETIME_SECONDS, - 'iat' => $t, - ); - - $encodings = array( - base64_encode(json_encode($header)), - base64_encode(json_encode($params)), - ); - - // Compute Signature - $input = implode('.', $encodings); - $certs = array(); - $pkcs12 = file_get_contents($this->privateKey); - if (!openssl_pkcs12_read($pkcs12, $certs, $this->password)) { - throw new Exception('Could not parse .p12 file'); - } - if (!isset($certs['pkey'])) { - throw new Exception('Could not find private key in .p12 file'); - } - $keyId = openssl_pkey_get_private($certs['pkey']); - if (!openssl_sign($input, $sig, $keyId, 'sha256')) { - throw new Exception('Could not sign data'); - } - - // Generate JWT - $encodings[] = base64_encode($sig); - $jwt = implode('.', $encodings); - return $jwt; - - } - -} - - - - -/** - * Oauth 2.0 for web applications - * @extends GoogleOauth - * - */ -class GoogleOauthWeb extends GoogleOauth { - - const AUTH_URL = 'https://accounts.google.com/o/oauth2/auth'; - const REVOKE_URL = 'https://accounts.google.com/o/oauth2/revoke'; - - protected $clientSecret = ''; - protected $redirectUri = ''; - - - /** - * Constructor - * - * @access public - * @param string $clientId (default: '') Client-ID of your web application from the Google APIs console - * @param string $clientSecret (default: '') Client-Secret of your web application from the Google APIs console - * @param string $redirectUri (default: '') Redirect URI to your app - must match with an URL provided in the Google APIs console - */ - public function __construct($clientId='', $clientSecret='', $redirectUri='') { - $this->clientId = $clientId; - $this->clientSecret = $clientSecret; - $this->redirectUri = $redirectUri; - } - - public function setClientSecret($secret) { - $this->clientSecret = $secret; - } - - public function setRedirectUri($uri) { - $this->redirectUri = $uri; - } - - /** - * Build auth url - * The user has to login with his Google Account and give your app access to the Analytics API - * - * @access public - * @param array $params Custom parameters - * @return string The auth login-url - */ - public function buildAuthUrl($params = array()) { - - if (!$this->clientId || !$this->redirectUri) { - throw new Exception('You must provide the clientId and a redirectUri'); - } - - $defaults = array( - 'response_type' => 'code', - 'client_id' => $this->clientId, - 'redirect_uri' => $this->redirectUri, - 'scope' => GoogleOauth::SCOPE_URL, - 'access_type' => 'offline', - 'approval_prompt' => 'force', - ); - $params = array_merge($defaults, $params); - $url = self::AUTH_URL . '?' . http_build_query($params); - return $url; - - } - - - /** - * Get the AccessToken in exchange with the code from the auth along with a refreshToken - * - * @access public - * @param mixed $data The code received with GET after auth - * @return array Array with the following keys: access_token, refresh_token, expires_in - */ - public function getAccessToken($data=null) { - - if (!$this->clientId || !$this->clientSecret || !$this->redirectUri) { - throw new Exception('You must provide the clientId, clientSecret and a redirectUri'); - } - - $params = array( - 'code' => $data, - 'client_id' => $this->clientId, - 'client_secret' => $this->clientSecret, - 'redirect_uri' => $this->redirectUri, - 'grant_type' => 'authorization_code', - ); - - $auth = Http::curl(GoogleOauth::TOKEN_URL, $params, true); - return json_decode($auth, $this->assoc); - - } - - - /** - * Get a new accessToken with the refreshToken - * - * @access public - * @param mixed $refreshToken The refreshToken - * @return array Array with the following keys: access_token, expires_in - */ - public function refreshAccessToken($refreshToken) { - - if (!$this->clientId || !$this->clientSecret) { - throw new Exception('You must provide the clientId and clientSecret'); - } - - $params = array( - 'client_id' => $this->clientId, - 'client_secret' => $this->clientSecret, - 'refresh_token' => $refreshToken, - 'grant_type' => 'refresh_token', - ); - - $auth = Http::curl(GoogleOauth::TOKEN_URL, $params, true); - return json_decode($auth, $this->assoc); - - } - - - /** - * Revoke access - * - * @access public - * @param mixed $token accessToken or refreshToken - */ - public function revokeAccess($token) { - - $params = array('token' => $token); - $data = Http::curl(self::REVOKE_URL, $params); - return json_decode($data, $this->assoc); - } - - -} - - - -/** - * Send data with curl - * - */ -class Http { - - - /** - * Send http requests with curl - * - * @access public - * @static - * @param mixed $url The url to send data - * @param array $params (default: array()) Array with key/value pairs to send - * @param bool $post (default: false) True when sending with POST - */ - public static function curl($url, $params=array(), $post=false) { - - if (empty($url)) return false; - - if (!$post && !empty($params)) { - $url = $url . "?" . http_build_query($params); - } - $curl = curl_init($url); - if ($post) { - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, $params); - } - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - $data = curl_exec($curl); - $http_code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); - // Add the status code to the json data, useful for error-checking - $data = preg_replace('/^{/', '{"http_code":'.$http_code.',', $data); - curl_close($curl); - return $data; - - } - -} - -?> diff --git a/Lib/Google/Api/Analytics.php b/Lib/Google/Api/Analytics.php new file mode 100644 index 0000000..7ee9c36 --- /dev/null +++ b/Lib/Google/Api/Analytics.php @@ -0,0 +1,330 @@ +auth = ($auth == 'web') ? new OauthWeb() : new OauthService(); + $this->defaultQueryParams = array( + 'start-date' => date('Y-m-d', strtotime('-1 month')), + 'end-date' => date('Y-m-d'), + 'metrics' => 'ga:visits', + ); + + } + + public function __set($key, $value) + { + + switch ($key) { + case 'auth' : + if (($value instanceof GoogleOauth) == false) { + throw new Exception('auth needs to be a subclass of GoogleOauth'); + } + $this->auth = $value; + break; + case 'defaultQueryParams' : + $this->setDefaultQueryParams($value); + break; + default: + $this->{$key} = $value; + } + + } + + public function setAccessToken($token) + { + $this->accessToken = $token; + } + + public function setAccountId($id) + { + $this->accountId = $id; + } + + public function setDefaultQueryParams(array $params) + { + $params = array_merge($this->defaultQueryParams, $params); + $this->defaultQueryParams = $params; + } + + public function returnObjects($bool) + { + $this->assoc = !$bool; + $this->auth->returnObjects($bool); + } + + public function query($params = array()) + { + return $this->_query($params); + } + + public function getWebProperties() + { + + if (!$this->accessToken) { + throw new Exception('You must provide an accessToken'); + } + + $data = Http::curl(self::WEBPROPERTIES_URL, array('access_token' => $this->accessToken)); + return json_decode($data, $this->assoc); + + } + + public function getProfiles() + { + + if (!$this->accessToken) { + throw new Exception('You must provide an accessToken'); + } + + $data = Http::curl(self::PROFILES_URL, array('access_token' => $this->accessToken)); + return json_decode($data, $this->assoc); + + } + + public function getVisitsByDate($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:date', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getAudienceStatistics($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visitors,ga:newVisits,ga:percentNewVisits,ga:visits,ga:bounces,ga:pageviews,ga:visitBounceRate,ga:timeOnSite,ga:avgTimeOnSite', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsByCountries($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:country', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsByCities($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:city', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsByLanguages($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:language', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsBySystemBrowsers($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:browser', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsBySystemOs($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:operatingSystem', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + + } + + public function getVisitsBySystemResolutions($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:screenResolution', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsByMobileOs($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:operatingSystem', + 'sort' => '-ga:visits', + 'segment' => 'gaid::-11', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getVisitsByMobileResolutions($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:screenResolution', + 'sort' => '-ga:visits', + 'segment' => 'gaid::-11', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getPageviewsByDate($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:pageviews', + 'dimensions' => 'ga:date', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getContentStatistics($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:pageviews,ga:uniquePageviews', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getContentTopPages($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:pageviews', + 'dimensions' => 'ga:pagePath', + 'sort' => '-ga:pageviews', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getTrafficSources($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:medium', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getKeywords($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:keyword', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + public function getReferralTraffic($params = array()) + { + + $defaults = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:source', + 'sort' => '-ga:visits', + ); + $_params = array_merge($defaults, $params); + return $this->_query($_params); + + } + + + protected function _query($params = array()) + { + + if (!$this->accessToken || !$this->accountId) { + throw new Exception('You must provide the accessToken and an accountId'); + } + $_params = array_merge( + $this->defaultQueryParams, + array('access_token' => $this->accessToken, 'ids' => $this->accountId) + ); + $queryParams = array_merge($_params, $params); + $data = Http::curl(self::API_URL, $queryParams); + return json_decode($data, $this->assoc); + + } +} \ No newline at end of file diff --git a/Lib/Google/Api/Http.php b/Lib/Google/Api/Http.php new file mode 100644 index 0000000..94ad87b --- /dev/null +++ b/Lib/Google/Api/Http.php @@ -0,0 +1,44 @@ +{$key} = $value; + } + + public function setClientId($id) + { + $this->clientId = $id; + } + + public function returnObjects($bool) + { + $this->assoc = !$bool; + } + + /** + * To be implemented by the subclasses + * + */ + public function getAccessToken($data = null) + { + } +} \ No newline at end of file diff --git a/Lib/Google/Api/OauthService.php b/Lib/Google/Api/OauthService.php new file mode 100644 index 0000000..3fbe71f --- /dev/null +++ b/Lib/Google/Api/OauthService.php @@ -0,0 +1,103 @@ +clientId = $clientId; + $this->email = $email; + $this->privateKey = $privateKey; + } + + + public function setEmail($email) + { + $this->email = $email; + } + + public function setPrivateKey($key) + { + $this->privateKey = $key; + } + + public function getAccessToken($data = null) + { + + if (!$this->clientId || !$this->email || !$this->privateKey) { + throw new Exception('You must provide the clientId, email and a path to your private Key'); + } + + $jwt = $this->generateSignedJWT(); + + $params = array( + 'grant_type' => self::GRANT_TYPE, + 'assertion' => $jwt, + ); + + $auth = Http::curl(Oauth::TOKEN_URL, $params, true); + return json_decode($auth, $this->assoc); + + } + + protected function generateSignedJWT() + { + if (!file_exists($this->privateKey) || !is_file($this->privateKey)) { + throw new Exception('Private key does not exist'); + } + + $header = array( + 'alg' => 'RS256', + 'typ' => 'JWT', + ); + + $t = time(); + $params = array( + 'iss' => $this->email, + 'scope' => Oauth::SCOPE_URL, + 'aud' => Oauth::TOKEN_URL, + 'exp' => $t + self::MAX_LIFETIME_SECONDS, + 'iat' => $t, + ); + + $encodings = array( + base64_encode(json_encode($header)), + base64_encode(json_encode($params)), + ); + + $input = implode('.', $encodings); + $certs = array(); + $pkcs12 = file_get_contents($this->privateKey); + if (!openssl_pkcs12_read($pkcs12, $certs, $this->password)) { + throw new Exception('Could not parse .p12 file'); + } + if (!isset($certs['pkey'])) { + throw new Exception('Could not find private key in .p12 file'); + } + $keyId = openssl_pkey_get_private($certs['pkey']); + if (!openssl_sign($input, $sig, $keyId, 'sha256')) { + throw new Exception('Could not sign data'); + } + + $encodings[] = base64_encode($sig); + $jwt = implode('.', $encodings); + return $jwt; + + } + +} \ No newline at end of file diff --git a/Lib/Google/Api/OauthWeb.php b/Lib/Google/Api/OauthWeb.php new file mode 100644 index 0000000..de93ab2 --- /dev/null +++ b/Lib/Google/Api/OauthWeb.php @@ -0,0 +1,99 @@ +clientId = $clientId; + $this->clientSecret = $clientSecret; + $this->redirectUri = $redirectUri; + } + + public function setClientSecret($secret) + { + $this->clientSecret = $secret; + } + + public function setRedirectUri($uri) + { + $this->redirectUri = $uri; + } + + public function buildAuthUrl($params = array()) + { + + if (!$this->clientId || !$this->redirectUri) { + throw new Exception('You must provide the clientId and a redirectUri'); + } + + $defaults = array( + 'response_type' => 'code', + 'client_id' => $this->clientId, + 'redirect_uri' => $this->redirectUri, + 'scope' => Oauth::SCOPE_URL, + 'access_type' => 'offline', + 'approval_prompt' => 'force', + ); + $params = array_merge($defaults, $params); + $url = self::AUTH_URL . '?' . http_build_query($params); + return $url; + + } + + public function getAccessToken($data = null) + { + + if (!$this->clientId || !$this->clientSecret || !$this->redirectUri) { + throw new Exception('You must provide the clientId, clientSecret and a redirectUri'); + } + + $params = array( + 'code' => $data, + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + 'grant_type' => 'authorization_code', + ); + + $auth = Http::curl(Oauth::TOKEN_URL, $params, true); + return json_decode($auth, $this->assoc); + + } + + public function refreshAccessToken($refreshToken) + { + if (!$this->clientId || !$this->clientSecret) { + throw new Exception('You must provide the clientId and clientSecret'); + } + + $params = array( + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'refresh_token' => $refreshToken, + 'grant_type' => 'refresh_token', + ); + + $auth = Http::curl(Oauth::TOKEN_URL, $params, true); + return json_decode($auth, $this->assoc); + + } + + public function revokeAccess($token) + { + $params = array('token' => $token); + $data = Http::curl(self::REVOKE_URL, $params); + return json_decode($data, $this->assoc); + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c554a17 --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "shahariaazam/simple-analytics", + "description": "Simple Google Analytics Reporting API", + "homepage": "http://shahariaazam.com", + "type": "project", + "license": "MIT", + "require": { + "php": ">=5.4" + }, + "autoload": { + "psr-0": { + "Google\\Api\\": "Lib" + } + } +} \ No newline at end of file diff --git a/examples/basics.php b/examples/basics.php deleted file mode 100644 index 252e41e..0000000 --- a/examples/basics.php +++ /dev/null @@ -1,90 +0,0 @@ -'; - - // From the APIs console - $client_secret = ''; - - // Url to your this page, must match the one in the APIs console - $redirect_uri = ''; - - // Analytics account id like, 'ga:xxxxxxx' - $account_id = ''; - - session_start(); - include('../GoogleAnalyticsAPI.class.php'); - - $ga = new GoogleAnalyticsAPI(); - $ga->auth->setClientId($client_id); - $ga->auth->setClientSecret($client_secret); - $ga->auth->setRedirectUri($redirect_uri); - - if (isset($_GET['force_oauth'])) { - $_SESSION['oauth_access_token'] = null; - } - - - /* - * Step 1: Check if we have an oAuth access token in our session - * If we've got $_GET['code'], move to the next step - */ - if (!isset($_SESSION['oauth_access_token']) && !isset($_GET['code'])) { - // Go get the url of the authentication page, redirect the client and go get that token! - $url = $ga->auth->buildAuthUrl(); - header("Location: ".$url); - } - - /* - * Step 2: Returning from the Google oAuth page, the access token should be in $_GET['code'] - */ - if (!isset($_SESSION['oauth_access_token']) && isset($_GET['code'])) { - $auth = $ga->auth->getAccessToken($_GET['code']); - if ($auth['http_code'] == 200) { - $accessToken = $auth['access_token']; - $refreshToken = $auth['refresh_token']; - $tokenExpires = $auth['expires_in']; - $tokenCreated = time(); - - // For simplicity of the example we only store the accessToken - // If it expires use the refreshToken to get a fresh one - $_SESSION['oauth_access_token'] = $accessToken; - } else { - die("Sorry, something wend wrong retrieving the oAuth tokens"); - } - } - - /* - * Step 3: Do real stuff! - * If we're here, we sure we've got an access token - */ - $ga->setAccessToken($_SESSION['oauth_access_token']); - $ga->setAccountId($account_id); - - - // Set the default params. For example the start/end dates and max-results - $defaults = array( - 'start-date' => date('Y-m-d', strtotime('-1 month')), - 'end-date' => date('Y-m-d'), - ); - $ga->setDefaultQueryParams($defaults); - - $params = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:date', - ); - $visits = $ga->query($params); - - print "
";
-    var_dump($visits);
-    print "
"; diff --git a/index.php b/index.php new file mode 100644 index 0000000..cac80bf --- /dev/null +++ b/index.php @@ -0,0 +1,63 @@ +auth->setClientId($client_id); +$ga->auth->setClientSecret($client_secret); +$ga->auth->setRedirectUri($redirect_uri); + +if (isset($_GET['force_oauth'])) { + $_SESSION['oauth_access_token'] = null; +} + +if (!isset($_SESSION['oauth_access_token']) && !isset($_GET['code'])) { + // Go get the url of the authentication page, redirect the client and go get that token! + $url = $ga->auth->buildAuthUrl(); + header("Location: " . $url); +} + +if (!isset($_SESSION['oauth_access_token']) && isset($_GET['code'])) { + + $auth = $ga->auth->getAccessToken($_GET['code']); + + if ($auth['http_code'] == 200) { + $accessToken = $auth['access_token']; + $refreshToken = $auth['refresh_token']; + $tokenExpires = $auth['expires_in']; + $tokenCreated = time(); + + $_SESSION['oauth_access_token'] = $accessToken; + } else { + die("Sorry, something wend wrong retrieving the oAuth tokens"); + } +} + +$ga->setAccessToken($_SESSION['oauth_access_token']); +$ga->setAccountId($account_id); + + +$defaults = array( + 'start-date' => date('Y-m-d', strtotime('-1 month')), + 'end-date' => date('Y-m-d'), +); +$ga->setDefaultQueryParams($defaults); + +$params = array( + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:date', +); +$visits = $ga->query($params); + +print "
";
+var_dump($visits);
+print "
"; diff --git a/settings_backup.php b/settings_backup.php new file mode 100644 index 0000000..3bf15f1 --- /dev/null +++ b/settings_backup.php @@ -0,0 +1,8 @@ + '', + 'clientSecret' => '', + 'redirectURI' => '', + 'accountID' => '' +); \ No newline at end of file From 1ad0cb5f5aa1147b0d9ecde8cdce1183bb50f8b5 Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Mon, 19 May 2014 01:56:15 +0600 Subject: [PATCH 2/6] Composer project name modified --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c554a17..54a576d 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "shahariaazam/simple-analytics", + "name": "shahariaazam/Google-Analytics-API-PHP", "description": "Simple Google Analytics Reporting API", "homepage": "http://shahariaazam.com", "type": "project", From 24c8fbe365810cbb4110494f9fb8f00ada0e7509 Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Sun, 15 Jun 2014 03:23:14 +0600 Subject: [PATCH 3/6] LongTerm access token mechanism configured --- Lib/Google/Api/Analytics.php | 39 +++++++++++++++++++++++++++++++++ Lib/Google/Api/OauthWeb.php | 42 ++++++++++++++++++++++++++++++++++++ index.php | 30 +++----------------------- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/Lib/Google/Api/Analytics.php b/Lib/Google/Api/Analytics.php index 7ee9c36..b3aea5e 100644 --- a/Lib/Google/Api/Analytics.php +++ b/Lib/Google/Api/Analytics.php @@ -327,4 +327,43 @@ protected function _query($params = array()) return json_decode($data, $this->assoc); } + + public function getOfflineAccessToken($grantCode, $grantType) + { + $oathWeb = new OauthWeb(); + $oathWeb->getOfflineAccessToken($grantCode, $grantType); + } + + public function prepareToken() + { + if (isset($_GET['force_oauth'])) { + $_SESSION['oauth_access_token'] = null; + } + + if (!isset($_SESSION['oauth_access_token']) && !isset($_GET['code'])) { + // Go get the url of the authentication page, redirect the client and go get that token! + $url = $this->auth->buildAuthUrl(); + header("Location: " . $url); + } + + if (!isset($_SESSION['oauth_access_token']) && isset($_GET['code'])) { + + $auth = $this->auth->getAccessToken($_GET['code']); + + if ($auth['http_code'] == 200) { + $accessToken = $auth['access_token']; + $refreshToken = $auth['refresh_token']; + $tokenExpires = $auth['expires_in']; + $tokenCreated = time(); + + $_SESSION['oauth_access_token'] = $accessToken; + + if(null != $this->getOfflineAccessToken($accessToken, 'offline')){ + $_SESSION['oauth_access_token'] = $this->getOfflineAccessToken($accessToken, 'offline'); + } + } else { + die("Sorry, something wend wrong retrieving the oAuth tokens"); + } + } + } } \ No newline at end of file diff --git a/Lib/Google/Api/OauthWeb.php b/Lib/Google/Api/OauthWeb.php index de93ab2..1e75822 100644 --- a/Lib/Google/Api/OauthWeb.php +++ b/Lib/Google/Api/OauthWeb.php @@ -96,4 +96,46 @@ public function revokeAccess($token) return json_decode($data, $this->assoc); } + function getOfflineAccessToken($grantCode, $grantType) + { + $oauth2token_url = "https://accounts.google.com/o/oauth2/token"; + $clienttoken_post = array( + "client_id" => $this->clientId, + "client_secret" => $this->clientSecret + ); + + if ($grantType === "online") { + $clienttoken_post["code"] = $grantCode; + $clienttoken_post["redirect_uri"] = 'http://google-analytics.com/oauth2callback'; + $clienttoken_post["grant_type"] = "authorization_code"; + } + + if ($grantType === "offline") { + $clienttoken_post["refresh_token"] = $grantCode; + $clienttoken_post["grant_type"] = "refresh_token"; + } + + $curl = curl_init($oauth2token_url); + + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $clienttoken_post); + curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + + $json_response = curl_exec($curl); + curl_close($curl); + + $authObj = json_decode($json_response); + + //if offline access requested and granted, get refresh token + if (isset($authObj->refresh_token)) { + global $refreshToken; + $refreshToken = $authObj->refresh_token; + } + + $accessToken = $authObj->access_token; + return $accessToken; + } + } \ No newline at end of file diff --git a/index.php b/index.php index cac80bf..a7b83b0 100644 --- a/index.php +++ b/index.php @@ -16,31 +16,7 @@ $ga->auth->setClientSecret($client_secret); $ga->auth->setRedirectUri($redirect_uri); -if (isset($_GET['force_oauth'])) { - $_SESSION['oauth_access_token'] = null; -} - -if (!isset($_SESSION['oauth_access_token']) && !isset($_GET['code'])) { - // Go get the url of the authentication page, redirect the client and go get that token! - $url = $ga->auth->buildAuthUrl(); - header("Location: " . $url); -} - -if (!isset($_SESSION['oauth_access_token']) && isset($_GET['code'])) { - - $auth = $ga->auth->getAccessToken($_GET['code']); - - if ($auth['http_code'] == 200) { - $accessToken = $auth['access_token']; - $refreshToken = $auth['refresh_token']; - $tokenExpires = $auth['expires_in']; - $tokenCreated = time(); - - $_SESSION['oauth_access_token'] = $accessToken; - } else { - die("Sorry, something wend wrong retrieving the oAuth tokens"); - } -} +$ga->prepareToken(); $ga->setAccessToken($_SESSION['oauth_access_token']); $ga->setAccountId($account_id); @@ -53,8 +29,8 @@ $ga->setDefaultQueryParams($defaults); $params = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:date', + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:date', ); $visits = $ga->query($params); From 140937ae77fceed544fd02626a94d2a9118d62de Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Sun, 15 Jun 2014 03:28:36 +0600 Subject: [PATCH 4/6] ReadMe file updated --- README.md | 146 ++++++++++-------------------------------------------- 1 file changed, 27 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 0aabbdd..a5f2285 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ #Google Analytics API PHP +Clone this repo and run [composer](http://getcomposer.com). `composer install` then it will autoload the classes and you are done! + Simple class to set up Oauth 2.0 with Google and query the Google Analytics API v3 with PHP. Curl is required! The class supports getting the access tokens for *web applications* and *service accounts* registered in the Google APIs console. See the documentation for further informations: https://developers.google.com/accounts/docs/OAuth2 @@ -17,148 +19,54 @@ See the documentation for further informations: https://developers.google.com/ac Depending on the chosen application type, the setup is slightly different. This section describes both ways independently. -###2.1 Web applications +###Web applications ```php -include('GoogleAnalyticsAPI.class.php'); +auth->setClientId('your_client_id'); // From the APIs console -$ga->auth->setClientSecret('your_client_secret'); // From the APIs console -$ga->auth->setRedirectUri('redirect_uri'); // Url to your app, must match one in the APIs console +use Google\Api\Analytics; -// Get the Auth-Url -$url = $ga->auth->buildAuthUrl(); -``` +$client_id = $config['clientID']; +$client_secret = $config['clientSecret']; +$redirect_uri = $config['redirectURI']; +$account_id = $config['accountID']; -Provide a link to the Auth-Url. The user has to log in with his Google Account, accept that your App will access the Analytics Data. After completing this steps, -the user will be redirected back to the redirect-uri along with a code. -This code is needed to get the tokens. +session_start(); -```php -$code = $_GET['code']; -$auth = $ga->auth->getAccessToken($code); - -// Try to get the AccessToken -if ($auth['http_code'] == 200) { - $accessToken = $auth['access_token']; - $refreshToken = $auth['refresh_token']; - $tokenExpires = $auth['expires_in']; - $tokenCreated = time(); -} else { - // error... -} -``` +$ga = new Analytics(); +$ga->auth->setClientId($client_id); +$ga->auth->setClientSecret($client_secret); +$ga->auth->setRedirectUri($redirect_uri); -With the accessToken you can query the API for the given time (seconds) in *$tokenExpires*. -If you need to query the API beyond this time, you should store the refreshToken along with a timestamp in the Database / Session. -If the accessToken expires, you can get a new one with the refreshToken. +$ga->prepareToken(); -```php -// Check if the accessToken is expired -if ((time() - $tokenCreated) >= $tokenExpires) { - $auth = $ga->auth->refreshAccessToken($refreshToken); - // Get the accessToken as above and save it into the Database / Session -} +$ga->setAccessToken($_SESSION['oauth_access_token']); +$ga->setAccountId($account_id); ``` -###2.2 Service accounts - -Copy the email address from the APIs console (xxxxxxxx@developer.gserviceaccount.com). Visit your GA admin and add this email -as a user to your properties. - -```php -include('GoogleAnalyticsAPI.class.php'); - -$ga = new GoogleAnalyticsAPI('service'); -$ga->auth->setClientId('your_client_id'); // From the APIs console -$ga->auth->setEmail('your_email_addy'); // From the APIs console -$ga->auth->setPrivateKey('/super/secure/path/to/your/privatekey.p12'); // Path to the .p12 file -``` - -To query the API, you need to obtain an access token. This token is valid *one hour*, afterwards you'll need to get a new -token. You can store the token in the database/session. - -```php -$auth = $ga->auth->getAccessToken(); - -// Try to get the AccessToken -if ($auth['http_code'] == 200) { - $accessToken = $auth['access_token']; - $tokenExpires = $auth['expires_in']; - $tokenCreated = time(); -} else { - // error... -} -``` - -##3. Find the Account-ID - -Before you can query the API, you need the ID of the Account you want to query the data. -The ID can be found like this: - -```php -// Set the accessToken and Account-Id -$ga->setAccessToken($accessToken); -$ga->setAccountId('ga:xxxxxxx'); - -// Load profiles -$profiles = $ga->getProfiles(); -$accounts = array(); -foreach ($profiles['items'] as $item) { - $id = "ga:{$item['id']}"; - $name = $item['name']; - $accounts[$id] = $name; -} -// Print out the Accounts with Id => Name. Save the Id (array-key) of the account you want to query data. -// See next chapter how to set the account-id. -print_r($accounts); -``` -##4. Query the Google Analytics API +##Query the Google Analytics API Once you have a valid accessToken and an Account-ID, you can query the Google Analytics API. You can set some default Query Parameters that will be executed with every query. ```php -// Set the accessToken and Account-Id -$ga->setAccessToken($accessToken); -$ga->setAccountId('ga:xxxxxxx'); - -// Set the default params. For example the start/end dates and max-results $defaults = array( - 'start-date' => date('Y-m-d', strtotime('-1 month')), - 'end-date' => date('Y-m-d'), + 'start-date' => date('Y-m-d', strtotime('-1 month')), + 'end-date' => date('Y-m-d'), ); $ga->setDefaultQueryParams($defaults); -// Example1: Get visits by date $params = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:date', + 'metrics' => 'ga:visits', + 'dimensions' => 'ga:date', ); $visits = $ga->query($params); -// Example2: Get visits by country -$params = array( - 'metrics' => 'ga:visits', - 'dimensions' => 'ga:country', - 'sort' => '-ga:visits', - 'max-results' => 30, - 'start-date' => '2013-01-01' //Overwrite this from the defaultQueryParams -); -$visitsByCountry = $ga->query($params); - -// Example3: Same data as Example1 but with the built in method: -$visits = $ga->getVisitsByDate(); - -// Example4: Get visits by Operating Systems and return max. 100 results -$visitsByOs = $ga->getVisitsBySystemOs(array('max-results' => 100)); - -// Example5: Get referral traffic -$referralTraffic = $ga->getReferralTraffic(); - -// Example6: Get visits by languages -$visitsByLanguages = $ga->getVisitsByLanguages(); +print "
";
+var_dump($visits);
+print "
"; ``` ###Metrics & Dimensions Reference: https://developers.google.com/analytics/devguides/reporting/core/dimsmets From 523761bc985601846b1684c4884e0a299e6f803e Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Sun, 15 Jun 2014 03:35:53 +0600 Subject: [PATCH 5/6] Composer package name updated --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 54a576d..2641b96 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "shahariaazam/Google-Analytics-API-PHP", + "name": "shahariaazam/google-analytics-api-php", "description": "Simple Google Analytics Reporting API", "homepage": "http://shahariaazam.com", "type": "project", From f69715c424eedf0b344ac581941d71169003dc94 Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Sun, 15 Jun 2014 03:39:40 +0600 Subject: [PATCH 6/6] ReadMe updated with Composer add option --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a5f2285..417846e 100755 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Simple class to set up Oauth 2.0 with Google and query the Google Analytics API The class supports getting the access tokens for *web applications* and *service accounts* registered in the Google APIs console. See the documentation for further informations: https://developers.google.com/accounts/docs/OAuth2 +##Install via Composer +Just add the following line in your `composer.json` and update your dependencies via running composer update command. +`"shahariaazam/google-analytics-api-php": "dev-master"` + ##1. Basic Setup * Create a Project in the Google APIs Console: https://code.google.com/apis/console/