Bringing Google Analytics Reports to Your Application

Reasonably statistic analyses belong to CMS ACP reports. So, CMS vendors tend to include some into their products. However, they often make statistical data collection and analysis a part of CMS. It usually gains some primitive reports and affects application performance. I believe any sound statistic analysis can be delivered only by a dedicated specialized application. Meantime CMS can be used to represent the reports prepared by that application. Have you ever fancied displaying Google Analytics Reports in your CMS?


Why GA?

Google Analytics is the most widely used website statistics service, currently in use on around 55% of the 10,000 most popular websites , No wonder it is, - GA provides formally for free incredible data visualization, segmentation for analysis of subsets, such as conversions, custom reports, integration with other Google products, such as AdWords, Public Data Explorer and Website Optimizer. Besides, GA can track leads and sales, calculating conversion rates and helping you get a clearer idea on how effective your marketing campaigns are.

What I especially love is GA's event tracking API. You can subscribe GA for your custom events using JavaScript. It implies any DOM events (e.g. play Video, for submit, sign up click and etc), AJAX-load events, events on widgets, file download events, load time measurement events and events from Flash-embeddings.


GA APIs

GA provides following APIs:

  • Management API - used to access GA configuration data for accounts, web properties, profiles, goals, custom data sources, daily uploads, and segments.
  • Core Reporting API - query for dimensions and metrics to produce customized reports
  • Multi-Channel Funnels Reporting API - query the traffic source paths that lead to a user's goal conversion. This allows you to analyze how multiple marketing channels influence conversions over time

You can find client libraries in here


Step by step guide

To access any of Google APIs you will need permission from Google and that is supposed to be done using OAuth 2.0 authentication. In plain English you follow a special link from your application and get redirected to Google service, which authenticates you and brings you back on your page with a token. That token you can use to access Google API methods.

Now let's try make a simple GA report within our application. First we have to get the OAuth 2.0 credentials. So, we enter Google APIs Console, click on combo-box under the logo and select "Create..."

Here we name our application (part), which will deliver the reports (e.g. My CMS – staging env.). Keep it in mind that we will need a separate project per every environment, because every project will be bound to a particular URL.

Now we can go to the Services tab and enable Analytics API for the project.

Then we go to API Access tab and click there on "Create an OAuth 2.0 client ID...". There we enter again project name, optionally link to logo image and project home page URL.


On the next screen we choose Web application a type. Pay attention when entering hostname; it's excepted to be the link to our controller serving GA reports.

Well, now we have everything to access GA API from our application. Let's download the API client library (PHP library for the example below). Then we copy src folder of the library to our Plugin/GoogleApi/. In the last one we also put facade class to the API client library:

<?php

namespace Plugin\GoogleApi;

require_once __DIR__ . '/src/Google_Client.php';
require_once __DIR__ . '/src/contrib/Google_AnalyticsService.php';

/**
 * GA access facade
 */
class Api
{
    /**
     *
     * @var \Google_Client
     */
    private $_client;
    /**
     *
     * @var \Google_AnalyticsService
     */
    private $_service;
    /**
     * Session storage. In here can $_SESSION
     * @var \Lib\Storage\Session
     */
    private $_session;
    /**
     *
     * @var int
     */
    private $_defaultProfile;

    const START_OF_AGE = '2012-01-01';
    /**
     * @param GaConfig $config
     */
    public function __construct(\GaConfig $config)
    {
        $this->_session = \Lib\Storage\Session::getInstance();
        $this->_client = new \Google_Client();
        $this->_client->setApplicationName($config->productName);
        $this->_client->setClientId($config->clientId);
        $this->_client->setClientSecret($config->clientSecret);
        $this->_client->setRedirectUri($config->redirectUri);
        $this->_client->setDeveloperKey($config->apiKey);
        $this->_service = new \Google_AnalyticsService($this->_client);
        $this->_defaultProfile = $config->analyticsProfileId;
    }
    /**
     * By forced killing session we log out the client
     */
    public function logout()
    {
        $this->_session->token = null;
    }
    /**
     * Authenticate application by returned code
     *
     * @param string $code
     */
    public function authenticate( $code )
    {
        $this->_client->authenticate( $code );
        $this->_session->token = $this->_client->getAccessToken();
    }
    /**
     * Required by OAuth authentication flow
     */
    public function updateAccessToken()
    {
        if ($this->_session->token) {
            $this->_client->setAccessToken($this->_session->token);
         }
    }
    /**
     * Required by OAuth authentication flow
     */
    public function validAccessToken()
    {
        $token = "";
        if ($token = $this->_client->getAccessToken()) {
            $this->_session->token = $this->_client->getAccessToken();
        }
        return $token;
    }
    /**
     * Required by OAuth authentication flow
     * @return string
     */
    public function createAuthUrl()
    {
        return $this->_client->createAuthUrl();
    }
    /**
     * Retrieve the first found profile id
     * @return int
     * @throws Exception
     */
    public function getFirstProfileId()
    {
        $accounts = $this->_service->management_accounts->listManagementAccounts();
        if (count($accounts["items"]) > 0) {
          $items = $accounts["items"];
          $firstAccountId = $items[0]["id"];
          $webproperties = $this->_service->management_webproperties
              ->listManagementWebproperties($firstAccountId);
          if (count($webproperties["items"]) > 0) {
            $items = $webproperties["items"];
            $firstWebpropertyId = $items[0]["id"];
            $profiles = $this->_service->management_profiles
                ->listManagementProfiles($firstAccountId, $firstWebpropertyId);
            if (count($profiles["items"]) > 0) {
              $items = $profiles["items"];
              return $items[0]["id"];
            } else {
              throw new Exception('No profiles found for this user.');
            }
          } else {
            throw new Exception('No webproperties found for this user.');
          }
        } else {
          throw new Exception('No accounts found for this user.');
        }
      }

