├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── Krizon │ └── Google │ └── Analytics │ └── MeasurementProtocol │ ├── MeasurementProtocolClient.php │ └── Resources │ └── service.php └── tests ├── Krizon └── Google │ └── Analytics │ └── MeasurementProtocol │ └── Test │ └── MeasurementProtocolClientTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor/ 3 | phpunit.xml 4 | composer.lock 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [vendor/*] 3 | 4 | tools: 5 | php_pdepend: true 6 | php_code_coverage: true 7 | php_analyzer: true 8 | php_code_sniffer: true 9 | 10 | before_commands: 11 | - "composer install --prefer-source" 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_script: 9 | - composer install --dev 10 | 11 | script: php ./vendor/bin/phpunit 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.0.0 (2015-10-09) 2 | 3 | First stable release 4 | 5 | ## 0.1.0 (2014-04-08) 6 | 7 | Initial development release 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Kristian Zondervan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google Analytics Measurement Protocol PHP Client 2 | =========================================================================================== 3 | 4 | [![Build Status](https://travis-ci.org/krizon/php-ga-measurement-protocol.png?branch=master)](https://travis-ci.org/krizon/php-ga-measurement-protocol) 5 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/krizon/php-ga-measurement-protocol/badges/quality-score.png?s=690ba3465d629f9876678af9ae4a41a346c994ab)](https://scrutinizer-ci.com/g/krizon/php-ga-measurement-protocol/) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/krizon/php-ga-measurement-protocol/badges/coverage.png?s=17fc1b99fc85fec329329b96ecca1838fe3a5b7d)](https://scrutinizer-ci.com/g/krizon/php-ga-measurement-protocol/) 7 | [![Latest Stable Version](https://poser.pugx.org/krizon/php-ga-measurement-protocol/v/stable.png)](https://packagist.org/packages/krizon/php-ga-measurement-protocol) [![Total Downloads](https://poser.pugx.org/krizon/php-ga-measurement-protocol/downloads.png)](https://packagist.org/packages/krizon/php-ga-measurement-protocol) [![Latest Unstable Version](https://poser.pugx.org/krizon/php-ga-measurement-protocol/v/unstable.png)](https://packagist.org/packages/krizon/php-ga-measurement-protocol) [![License](https://poser.pugx.org/krizon/php-ga-measurement-protocol/license.png)](https://packagist.org/packages/krizon/php-ga-measurement-protocol) 8 | 9 | A full featured php client for the Google Analytics Measurment Protocol API. Build upon the shoulders of the great [Guzzle](http://docs.guzzlephp.org/en/latest/). 10 | 11 | See https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide 12 | 13 | Installation 14 | ------------------------------------------------------------------------------------------- 15 | Use [Composer](http://getcomposer.org/doc/00-intro.md) to add this library to your dependencies: 16 | ```bash 17 | $ composer require krizon/php-ga-measurement-protocol 18 | ``` 19 | 20 | Features 21 | ------------------------------------------------------------------------------------------- 22 | - Page tracking 23 | - Event tracking 24 | - Ecommerce tracking 25 | - Social interactions tracking 26 | - Exception tracking 27 | - User timing tracking 28 | - App tracking 29 | - Non-blocking requests (todo) 30 | 31 | Usage 32 | ------------------------------------------------------------------------------------------- 33 | ```php 34 | $config = array( 35 | 'ssl' => true // Enable/Disable SSL, default false 36 | ); 37 | $client = Krizon\Google\Analytics\MeasurementProtocol\MeasurementProtocolClient::factory($config); 38 | $client->pageview(array( 39 | 'tid' => 'UA-XXXX-XXXX', // Tracking Id 40 | 'cid' => 'XXXX-XXXXX-XXXXX', // Customer Id 41 | 'dh' => 'domain.do', 42 | 'dp' => '/php-ga-measurement-protocol/phpunit-test', 43 | 'dt' => 'PHP GA Measurement Protocol' 44 | )); 45 | ``` 46 | 47 | Testing 48 | ------------------------------------------------------------------------------------------- 49 | Before you can run the tests make sure you installed the dependencies using composer: 50 | 51 | ```$ composer install``` 52 | 53 | PHPUnit itself is included in the dependencies so now you can call: 54 | 55 | ```$ vendor/bin/phpunit``` 56 | 57 | We have two types of tests: 58 | 59 | * Tests with mocked 200 OK response, @group ```__nogroup__```. This type of tests are used tor testing required fields, 60 | asserting classtypes etc.; 61 | * Tests that do real calls to the Google API, @group ```internet```. The Google API itself always returns a 200 OK so to 62 | be sure the requests are transferred and handled correctly you can run the tests of group 'internet'. Before running 63 | this group make sure you've configured the correct tracking id in the phpunit.xml configuration by setting the env variable 64 | ```tracking_id```. This group is excluded by default but you can run this tests by calling: 65 | ```$ vendor/bin/phpunit --group internet``` 66 | 67 | Contributors 68 | ------------------------------------------------------------------------------------------- 69 | * [Kristian Zondervan](https://github.com/krizon) 70 | * [Alexandre Assouad](https://github.com/t0k4rt) 71 | * [Matt Weghorst](https://github.com/mattweg) 72 | 73 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "krizon/php-ga-measurement-protocol", 3 | "description": "Easy use of the Google Analytics Measurement Protocol in PHP", 4 | "minimum-stability": "stable", 5 | "require": { 6 | "guzzle/guzzle": "~3.7" 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": ">=3.7.28" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "Kristian Zondervan", 14 | "email": "kristian.zondervan@gmail.com" 15 | } 16 | ], 17 | "suggest": { 18 | "ramsey/uuid": "Generate RFC 4122 UUID with PHP." 19 | }, 20 | "autoload": { 21 | "psr-0": { "Krizon": "src/" } 22 | }, 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests/Krizon/Google/Analytics/MeasurementProtocol/ 6 | 7 | 8 | 9 | 10 | src/Krizon/Google/Analytics/MeasurementProtocol/ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | internet 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Krizon/Google/Analytics/MeasurementProtocol/MeasurementProtocolClient.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Krizon\Google\Analytics\MeasurementProtocol; 13 | 14 | use Guzzle\Common\Collection; 15 | use Guzzle\Http\Message\Request; 16 | use Guzzle\Service\Client; 17 | use Guzzle\Service\Description\ServiceDescription; 18 | 19 | class MeasurementProtocolClient extends Client 20 | { 21 | public static function factory($config = array()) 22 | { 23 | $default = array( 24 | 'ssl' => false, 25 | 'tid' => null 26 | ); 27 | $required = array('ssl'); 28 | 29 | $config = Collection::fromConfig($config, $default, $required); 30 | 31 | $baseUrl = ($config->get('ssl') === true) ? 'https://ssl.google-analytics.com' : 'http://www.google-analytics.com'; 32 | 33 | $client = new self($baseUrl, $config); 34 | 35 | $description = ServiceDescription::factory(__DIR__ . '/Resources/service.php'); 36 | $client->setDescription($description); 37 | 38 | if (true === isset($config['tid'])) { 39 | $client->getEventDispatcher()->addListener('command.before_prepare', function (\Guzzle\Common\Event $e) use($config) { 40 | if (false === $e['command']->hasKey('tid')) { 41 | $e['command']->set('tid', $config['tid']); 42 | } 43 | }); 44 | } 45 | 46 | return $client; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Krizon/Google/Analytics/MeasurementProtocol/Resources/service.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | * for more information, refer to google documentation : 12 | * https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters 13 | * 14 | */ 15 | 16 | return array( 17 | 'name' => 'Google Analytics Measurement Protocol PHP Client', 18 | 'operations' => array( 19 | 'abstract.collect' => array( 20 | 'httpMethod' => 'POST', 21 | 'uri' => 'collect', 22 | 'summary' => 'Collect data', 23 | 'parameters' => array( 24 | 'v' => array( 25 | 'location' => 'postField', 26 | 'default'=> 1, 27 | 'static' => true 28 | ), 29 | 'tid' => array( 30 | 'location' => 'query', 31 | 'required' => true, 32 | ), 33 | 'aip' => array( 34 | 'description' => 'Anonymize IP', 35 | 'location' => 'postField' 36 | ), 37 | 'qt' => array( 38 | 'description' => 'Queue Time', 39 | 'location' => 'postField' 40 | ), 41 | /** User **/ 42 | 'cid' => array( 43 | 'location' => 'postField', 44 | 'required' => true 45 | ), 46 | 'uid' => array( 47 | 'description' => 'User id', 48 | 'location' => 'postField' 49 | ), 50 | /** Content information **/ 51 | 'dl' => array( 52 | 'description' => 'Document location url', 53 | 'location' => 'postField', 54 | ), 55 | 'dh' => array( 56 | 'description' => 'Document hostname', 57 | 'location' => 'postField', 58 | ), 59 | 'dp' => array( 60 | 'description' => 'Document path', 61 | 'location' => 'postField', 62 | ), 63 | 'dt' => array( 64 | 'description' => 'Document title', 65 | 'location' => 'postField', 66 | ), 67 | 'cd' => array( 68 | 'description' => 'Content description', 69 | 'location' => 'postField', 70 | ), 71 | 'linkid' => array( 72 | 'description' => 'The ID of a clicked DOM element', 73 | 'location' => 'postField', 74 | ), 75 | /** Session **/ 76 | 'sc' => array( 77 | 'location' => 'postField', 78 | 'description' => 'Session control', 79 | ), 80 | /** Traffic Sources **/ 81 | 'dr' => array( 82 | 'location' => 'postField', 83 | 'description' => 'Document referrer', 84 | ), 85 | 'cn' => array( 86 | 'location' => 'postField', 87 | 'description' => 'Specifies the campaign name', 88 | ), 89 | 'cs' => array( 90 | 'location' => 'postField', 91 | 'description' => 'Specifies the campaign source', 92 | ), 93 | 'cm' => array( 94 | 'location' => 'postField', 95 | 'description' => 'Specifies the campaign medium', 96 | ), 97 | 'ck' => array( 98 | 'location' => 'postField', 99 | 'description' => 'Specifies the campaign keyword', 100 | ), 101 | 'cc' => array( 102 | 'location' => 'postField', 103 | 'description' => 'Specifies the campaign content', 104 | ), 105 | 'ci' => array( 106 | 'location' => 'postField', 107 | 'description' => 'Specifies the campaign ID', 108 | ), 109 | 'gclid' => array( 110 | 'location' => 'postField', 111 | 'description' => 'Specifies the Google AdWords Id', 112 | ), 113 | 'dclid' => array( 114 | 'location' => 'postField', 115 | 'description' => 'Specifies the Google Display Ads Id', 116 | ), 117 | /** Application tracking **/ 118 | 'an' => array( 119 | 'location' => 'postField', 120 | 'description' => 'Application name', 121 | ), 122 | 'aid' => array( 123 | 'description' => 'Application id', 124 | 'location' => 'postField' 125 | ), 126 | 'av' => array( 127 | 'description' => 'Application version', 128 | 'location' => 'postField' 129 | ), 130 | 'aiid' => array( 131 | 'description' => 'Application installer id', 132 | 'location' => 'postField' 133 | ), 134 | /** Content Experiments **/ 135 | 'xid' => array( 136 | 'location' => 'postField', 137 | 'description' => 'Experiment ID', 138 | ), 139 | 'xvar' => array( 140 | 'description' => 'Experiment variant', 141 | 'location' => 'postField' 142 | ), 143 | /** System info **/ 144 | 'sr' => array( 145 | 'description' => 'Screen resolution', 146 | 'location' => 'postField' 147 | ), 148 | 'vp' => array( 149 | 'description' => 'Viewport size', 150 | 'location' => 'postField' 151 | ), 152 | 'de' => array( 153 | 'description' => 'Document encoding', 154 | 'location' => 'postField', 155 | 'default' => 'UTF-8' 156 | ), 157 | 'sd' => array( 158 | 'description' => 'Screen colors', 159 | 'location' => 'postField' 160 | ), 161 | 'ul' => array( 162 | 'description' => 'User language', 163 | 'location' => 'postField' 164 | ), 165 | 'je' => array( 166 | 'description' => 'Java enabled', 167 | 'location' => 'postField' 168 | ), 169 | 'fl' => array( 170 | 'description' => 'Flash version', 171 | 'location' => 'postField' 172 | ), 173 | /** Hit */ 174 | 'ni' => array( 175 | 'description' => 'Non-Interaction Hit', 176 | 'location' => 'postField' 177 | ), 178 | /** unofficial **/ 179 | 'ua' => array( 180 | 'location' => 'postField', 181 | 'description' => 'User-agent override', 182 | ), 183 | 'uip' => array( 184 | 'description' => 'ip override', 185 | 'location' => 'postField' 186 | ), 187 | 'cm1' => array( 188 | 'description' => 'Custom metric 1', 189 | 'location' => 'postField', 190 | ), 191 | 'cm2' => array('location' => 'postField'), 192 | 'cm3' => array('location' => 'postField'), 193 | 'cm4' => array('location' => 'postField'), 194 | 'cm5' => array('location' => 'postField'), 195 | 'cm6' => array('location' => 'postField'), 196 | 'cm7' => array('location' => 'postField'), 197 | 'cm8' => array('location' => 'postField'), 198 | 'cm9' => array('location' => 'postField'), 199 | 'cm10' => array('location' => 'postField'), 200 | 'cm11' => array('location' => 'postField'), 201 | 'cm12' => array('location' => 'postField'), 202 | 'cm13' => array('location' => 'postField'), 203 | 'cm14' => array('location' => 'postField'), 204 | 'cm15' => array('location' => 'postField'), 205 | 'cm16' => array('location' => 'postField'), 206 | 'cm17' => array('location' => 'postField'), 207 | 'cm18' => array('location' => 'postField'), 208 | 'cm19' => array('location' => 'postField'), 209 | 'cm20' => array('location' => 'postField'), 210 | 'cm21' => array('location' => 'postField'), 211 | 'cm22' => array('location' => 'postField'), 212 | 'cm23' => array('location' => 'postField'), 213 | 'cm24' => array('location' => 'postField'), 214 | 'cm25' => array('location' => 'postField'), 215 | 'cm26' => array('location' => 'postField'), 216 | 'cm27' => array('location' => 'postField'), 217 | 'cm28' => array('location' => 'postField'), 218 | 'cm29' => array('location' => 'postField'), 219 | 'cm30' => array('location' => 'postField'), 220 | 'cm31' => array('location' => 'postField'), 221 | 'cm32' => array('location' => 'postField'), 222 | 'cm33' => array('location' => 'postField'), 223 | 'cm34' => array('location' => 'postField'), 224 | 'cm35' => array('location' => 'postField'), 225 | 'cm36' => array('location' => 'postField'), 226 | 'cm37' => array('location' => 'postField'), 227 | 'cm38' => array('location' => 'postField'), 228 | 'cm39' => array('location' => 'postField'), 229 | 'cm40' => array('location' => 'postField'), 230 | 'cm41' => array('location' => 'postField'), 231 | 'cm42' => array('location' => 'postField'), 232 | 'cm43' => array('location' => 'postField'), 233 | 'cm44' => array('location' => 'postField'), 234 | 'cm45' => array('location' => 'postField'), 235 | 'cm46' => array('location' => 'postField'), 236 | 'cm47' => array('location' => 'postField'), 237 | 'cm48' => array('location' => 'postField'), 238 | 'cm49' => array('location' => 'postField'), 239 | 'cm50' => array('location' => 'postField'), 240 | 241 | 'cd1' => array( 242 | 'description' => 'Custom dimension 1', 243 | 'location' => 'postField', 244 | ), 245 | 'cd2' => array('location' => 'postField'), 246 | 'cd3' => array('location' => 'postField'), 247 | 'cd4' => array('location' => 'postField'), 248 | 'cd5' => array('location' => 'postField'), 249 | 'cd6' => array('location' => 'postField'), 250 | 'cd7' => array('location' => 'postField'), 251 | 'cd8' => array('location' => 'postField'), 252 | 'cd9' => array('location' => 'postField'), 253 | 'cd10' => array('location' => 'postField'), 254 | 'cd11' => array('location' => 'postField'), 255 | 'cd12' => array('location' => 'postField'), 256 | 'cd13' => array('location' => 'postField'), 257 | 'cd14' => array('location' => 'postField'), 258 | 'cd15' => array('location' => 'postField'), 259 | 'cd16' => array('location' => 'postField'), 260 | 'cd17' => array('location' => 'postField'), 261 | 'cd18' => array('location' => 'postField'), 262 | 'cd19' => array('location' => 'postField'), 263 | 'cd20' => array('location' => 'postField'), 264 | 'cd21' => array('location' => 'postField'), 265 | 'cd22' => array('location' => 'postField'), 266 | 'cd23' => array('location' => 'postField'), 267 | 'cd24' => array('location' => 'postField'), 268 | 'cd25' => array('location' => 'postField'), 269 | 'cd26' => array('location' => 'postField'), 270 | 'cd27' => array('location' => 'postField'), 271 | 'cd28' => array('location' => 'postField'), 272 | 'cd29' => array('location' => 'postField'), 273 | 'cd30' => array('location' => 'postField'), 274 | 'cd31' => array('location' => 'postField'), 275 | 'cd32' => array('location' => 'postField'), 276 | 'cd33' => array('location' => 'postField'), 277 | 'cd34' => array('location' => 'postField'), 278 | 'cd35' => array('location' => 'postField'), 279 | 'cd36' => array('location' => 'postField'), 280 | 'cd37' => array('location' => 'postField'), 281 | 'cd38' => array('location' => 'postField'), 282 | 'cd39' => array('location' => 'postField'), 283 | 'cd40' => array('location' => 'postField'), 284 | 'cd41' => array('location' => 'postField'), 285 | 'cd42' => array('location' => 'postField'), 286 | 'cd43' => array('location' => 'postField'), 287 | 'cd44' => array('location' => 'postField'), 288 | 'cd45' => array('location' => 'postField'), 289 | 'cd46' => array('location' => 'postField'), 290 | 'cd47' => array('location' => 'postField'), 291 | 'cd48' => array('location' => 'postField'), 292 | 'cd49' => array('location' => 'postField'), 293 | 'cd50' => array('location' => 'postField') 294 | ) 295 | ), 296 | 'pageview' => array( 297 | 'extends' => 'abstract.collect', 298 | 'parameters' => array( 299 | 't' => array( 300 | 'description' => 'Pageview hit type', 301 | 'location' => 'postField', 302 | 'default' => 'pageview', 303 | 'static' => true 304 | ) 305 | ) 306 | ), 307 | 'event' => array( 308 | 'extends' => 'abstract.collect', 309 | 'parameters' => array( 310 | 't' => array( 311 | 'description' => 'Event hit type', 312 | 'location' => 'postField', 313 | 'default' => 'event', 314 | 'static' => true 315 | ), 316 | 'ec' => array( 317 | 'description' => 'Event category', 318 | 'location' => 'postField', 319 | ), 320 | 'ea' => array( 321 | 'description' => 'Event action', 322 | 'location' => 'postField', 323 | ), 324 | 'el' => array( 325 | 'description' => 'Event label', 326 | 'location' => 'postField', 327 | ), 328 | 'ev' => array( 329 | 'description' => 'Event value', 330 | 'location' => 'postField', 331 | ) 332 | ) 333 | ), 334 | 'transaction' => array( 335 | 'extends' => 'abstract.collect', 336 | 'parameters' => array( 337 | 't' => array( 338 | 'description' => 'Transaction hit type', 339 | 'location' => 'postField', 340 | 'default' => 'transaction', 341 | 'static' => true 342 | ), 343 | 'ti' => array( 344 | 'description' => 'transaction ID', 345 | 'location' => 'postField', 346 | 'required' => true 347 | ), 348 | 'ta' => array( 349 | 'description' => 'Transaction affiliation', 350 | 'location' => 'postField', 351 | ), 352 | 'tr' => array( 353 | 'description' => 'Transaction revenue', 354 | 'location' => 'postField', 355 | ), 356 | 'ts' => array( 357 | 'description' => 'Transaction shipping', 358 | 'location' => 'postField', 359 | ), 360 | 'tt' => array( 361 | 'description' => 'Transaction tax', 362 | 'location' => 'postField', 363 | ), 364 | 'cu' => array( 365 | 'description' => 'Currency code', 366 | 'location' => 'postField', 367 | ), 368 | ) 369 | ), 370 | 'item' => array( 371 | 'extends' => 'abstract.collect', 372 | 'parameters' => array( 373 | 't' => array( 374 | 'description' => 'Item hit type', 375 | 'location' => 'postField', 376 | 'default' => 'item', 377 | 'static' => true 378 | ), 379 | 'ti' => array( 380 | 'description' => 'transaction ID', 381 | 'location' => 'postField', 382 | 'required' => true 383 | ), 384 | 'in' => array( 385 | 'description' => 'Item name', 386 | 'location' => 'postField', 387 | 'required' => true 388 | ), 389 | 'ip' => array( 390 | 'description' => 'Item price', 391 | 'location' => 'postField', 392 | ), 393 | 'iq' => array( 394 | 'description' => 'Item quantity', 395 | 'location' => 'postField', 396 | ), 397 | 'ic' => array( 398 | 'description' => 'Item code / SKU', 399 | 'location' => 'postField', 400 | ), 401 | 'iv' => array( 402 | 'description' => 'Item variation / category', 403 | 'location' => 'postField', 404 | ), 405 | 'cu' => array( 406 | 'description' => 'Currency code', 407 | 'location' => 'postField', 408 | ), 409 | ) 410 | ), 411 | 'social' => array( 412 | 'extends' => 'abstract.collect', 413 | 'parameters' => array( 414 | 't' => array( 415 | 'description' => 'Social hit type', 416 | 'location' => 'postField', 417 | 'default' => 'social', 418 | 'static' => true 419 | ), 420 | 'sa' => array( 421 | 'description' => 'Social Action', 422 | 'location' => 'postField', 423 | 'required' => true 424 | ), 425 | 'sn' => array( 426 | 'description' => 'Social Network', 427 | 'location' => 'postField', 428 | 'required' => true 429 | ), 430 | 'st' => array( 431 | 'description' => 'Social Target', 432 | 'location' => 'postField', 433 | 'required' => true 434 | ), 435 | ) 436 | ), 437 | 'exception' => array( 438 | 'extends' => 'abstract.collect', 439 | 'parameters' => array( 440 | 't' => array( 441 | 'description' => 'Exception hit type', 442 | 'location' => 'postField', 443 | 'default' => 'exception', 444 | 'static' => true 445 | ), 446 | 'exd' => array( 447 | 'description' => 'Exception description', 448 | 'location' => 'postField' 449 | ), 450 | 'exf' => array( 451 | 'description' => 'Exception is fatal?', 452 | 'location' => 'postField' 453 | ) 454 | ) 455 | ), 456 | /** Timing **/ 457 | 'timing' => array( 458 | 'extends' => 'abstract.collect', 459 | 'parameters' => array( 460 | 't' => array( 461 | 'description' => 'Timing hit type', 462 | 'location' => 'postField', 463 | 'default' => 'timing', 464 | 'static' => true 465 | ), 466 | 'utc' => array( 467 | 'description' => 'User timing category', 468 | 'location' => 'postField' 469 | ), 470 | 'utv' => array( 471 | 'description' => 'User timing variable name', 472 | 'location' => 'postField' 473 | ), 474 | 'utt' => array( 475 | 'description' => 'User timing time', 476 | 'location' => 'postField' 477 | ), 478 | 'utl' => array( 479 | 'description' => 'User timing label', 480 | 'location' => 'postField' 481 | ), 482 | 'plt' => array( 483 | 'description' => 'Page Load Time', 484 | 'location' => 'postField' 485 | ), 486 | 'dns' => array( 487 | 'description' => 'DNS Time', 488 | 'location' => 'postField' 489 | ), 490 | 'pdt' => array( 491 | 'description' => 'Page Download Time', 492 | 'location' => 'postField' 493 | ), 494 | 'rrt' => array( 495 | 'description' => 'Redirect Response Time', 496 | 'location' => 'postField' 497 | ), 498 | 'tcp' => array( 499 | 'description' => 'TCP Connect Time', 500 | 'location' => 'postField' 501 | ), 502 | 'srt' => array( 503 | 'description' => 'Server Response Time', 504 | 'location' => 'postField' 505 | ), 506 | 'dit' => array( 507 | 'description' => 'DOM Interactive Time', 508 | 'location' => 'postField' 509 | ), 510 | 'clt' => array( 511 | 'description' => 'Content Load Time', 512 | 'location' => 'postField' 513 | ), 514 | ) 515 | ) 516 | ) 517 | ); 518 | -------------------------------------------------------------------------------- /tests/Krizon/Google/Analytics/MeasurementProtocol/Test/MeasurementProtocolClientTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Krizon\Google\Analytics\MeasurementProtocol\Test; 13 | 14 | use Guzzle\Http\Message\Response; 15 | use Guzzle\Plugin\History\HistoryPlugin; 16 | use Guzzle\Plugin\Mock\MockPlugin; 17 | use Guzzle\Service\Exception\ValidationException; 18 | use Guzzle\Tests\GuzzleTestCase; 19 | use Krizon\Google\Analytics\MeasurementProtocol\MeasurementProtocolClient; 20 | 21 | class MeasurementProtocolClientTest extends GuzzleTestCase 22 | { 23 | /** 24 | * @var null|HistoryPlugin 25 | */ 26 | private $history = null; 27 | 28 | public function testFactoryInitializesClient() 29 | { 30 | $client = MeasurementProtocolClient::factory(); 31 | $this->assertInstanceOf('Krizon\Google\Analytics\MeasurementProtocol\MeasurementProtocolClient', $client); 32 | $this->assertEquals('http://www.google-analytics.com', $client->getBaseUrl()); 33 | $this->assertFalse($client->getConfig('ssl')); 34 | } 35 | 36 | public function testFactoryInitializesClientWithSsl() 37 | { 38 | $client = MeasurementProtocolClient::factory(array( 39 | 'ssl' => true 40 | )); 41 | $this->assertEquals('https://ssl.google-analytics.com', $client->getBaseUrl()); 42 | $this->assertTrue($client->getConfig('ssl')); 43 | } 44 | 45 | public function testAbstractCollect() 46 | { 47 | $response = $this->getResponse('abstract.collect', array( 48 | 'tid' => $this->getTrackingId(), 49 | 'cid' => $this->getCustomerId(), 50 | ), true); 51 | $this->assertEquals(200, $response->getStatusCode()); 52 | } 53 | 54 | public function testAbstractCollectRequiredFields() 55 | { 56 | try { 57 | $this->getResponse('abstract.collect', array(), true); 58 | } catch (ValidationException $e) { 59 | $this->assertInstanceOf('Guzzle\Service\Exception\ValidationException', $e); 60 | $this->assertSame('Validation errors: [tid] is required 61 | [cid] is required', $e->getMessage()); 62 | } 63 | } 64 | 65 | public function testPageview($mockResponse = true) 66 | { 67 | $response = $this->getResponse('pageview', array( 68 | 'tid' => $this->getTrackingId(), 69 | 'cid' => $this->getCustomerId(), 70 | 't' => 'pageview', 71 | 'dh' => 'domain.do', 72 | 'dp' => '/php-ga-measurement-protocol/phpunit-test', 73 | 'dt' => 'PHP GA Measurement Protocol' 74 | ), $mockResponse); 75 | $this->assertEquals(200, $response->getStatusCode()); 76 | } 77 | 78 | /** 79 | * @group internet 80 | */ 81 | public function testPageviewLive() 82 | { 83 | $this->testPageview(false); 84 | } 85 | 86 | public function testEvent($mockResponse = true) 87 | { 88 | $response = $this->getResponse('event', array( 89 | 'tid' => $this->getTrackingId(), 90 | 'cid' => $this->getCustomerId(), 91 | 't' => 'event', 92 | 'ec' => 'Event category', 93 | 'ea' => 'Event action', 94 | 'el' => 'Event label', 95 | 'ev' => 300 96 | ), $mockResponse); 97 | 98 | $this->assertEquals(200, $response->getStatusCode()); 99 | } 100 | 101 | /** 102 | * @group internet 103 | */ 104 | public function testEventLive() 105 | { 106 | $this->testEvent(false); 107 | } 108 | 109 | public function testTransaction($mockResponse = true) 110 | { 111 | $response = $this->getResponse('event', array( 112 | 'tid' => $this->getTrackingId(), 113 | 'cid' => $this->getCustomerId(), 114 | 'ti' => time(), 115 | 'ta' => 'westernWear', 116 | 'tr' => '50.00', 117 | 'ts' => '32.00', 118 | 'tt' => '12.00', 119 | 'cu' => 'EUR' 120 | ), $mockResponse); 121 | 122 | $this->assertEquals(200, $response->getStatusCode()); 123 | } 124 | 125 | /** 126 | * @group internet 127 | */ 128 | public function testTransactionLive() 129 | { 130 | $this->testTransaction(false); 131 | } 132 | 133 | public function testTransactionItem($mockResponse = true) 134 | { 135 | $response = $this->getResponse('event', array( 136 | 'tid' => $this->getTrackingId(), 137 | 'cid' => $this->getCustomerId(), 138 | 'ti' => time(), 139 | 'in' => 'sofa', 140 | 'ip' => 300, 141 | 'iq' => 2, 142 | 'ic' => 'u3eqds43', 143 | 'iv' => 'furniture', 144 | 'cu' => 'EUR' 145 | ), $mockResponse); 146 | 147 | $this->assertEquals(200, $response->getStatusCode()); 148 | } 149 | 150 | /** 151 | * @group internet 152 | */ 153 | public function testTransactionItemLive() 154 | { 155 | $this->testTransactionItem(false); 156 | } 157 | 158 | public function testSocial($mockResponse = true) 159 | { 160 | $response = $this->getResponse('social', array( 161 | 'tid' => $this->getTrackingId(), 162 | 'cid' => $this->getCustomerId(), 163 | 'sa' => 'like', 164 | 'sn' => 'facebook', 165 | 'st' => '/home', 166 | ), $mockResponse); 167 | 168 | $this->assertEquals(200, $response->getStatusCode()); 169 | } 170 | 171 | /** 172 | * @group internet 173 | */ 174 | public function testSocialLive() 175 | { 176 | $this->testSocial(false); 177 | } 178 | 179 | public function testException($mockResponse = true) 180 | { 181 | $response = $this->getResponse('exception', array( 182 | 'tid' => $this->getTrackingId(), 183 | 'cid' => $this->getCustomerId(), 184 | 'exd' => 'IOException', 185 | 'exf' => 1 186 | ), $mockResponse); 187 | 188 | $this->assertEquals(200, $response->getStatusCode()); 189 | } 190 | 191 | /** 192 | * @group internet 193 | */ 194 | public function testExceptionLive() 195 | { 196 | $this->testException(false); 197 | } 198 | 199 | public function testPageLoadTiming($mockResponse = true) 200 | { 201 | $response = $this->getResponse('timing', array( 202 | 'tid' => $this->getTrackingId(), 203 | 'cid' => $this->getCustomerId(), 204 | 't' => 'timing', 205 | 'dns' => 331, 206 | 'pdt' => 400, 207 | 'rrt' => 500, 208 | 'tcp' => 600, 209 | 'srt' => 700, 210 | 'plt' => 3554, 211 | 'dit' => 800, 212 | 'clt' => 900, 213 | 'dh' => 'domain.do', 214 | 'dp' => '/php-ga-measurement-protocol/page-timing-test', 215 | 'dt' => 'PHP GA Measurement Protocol Page Load Timing Test' 216 | ), $mockResponse); 217 | $this->assertEquals(200, $response->getStatusCode()); 218 | } 219 | 220 | /** 221 | * @group internet 222 | */ 223 | public function testPageLoadTimingLive() 224 | { 225 | $this->testPageLoadTiming(false); 226 | } 227 | 228 | public function testUserTiming($mockResponse = true) 229 | { 230 | $response = $this->getResponse('timing', array( 231 | 'tid' => $this->getTrackingId(), 232 | 'cid' => $this->getCustomerId(), 233 | 't' => 'timing', 234 | 'utc' => 'category', 235 | 'utv' => 'lookup', 236 | 'utt' => 10000, 237 | 'utl' => 'label', 238 | ), $mockResponse); 239 | $this->assertEquals(200, $response->getStatusCode()); 240 | } 241 | 242 | /** 243 | * @group internet 244 | */ 245 | public function testUserTimingLive() 246 | { 247 | $this->testUserTiming(false); 248 | } 249 | 250 | public function testTrackingIdAsDefault() 251 | { 252 | $this->getResponse('abstract.collect', array( 253 | 'cid' => $this->getCustomerId(), 254 | ), true, MeasurementProtocolClient::factory(array('tid' => 'X2'))); 255 | $this->assertEquals('X2', $this->history->getLastRequest()->getQuery()->get('tid')); 256 | 257 | } 258 | 259 | public function testTrackingIdAsParam() 260 | { 261 | $this->getResponse('abstract.collect', array( 262 | 'tid' => 'X1', 263 | 'cid' => $this->getCustomerId(), 264 | ), true); 265 | $this->assertEquals('X1', $this->history->getLastRequest()->getQuery()->get('tid')); 266 | } 267 | 268 | public function testTrackingIdAsParamWins() 269 | { 270 | $this->getResponse('abstract.collect', array( 271 | 'tid' => 'X3', 272 | 'cid' => $this->getCustomerId(), 273 | ), true, MeasurementProtocolClient::factory(array('tid' => 'X4'))); 274 | $this->assertEquals('X3', $this->history->getLastRequest()->getQuery()->get('tid')); 275 | } 276 | 277 | /** 278 | * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException 279 | */ 280 | public function testBadGatewayTriggersException() 281 | { 282 | $this->getResponse('abstract.collect', array( 283 | 'cid' => $this->getCustomerId(), 284 | ), true, MeasurementProtocolClient::factory(array('tid' => 'BDGW')), 502); 285 | } 286 | 287 | public function testModifyProxyCurlSetting() 288 | { 289 | $proxy = 'tcp://localhost:80'; 290 | $client = MeasurementProtocolClient::factory(array( 291 | 'tid' => $this->getTrackingId(), 292 | 'curl.options' => array( 293 | 'CURLOPT_PROXY' => $proxy 294 | ) 295 | )); 296 | $this->getResponse('abstract.collect', array( 297 | 'cid' => $this->getCustomerId(), 298 | ), true, $client); 299 | $requestCurlOptions = $this->history->getLastRequest()->getCurlOptions(); 300 | $this->assertSame($proxy, $requestCurlOptions[CURLOPT_PROXY]); 301 | } 302 | 303 | /** 304 | * @param $operation 305 | * @param array $parameters 306 | * @param bool $mockResponse 307 | * @return Response 308 | */ 309 | protected function getResponse($operation, array $parameters, $mockResponse = true, MeasurementProtocolClient $client = null, $statusCode = 200) 310 | { 311 | if (null === $client) { 312 | $client = $this->getServiceBuilder()->get('ga_measurement_protocol'); 313 | } 314 | 315 | if (true === $mockResponse) { 316 | $mock = new MockPlugin(array(new Response($statusCode)), true); 317 | $client->addSubscriber($mock); 318 | } 319 | 320 | $history = new HistoryPlugin(); 321 | $history->setLimit(3); 322 | $client->addSubscriber($history); 323 | $this->history = $history; 324 | 325 | $return = call_user_func(array($client, $operation), $parameters); 326 | 327 | return $return; 328 | } 329 | 330 | protected function getTrackingId() 331 | { 332 | return getenv('tracking_id'); 333 | } 334 | 335 | protected function getCustomerId() 336 | { 337 | return '9dc22b58-e588-4a93-b5e6-89431cd9ef14'; 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | if (!file_exists(dirname(__DIR__) . '/composer.lock')) { 13 | die("Dependencies must be installed using composer:\n\nphp composer.phar install\n\n" 14 | . "See http://getcomposer.org for help with installing composer\n"); 15 | } 16 | 17 | $loader = require dirname(__DIR__) . '/vendor/autoload.php'; 18 | 19 | Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Guzzle\Service\Builder\ServiceBuilder::factory(array( 20 | 'ga_measurement_protocol' => array( 21 | 'class' => 'Krizon\Google\Analytics\MeasurementProtocol\MeasurementProtocolClient', 22 | 'params' => array( 23 | ) 24 | ) 25 | ))); 26 | --------------------------------------------------------------------------------