      /**
       * Query helper - get array of the query results
       *
       * @param int $profileId
       * @param string $sDate
       * @param string $eDate
       * @param string $metric
       * @param array $otions
       * @return string
       */
      private function _queryTable($profileId = 0, $sDate, $eDate, $metric, $options)
      {
          $profileId = $profileId ? : $this->_defaultProfile;
          $results = $this->_service->data_ga->get(
            'ga:' . $profileId, $sDate, $eDate, $metric, $options
           );
           return $results["rows"];
      }

    /**
     * Get page views/visitors per day table
     *
     * @param string $sDate
     * @param string $eDate
     * @param int $profileId
     * @return array [[datatime, visits, page views], ...]
     */
      public function queryVisitsPageViews($sDate = "", $eDate = "", $profileId = 0)
      {
          return array_map(function( $val )
          {
              $val[0] = strtotime( $val[0] . " UTC") * 1000;
              return $val;
          }, $this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:visits, ga:pageviews',
                array(
                    'dimensions' => 'ga:date',
                    'sort' => 'ga:date'
                )
            ));
      }
     /**
      * Get visiting percent per country table
      *
      * @param string $sDate
      * @param string $eDate
      * @param int $profileId
      * @return array [[country, percent], ...]
      */
      public function queryCountryPercentage($sDate = "", $eDate = "", $profileId = 0)
      {
          $data = $this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:visits',
                array(
                    'dimensions' => 'ga:country',
                    'sort' => '-ga:visits',
                    'max-results' => '10'
                )
            );
            $max = array_sum(array_map(function( $val ){ return $val[1]; }, $data));
            return array_map(function( $val ) use ( $max )
            {
                $val[1] = ceil($val[1] / $max * 100);
                return $val;

            }, $data);
      }
    /**
     * Get usage percent per browser table
     *
     * @param string $sDate
     * @param string $eDate
     * @param int $profileId
     * @return array [[browser, percent], ...]
     */
      public function queryBrowserPercentage($sDate = "", $eDate = "", $profileId = 0)
      {
          $data = $this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:visits',
                array(
                    'dimensions' => 'ga:browser',
                    'sort' => '-ga:visits',
                    'max-results' => '10'
                )
            );
            $max = array_sum(array_map(function( $val ){ return $val[1]; }, $data));
            $data = array_map(function( $val ) use ( $max )
            {
                $percent = $val[1] / $max * 100;
                $val[1] = $percent >= 1 ? ceil($percent) : 0;
                return $val;

            }, $data);
            return array_filter($data, function( $val ){ return (bool)$val[1]; });
      }
    /**
     * Get list of most popular referrals
     *
     * @param string $sDate
     * @param string $eDate
     * @param int $profileId
     * @return array [[referral, visits], ...]
     */
      public function queryReferrals($sDate = "", $eDate = "", $profileId = 0)
      {
          return array_filter($this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:organicSearches',
                array(
                    'dimensions' => 'ga:source',
                    'sort' => '-ga:organicSearches',
                    'max-results' => '20'
                )
            ), function( $val ){ return (bool)$val[1]; });
      }
    /**
     * Get list of most populat keywords
     *
     * @param string $sDate
     * @param string $eDate
     * @param int $profileId
     * @return array [[keyword, visits], ...]
     */
      public function queryKeywords($sDate = "", $eDate = "", $profileId = 0)
      {
          return array_filter($this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:organicSearches',
                array(
                    'dimensions' => 'ga:keyword',
                    'sort' => '-ga:organicSearches',
                    'max-results' => '15'
                )
            ), function( $val ){ return (bool)$val[1]; });
      }
    /**
     * Get list of most popular pages
     *
     * @param string $sDate
     * @param string $eDate
     * @param int $profileId
     * @return array [[page title, visits], ...]
     */
      public function queryPopularPages($sDate = "", $eDate = "", $profileId = 0)
      {
          return array_filter($this->_queryTable($profileId,
                 date("Y-m-d", strtotime( $sDate ? $sDate : '-1 week' )),
                 date("Y-m-d", strtotime( $eDate ? $eDate : '-1 day' )),
                'ga:pageviews',
                array(
                    'dimensions' => 'ga:pageTitle',
                    'sort' => '-ga:pageviews',
                    'max-results' => '15'
                )
            ), function( $val ){ return (bool)$val[1]; });
      }

}

As you see the facade class relies on GAConfig value object, which we will use to keep GA access credentials:

class GaConfig
{
    public $productName = "Demo";
    public $clientId = "XXXXXXXXXXX";
    public $clientSecret = "XXXXXXXXXXXXXXXXXXXXX";
    public $redirectUri = "http://demo.dev/admin/analytics";
    public $apiKey = "XXXXXXXXXXXXXXXXXX";
    public $analyticsProfileId = 1111111;
}

Have you noticed that facade depends also on \Lib\Storage\Session? Well, that's here nothing more than a simple abstraction of standard PHP session handler:

<?php

namespace Lib\Storage;
class Session
{
    /**
     * Singleton instance
     * @var \Lib\Storage\Session
     */
    private static $_instance = null;

    /**
     * Provides the last created instance
     * @return \Lib\Storage\Session
     */
    public static function getInstance()
    {
        return self::$_instance ? : (self::$_instance = new self());
    }
    /**
     *
     * @param string $cookieName
     */
    public function   __construct($cookieName = 'PHP_SESS_ID')
    {
        session_name($cookieName);
        session_start();
        self::$_instance = $this;
    }

    /**
     * Accessor
     * @param string $name
     * @return mixed
     */
    public function  __get($name)
    {
        return isset ($_SESSION[$name]) ? $_SESSION[$name] : null;
    }
    /**
     * Mutator
     * @param string $name
     * @param mixed $value
     */
    public function  __set($name, $value)
    {
        $_SESSION[$name] = $value;
    }
}

Now we ready with the model we can describe controller example:

<?php

class Analytics
{
   /**
    * View instance
    */
    protected $_view;
	/**
	 * Receives a reference to view object, which is available in templates
	 */
	public function  __construct(&$view)
    {
        $this->_view = &$view;
    }
    /**
     * Report landing page is requested
     */
    public function indexAction()
    {
        $ga = \Api::Plugin_GoogleApi_Api(new GaConfig());

        if ($_REQUEST["logout"]) {
            $ga->logout();
        }

        try {
            if ($_REQUEST["code"]) {
                $ga->authenticate($_REQUEST["code"]);
                $redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
                header('Location: /admin/analytics');
            }
            $ga->updateAccessToken();
            if ($ga->validAccessToken()) {

                $startDate = $_REQUEST["sDate"] ? : date("Y-m-d", strtotime( '-1 week' ));
                $endDate = $_REQUEST["eDate"] ? : date("Y-m-d", strtotime( '-1 day' ));
                $this->_view->startDate = strtotime($startDate);
                $this->_view->endDate = strtotime($endDate);
                $stats = $ga->queryVisitsPageViews($startDate, $endDate);
                $this->_view->queryLastWeek = $stats;
                $this->_view->queryCountryPercentage = $ga->queryCountryPercentage($startDate, $endDate);
                $this->_view->queryBrowserPercentage = $ga->queryBrowserPercentage($startDate, $endDate);
                $this->_view->queryReferrals = $ga->queryReferrals($startDate, $endDate);
                $this->_view->queryKeywords = $ga->queryKeywords($startDate, $endDate);
                $this->_view->queryPopularPages = $ga->queryPopularPages($startDate, $endDate);
            } else {
                $this->_view->authUrl = $ga->createAuthUrl();
            }
        } catch (\Exception $e) {
            $this->_view->exceptionCode = $e->getCode();
            $this->_view->exceptionMessage = $e->getMessage();
        }
    }
}

So, as we have controller assigned view properties ($view), we can use them within template to render report page like that:


Note that to access GA reports user has to go through OAuth authnetication. So writing template we add a condition: if $view->authUrl is truthy we provide only authentication link. E.g.:

<? if ($view->authUrl) : ?>
        <div class="modal default">
            <div class="modal-body">
                <p>This report uses Google Analytics service as a data provider. In order to access the data you
                    have to grand permission to this page at Google.</p>
            </div>
            <div class="modal-footer">
                <a href="<?= $view->authUrl ?>" id="login-submit" class="btn btn-inverse">Grand permission</a>
            </div>
        </div>
<? else: ?>
... Report

Reasonably statistic analyses belong to CMS ACP reports. So, CMS vendors tend to include some into their products. However, they often make statistical data collection and analysis a part of CMS. It usually gains some primitive reports and affects application performance. I believe any sound statistic analysis can be delivered only by a dedicated specialized application. Meantime CMS can be used to represent the reports prepared by that application. Have you ever fancied displaying Google Analytics Reports in your CMS?