first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
#!/bin/bash
if ! which brew >/dev/null; then
echo "homebrew is not available. Install it from http://brew.sh"
exit 1
else
echo "homebrew already installed"
fi
if ! which php >/dev/null; then
echo "installing php."
brew install php
else
echo "php already installed"
fi
echo "all dependencies installed."

View File

@@ -0,0 +1,13 @@
#!/bin/sh
set -ex
if [ "$RUN_E2E_TESTS" != "true" ]; then
echo "Skipping end to end tests."
else
echo "Running end to end tests..."
wget https://github.com/segmentio/library-e2e-tester/releases/download/0.4.1-pre1/tester_linux_amd64 -O tester
chmod +x tester
./tester -path='./bin/analytics'
echo "End to end tests completed!"
fi

View File

@@ -0,0 +1,59 @@
# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
multi-test: &multi-test
docker:
- image: php
environment:
XDEBUG_MODE: coverage
steps:
- checkout
- run: sudo apt update
- run: sudo apt install zlib1g-dev
- run: sudo docker-php-ext-install zip
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
- run: composer install -n --prefer-dist
- save_cache:
key: v1-dependencies-{{ checksum "composer.json" }}
paths:
- ./vendor
- restore_cache:
keys:
- node-v1-{{ checksum "composer.json" }}
- node-v1-
- run: yarn install
- save_cache:
key: node-v1-{{ checksum "composer.json" }}
paths:
- node_modules
- run:
name: 'Running unit tests'
command: './vendor/bin/phpunit test'
- run:
name: 'Running E2E tests'
command: '.buildscript/e2e.sh'
test-php7.2:
<<: *multi-test
docker:
- image: circleci/php:7.2-node-browsers
test-php7.4:
<<: *multi-test
docker:
- image: circleci/php:7.4-node-browsers
workflows:
version: 2
multi-test:
jobs:
- test-php7.2
- test-php7.4

View File

@@ -0,0 +1,2 @@
# Global users
@lubird @bsneed @pooyaj

View File

@@ -0,0 +1,7 @@
composer.lock
vendor
composer.phar
test/analytics.log
/.vscode
.phplint-cache
/.idea

View File

@@ -0,0 +1,78 @@
<?php
$header = <<<'EOF'
This file is part of PHP CS Fixer.
(c) Fabien Potencier <fabien@symfony.com>
Dariusz Rumiński <dariusz.ruminski@gmail.com>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
$config = PhpCsFixer\Config::create()
->setIndent(" ")
->setLineEnding("\n")
->setUsingCache(false)
->setRiskyAllowed(true)
->setRules([
'@PHP56Migration' => false,
'@PHPUnit60Migration:risky' => false,
'@Symfony' => false,
'@Symfony:risky' => false,
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'long'],
'blank_line_before_statement' => true,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'escape_implicit_backslashes' => true,
'explicit_indirect_variable' => true,
'explicit_string_variable' => true,
'final_internal_class' => true,
'heredoc_to_nowdoc' => true,
'list_syntax' => ['syntax' => 'long'],
'method_chaining_indentation' => true,
'method_argument_space' => ['ensure_fully_multiline' => true, 'keep_multiple_spaces_after_comma' => true],
'multiline_comment_opening_closing' => true,
'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']],
'no_null_property_initialization' => true,
'no_short_echo_tag' => true,
'no_superfluous_elseif' => true,
'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true,
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_class_elements' => true,
'ordered_imports' => true,
'php_unit_strict' => true,
'php_unit_test_annotation' => true,
'php_unit_test_class_requires_covers' => false,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_order' => true,
'phpdoc_types_order' => true,
'semicolon_after_instruction' => true,
'single_line_comment_style' => true,
'single_quote' => false,
'strict_comparison' => false,
'strict_param' => false,
'yoda_style' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('tests/Fixtures')
->in(__DIR__)
)
;
// special handling of fabbot.io service if it's using too old PHP CS Fixer version
try {
PhpCsFixer\FixerFactory::create()
->registerBuiltInFixers()
->registerCustomFixers($config->getCustomFixers())
->useRuleSet(new PhpCsFixer\RuleSet($config->getRules()));
} catch (PhpCsFixer\ConfigurationException\InvalidConfigurationException $e) {
$config->setRules([]);
} catch (UnexpectedValueException $e) {
$config->setRules([]);
} catch (InvalidArgumentException $e) {
$config->setRules([]);
}
return $config;

View File

@@ -0,0 +1,6 @@
path: ./
jobs: 10
extensions:
- php
exclude:
- vendor

View File

@@ -0,0 +1,277 @@
1.8.0 / 2021-05-31
==================
* Fix socket return response (#174)
* API Endpoint update (#168)
* Update Batch Size Check (#168)
* Remove messageID override capabilities (#163)
* Update flush sleep waiting period (#161)
1.7.0 / 2021-03-10
=======================
* Retry Network errors (#136)
* Update Tests [Improvement] (#132)
* Updtate Readme Status Badging (#139)
* Bump e2e tests to latest version [Improvement] (#142)
* Add Limits to message, batch and memeory usage [Feature] (#137)
* Add Configurable flush parameters [Feature] (#135)
* Add ability to use custom consumer [Feature] (#61)
* Add ability to set file permmissions [Feature] (#122)
* Fix curl error handler [Improvement] (#97)
* Fix timestamp implementation for microseconds (#94)
* Modify max queue size setting to match requirements (#153, #146)
* Add ability to set userid as zero (#157)
1.6.1-beta / 2018-05-01
=======================
* Fix tslint error in version.php
1.6.0-beta / 2018-04-30
=======================
* Add License file
* Coding style fixers (#112)
* rename type to method to match new harness contract (#110)
* Increase Code coverage (#108)
* Add Linter to CI (#109)
* When the message size is larger than 32KB, return FALSE instead of throw exception
* Make writeKey required as a flag in the CLI instead of as an environment variable.
* Verify message size is below than 32KB
* Run E2E test when RUN_E2E_TESTS is defined
* Add Rfc 7231 compliant user agent into request header
* Add backoff for socket communication
* Implement response error handling for POST request and add backoff (in LibCurl)
* Change environment to precise as default
* CI: Make PHP 5.3 test to be run in precise environment
* Make host to be configurable
* Add anonymousId in group payload
1.5.2 / 2017-08-18
==================
* Always set default context.
1.5.1 / 2017-04-06
==================
* Use require_once() instead of require(). Fixes issue where seperate plugins in systems such as Moodle break because of class redeclaration when using seperate internal versions of Segment.io.
1.5.0 / 2017-03-03
==================
* Adding context.library.consumer to all PHP events
* libcurl consumer will retry once if http response is not 200
* update link to php docs
* improve portability and reliability of Makefile accros different platforms (#74)
1.4.2 / 2016-07-11
==================
* remove the extra -e from echo in makefile
1.4.1 / 2016-07-11
==================
* use a more portable shebang
1.4.0 / 2016-07-11
==================
* adding a simple wrapper CLI
* explicitly declare library version in global scope during creating new release to allow using library with custom autoload (composer for example)
1.3.0 / 2016-04-05
==================
* Introducing libcurl consumer
* Change Consumer to protected instead of private
1.2.7 / 2016-03-04
==================
* adding global
1.2.6 / 2016-03-04
==================
* fix version
1.2.5 / 2016-03-04
==================
* Adding release script, fixing version
* Pass back ->flush() result to allow caller code know if flushed successfully
1.2.4 / 2016-02-17
=============
* core: fix error name
* send: make send.php executable
* socket: adding fix for FIN packets from remote
1.2.3 / 2016-02-01
==================
* instead of using just is_int and is_float for checking timestamp, use filter_var since that can detect string ints and floats - if its not a string or float, consider it might be a ISO8601 or some other string, so use strtotime() to support other strings
1.2.1 / 2015-12-29
==================
* socket open error checking fix
* Fix batch size check before flushing tracking queue
* Fix bug in send.php
1.2.0 / 2015-04-27
==================
* removing outdated test
* enabling ssl by default
* socket: bump timeout to 5s
1.1.3 / 2015-03-03
==================
* formatTime: use is_* and fix to support floats
1.1.2 / 2015-03-03
==================
* send.php: fix error handling
* client: fix float timestamp handling
1.1.1 / 2015-02-11
==================
* Add updated PHP version requirement for @phpunit
* add .sentAt
1.1.0 / 2015-01-07
==================
* support microtime
* Update README.md
* drop the io
1.0.3 / 2014-10-14
==================
* fix: empty array for traits and properties
1.0.2 / 2014-09-29
==================
* fix: identify(), group() with empty traits
* suppressing logs generated when attempting to write to a reset socket [j0ew00ds]
* Added PHP 5.6, 5.5 and HHVM to travis.yml [Nyholm]
1.0.1 / 2014-09-16
==================
* fixing validation for Segment::page() calls
* updating send.php error message
* fix send.php to exit gracefully when there is no log file to process
1.0.0 / 2014-06-16
==================
* update to work with new spec
* add ./composer.phar validation test
* better send.php output
* add validation
* use strtotime in send.php and support php5.3
* rename Analytics to Segment
* add send.php to replace file_reader.py
* add new methods implementation and tests
* implement spec changes
* change tests to reflect spec changes
* test changes:
* Fix typo in composer.json
0.4.8 / 8-21-2013
=============
* adding fix for socket requests which might complete in multiple fwrites
0.4.7 / 5-28-2013
=============
* `chmod` the log file to 0777 so that the file_reader.py can read it
0.4.6 / 5-25-2013
=============
* Check for status existing on response thanks to [@gmoreira](https://github.om/gmoreira)
0.4.5 / 5-20-2013
=============
* Check for empty secret thanks to [@mustela](https://github.com/mustela).
0.4.3 / 5-1-2013
=============
* Make file_reader rename to a file in the same directory as the log file thanks to [@marshally](https://github.com/marshally)
0.4.2 / 4-26-2013
=============
* Fix for $written var on connection error thanks to [@gmoreira](https://github.com/gmoreira)
0.4.1 / 4-25-2013
=============
* Adding fix to file_reader alias
0.4.0 / 4-8-2013
=============
* Full Autoloading an PEAR naming by [Cethy](https://github.com/Cethy)
* Adding alias call
0.3.0 / 3-22-2013
=============
* Adding try-catch around fwrite cal
0.2.7 / 3-17-2013
=============
* Adding file_reader.py fix
0.2.6 / 3-15-2013
=============
* Rename analytics.php -> Analytics.php to allow autoloading by [Cethy](https://github.com/Cethy)
0.2.5 / 2-22-2013
=============
* Trailing whitespace/end php tags fix by [jimrubenstein](https://github.com/jimrubenstein)
0.2.4 / 2-19-2013
=============
* Support fwrite retry on closed socket.
0.2.3 / 2-12-2013
=============
* Adding check for count in properties and traits length.
0.2.2 / 2-11-2013
=============
* Adding default args for properties
0.2.1 / 2-1-2013
=============
* Enabling pfsockopen for persistent connections
* Making socket default
0.2.0 / 2-1-2013
=============
* Updating consumer class to use shared functions.
* Removed *fork* consumer, renamed *fork_queue* to *fork_curl*.
0.1.1 / 1-30-2013
=============
* Adding fork consumer
* Adding fork_queue consumer
* Setting fork_queue consumer to be the default.
0.1.0 / 1-29-2013
=============
Initial version

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2014 Segment Inc. friends@segment.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,40 @@
bootstrap:
.buildscript/bootstrap.sh
dependencies: vendor
vendor: composer.phar
@php ./composer.phar install
composer.phar:
@curl -sS https://getcomposer.org/installer | php
test: lint
@vendor/bin/phpunit --colors test/
@php ./composer.phar validate
lint: dependencies
@if php -r 'exit(version_compare(PHP_VERSION, "5.5", ">=") ? 0 : 1);'; \
then \
php ./composer.phar require overtrue/phplint --dev; \
php ./composer.phar require squizlabs/php_codesniffer --dev; \
./vendor/bin/phplint; \
./vendor/bin/phpcs; \
else \
printf "Please update PHP version to 5.5 or above for code formatting."; \
fi
release:
@printf "releasing ${VERSION}..."
@printf '<?php\nglobal $$SEGMENT_VERSION;\n$$SEGMENT_VERSION = "%b";\n' ${VERSION} > ./lib/Segment/Version.php
@node -e "var fs = require('fs'), pkg = require('./composer'); pkg.version = '${VERSION}'; fs.writeFileSync('./composer.json', JSON.stringify(pkg, null, '\t'));"
@git changelog -t ${VERSION}
@git release ${VERSION}
clean:
rm -rf \
composer.phar \
vendor \
composer.lock
.PHONY: boostrap release clean

View File

@@ -0,0 +1,80 @@
analytics-php
==============
[![Segmentio](https://circleci.com/gh/segmentio/analytics-php.svg?style=shield&circle-token=a20a4b1daa865d164e62ca59a65d6753d286c1c1)](https://app.circleci.com/pipelines/github/segmentio/analytics-php)
analytics-php is a php client for [Segment](https://segment.com)
<div align="center">
<img src="https://user-images.githubusercontent.com/16131737/53616825-4bd81c00-3b99-11e9-819a-f5fdf527d3c4.png"/>
<p><b><i>You can't fix what you can't measure</i></b></p>
</div>
Analytics helps you measure your users, product, and business. It unlocks insights into your app's funnel, core business metrics, and whether you have product-market fit.
## How to get started
1. **Collect analytics data** from your app(s).
- The top 200 Segment companies collect data from 5+ source types (web, mobile, server, CRM, etc.).
2. **Send the data to analytics tools** (for example, Google Analytics, Amplitude, Mixpanel).
- Over 250+ Segment companies send data to eight categories of destinations such as analytics tools, warehouses, email marketing and remarketing systems, session recording, and more.
3. **Explore your data** by creating metrics (for example, new signups, retention cohorts, and revenue generation).
- The best Segment companies use retention cohorts to measure product market fit. Netflix has 70% paid retention after 12 months, 30% after 7 years.
[Segment](https://segment.com) collects analytics data and allows you to send it to more than 250 apps (such as Google Analytics, Mixpanel, Optimizely, Facebook Ads, Slack, Sentry) just by flipping a switch. You only need one Segment code snippet, and you can turn integrations on and off at will, with no additional code. [Sign up with Segment today](https://app.segment.com/signup).
### Why?
1. **Power all your analytics apps with the same data**. Instead of writing code to integrate all of your tools individually, send data to Segment, once.
2. **Install tracking for the last time**. We're the last integration you'll ever need to write. You only need to instrument Segment once. Reduce all of your tracking code and advertising tags into a single set of API calls.
3. **Send data from anywhere**. Send Segment data from any device, and we'll transform and send it on to any tool.
4. **Query your data in SQL**. Slice, dice, and analyze your data in detail with Segment SQL. We'll transform and load your customer behavioral data directly from your apps into Amazon Redshift, Google BigQuery, or Postgres. Save weeks of engineering time by not having to invent your own data warehouse and ETL pipeline.
For example, you can capture data on any app:
```js
analytics.track('Order Completed', { price: 99.84 })
```
Then, query the resulting data in SQL:
```sql
select * from app.order_completed
order by price desc
```
### 🚀 Startup Program
<div align="center">
<a href="https://segment.com/startups"><img src="https://user-images.githubusercontent.com/16131737/53128952-08d3d400-351b-11e9-9730-7da35adda781.png" /></a>
</div>
If you are part of a new startup (&lt;$5M raised, &lt;2 years since founding), we just launched a new startup program for you. You can get a Segment Team plan (up to <b>$25,000 value</b> in Segment credits) for free up to 2 years — <a href="https://segment.com/startups/">apply here</a>!
## Documentation
Documentation is available at [segment.com/docs/sources/server/php](https://segment.com/docs/sources/server/php/)
## Releasing
Run `make release VERSION=<version>`. It should automatically tag and release in composer
## License
```
WWWWWW||WWWWWW
W W W||W W W
||
( OO )__________
/ | \
/o o| MIT \
\___/||_||__||_|| *
|| || || ||
_||_|| _||_||
(__|__|(__|__|
```
(The MIT License)
Copyright (c) 2014 Segment Inc. <friends@segment.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,6 @@
Releasing
=========
1. Run `VERSION=X.Y.Z make release` (where X.Y.Z is the new version).
That's it! Composer will pick up the new tag and you can see the latest version at https://packagist.org/packages/segmentio/analytics-php.

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env php
<?php
require_once(__DIR__ . '/../lib/Segment.php');
if (in_array('--help', $argv)) {
print(usage());
exit;
}
date_default_timezone_set('UTC');
$options = getopt('', array(
'writeKey::',
'type:',
'userId::',
'event::',
'properties::',
'name::',
'traits::',
'groupId::',
'previousId::'
));
if (empty($options['writeKey'])) {
error('writeKey flag required');
}
Segment::init($options['writeKey']);
switch ($options['type']) {
case 'track':
Segment::track(array(
'userId' => $options['userId'],
'event' => $options['event'],
'properties' => parse_json($options['properties'])
));
break;
case 'identify':
Segment::identify(array(
'userId' => $options['userId'],
'traits' => parse_json($options['traits'])
));
break;
case 'page':
Segment::page(array(
'userId' => $options['userId'],
'name' => $options['name'],
'properties' => parse_json($options['properties'])
));
break;
case 'group':
Segment::identify(array(
'userId' => $options['userId'],
'groupId' => $options['groupId'],
'traits' => parse_json($options['traits'])
));
break;
case 'alias':
Segment::alias(array(
'userId' => $options['userId'],
'previousId' => $options['previousId']
));
break;
default:
error(usage());
break;
}
Segment::flush();
function usage() {
return "\n Usage: analytics --type <track|identify|page|group|alias> [options]\n\n";
}
function error($message) {
print("$message\n\n");
exit(1);
}
function parse_json($input) {
if (empty($input)) {
return null;
}
return json_decode($input);
}
function parse_timestamp($input) {
if (empty($input)) {
return null;
}
return strtotime($input);
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,35 @@
{
"name": "segmentio/analytics-php",
"version": "1.8.0",
"description": "Segment Analytics PHP Library",
"keywords": [
"analytics",
"segmentio",
"segment",
"analytics.js"
],
"homepage": "https://segment.com/libraries/php",
"license": "MIT",
"authors": [
{
"name": "Segment.io <friends@segment.com>",
"homepage": "https://segment.com/"
}
],
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"overtrue/phplint": "^1.1",
"squizlabs/php_codesniffer": "^3.3"
},
"autoload": {
"files": [
"lib/Segment.php"
]
},
"bin": [
"bin/analytics"
]
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,154 @@
<?php
require_once __DIR__ . '/Segment/Client.php';
class Segment {
private static $client;
/**
* Initializes the default client to use. Uses the libcurl consumer by default.
* @param string $secret your project's secret key
* @param array $options passed straight to the client
*/
public static function init($secret, $options = array()) {
self::assert($secret, "Segment::init() requires secret");
self::$client = new Segment_Client($secret, $options);
}
/**
* Tracks a user action
*
* @param array $message
* @return boolean whether the track call succeeded
*/
public static function track(array $message) {
self::checkClient();
$event = !empty($message["event"]);
self::assert($event, "Segment::track() expects an event");
self::validate($message, "track");
return self::$client->track($message);
}
/**
* Tags traits about the user.
*
* @param array $message
* @return boolean whether the identify call succeeded
*/
public static function identify(array $message) {
self::checkClient();
$message["type"] = "identify";
self::validate($message, "identify");
return self::$client->identify($message);
}
/**
* Tags traits about the group.
*
* @param array $message
* @return boolean whether the group call succeeded
*/
public static function group(array $message) {
self::checkClient();
$groupId = !empty($message['groupId']);
self::assert($groupId, "Segment::group() expects a groupId");
self::validate($message, "group");
return self::$client->group($message);
}
/**
* Tracks a page view
*
* @param array $message
* @return boolean whether the page call succeeded
*/
public static function page(array $message) {
self::checkClient();
self::validate($message, "page");
return self::$client->page($message);
}
/**
* Tracks a screen view
*
* @param array $message
* @return boolean whether the screen call succeeded
*/
public static function screen(array $message) {
self::checkClient();
self::validate($message, "screen");
return self::$client->screen($message);
}
/**
* Aliases the user id from a temporary id to a permanent one
*
* @param array $from user id to alias from
* @return boolean whether the alias call succeeded
*/
public static function alias(array $message) {
self::checkClient();
$userId = (array_key_exists('userId', $message) && strlen((string) $message['userId']) > 0);
$previousId = (array_key_exists('previousId', $message) && strlen((string) $message['previousId']) > 0);
self::assert($userId && $previousId, "Segment::alias() requires both userId and previousId");
return self::$client->alias($message);
}
/**
* Validate common properties.
*
* @param array $message
* @param string $type
*/
public static function validate($message, $type){
$userId = (array_key_exists('userId', $message) && strlen((string) $message['userId']) > 0);
$anonId = !empty($message['anonymousId']);
self::assert($userId || $anonId, "Segment::${type}() requires userId or anonymousId");
}
/**
* Flush the client
*/
public static function flush(){
self::checkClient();
return self::$client->flush();
}
/**
* Check the client.
*
* @throws Exception
*/
private static function checkClient(){
if (null != self::$client) {
return;
}
throw new Exception("Segment::init() must be called before any other tracking method.");
}
/**
* Assert `value` or throw.
*
* @param array $value
* @param string $msg
* @throws Exception
*/
private static function assert($value, $msg) {
if (!$value) {
throw new Exception($msg);
}
}
}
if (!function_exists('json_encode')) {
throw new Exception('Segment needs the JSON PHP extension.');
}

View File

@@ -0,0 +1,265 @@
<?php
require_once(__DIR__ . '/Consumer.php');
require_once(__DIR__ . '/QueueConsumer.php');
require_once(__DIR__ . '/Consumer/File.php');
require_once(__DIR__ . '/Consumer/ForkCurl.php');
require_once(__DIR__ . '/Consumer/LibCurl.php');
require_once(__DIR__ . '/Consumer/Socket.php');
require_once(__DIR__ . '/Version.php');
class Segment_Client {
protected $consumer;
/**
* Create a new analytics object with your app's secret
* key
*
* @param string $secret
* @param array $options array of consumer options [optional]
* @param string Consumer constructor to use, libcurl by default.
*
*/
public function __construct($secret, $options = array()) {
$consumers = array(
"socket" => "Segment_Consumer_Socket",
"file" => "Segment_Consumer_File",
"fork_curl" => "Segment_Consumer_ForkCurl",
"lib_curl" => "Segment_Consumer_LibCurl"
);
// Use our socket libcurl by default
$consumer_type = isset($options["consumer"]) ? $options["consumer"] :
"lib_curl";
if (!array_key_exists($consumer_type, $consumers) && class_exists($consumer_type)) {
if (!is_subclass_of($consumer_type, Segment_Consumer::class)) {
throw new Exception('Consumers must extend the Segment_Consumer abstract class');
}
// Try to resolve it by class name
$this->consumer = new $consumer_type($secret, $options);
return;
}
$Consumer = $consumers[$consumer_type];
$this->consumer = new $Consumer($secret, $options);
}
public function __destruct() {
$this->consumer->__destruct();
}
/**
* Tracks a user action
*
* @param array $message
* @return [boolean] whether the track call succeeded
*/
public function track(array $message) {
$message = $this->message($message, "properties");
$message["type"] = "track";
return $this->consumer->track($message);
}
/**
* Tags traits about the user.
*
* @param [array] $message
* @return [boolean] whether the track call succeeded
*/
public function identify(array $message) {
$message = $this->message($message, "traits");
$message["type"] = "identify";
return $this->consumer->identify($message);
}
/**
* Tags traits about the group.
*
* @param [array] $message
* @return [boolean] whether the group call succeeded
*/
public function group(array $message) {
$message = $this->message($message, "traits");
$message["type"] = "group";
return $this->consumer->group($message);
}
/**
* Tracks a page view.
*
* @param [array] $message
* @return [boolean] whether the page call succeeded
*/
public function page(array $message) {
$message = $this->message($message, "properties");
$message["type"] = "page";
return $this->consumer->page($message);
}
/**
* Tracks a screen view.
*
* @param [array] $message
* @return [boolean] whether the screen call succeeded
*/
public function screen(array $message) {
$message = $this->message($message, "properties");
$message["type"] = "screen";
return $this->consumer->screen($message);
}
/**
* Aliases from one user id to another
*
* @param array $message
* @return boolean whether the alias call succeeded
*/
public function alias(array $message) {
$message = $this->message($message);
$message["type"] = "alias";
return $this->consumer->alias($message);
}
/**
* Flush any async consumers
* @return boolean true if flushed successfully
*/
public function flush() {
if (method_exists($this->consumer, 'flush')) {
return $this->consumer->flush();
}
return true;
}
/**
* @return Segment_Consumer
*/
public function getConsumer() {
return $this->consumer;
}
/**
* Formats a timestamp by making sure it is set
* and converting it to iso8601.
*
* The timestamp can be time in seconds `time()` or `microtime(true)`.
* any other input is considered an error and the method will return a new date.
*
* Note: php's date() "u" format (for microseconds) has a bug in it
* it always shows `.000` for microseconds since `date()` only accepts
* ints, so we have to construct the date ourselves if microtime is passed.
*
* @param ts $timestamp - time in seconds (time())
*/
private function formatTime($ts) {
// time()
if (null == $ts || !$ts) {
$ts = time();
}
if (false !== filter_var($ts, FILTER_VALIDATE_INT)) {
return date("c", (int) $ts);
}
// anything else try to strtotime the date.
if (false === filter_var($ts, FILTER_VALIDATE_FLOAT)) {
if (is_string($ts)) {
return date("c", strtotime($ts));
}
return date("c");
}
// fix for floatval casting in send.php
$parts = explode(".", (string)$ts);
if (!isset($parts[1])) {
return date("c", (int)$parts[0]);
}
// microtime(true)
$sec = $parts[0];
$usec = $parts[1];
$fmt = sprintf("Y-m-d\TH:i:s.%sP", $usec);
return date($fmt, (int)$sec);
}
/**
* Add common fields to the given `message`
*
* @param array $msg
* @param string $def
* @return array
*/
private function message($msg, $def = ""){
if ($def && !isset($msg[$def])) {
$msg[$def] = array();
}
if ($def && empty($msg[$def])) {
$msg[$def] = (object)$msg[$def];
}
if (!isset($msg["context"])) {
$msg["context"] = array();
}
$msg["context"] = array_merge($this->getDefaultContext(), $msg["context"]);
if (!isset($msg["timestamp"])) {
$msg["timestamp"] = null;
}
$msg["timestamp"] = $this->formatTime($msg["timestamp"]);
if (!isset($msg["messageId"])) {
$msg["messageId"] = self::messageId();
}
return $msg;
}
/**
* Generate a random messageId.
*
* https://gist.github.com/dahnielson/508447#file-uuid-php-L74
*
* @return string
*/
private static function messageId(){
return sprintf("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
/**
* Add the segment.io context to the request
* @return array additional context
*/
private function getDefaultContext() {
global $SEGMENT_VERSION;
return array(
"library" => array(
"name" => "analytics-php",
"version" => $SEGMENT_VERSION,
"consumer" => $this->consumer->getConsumer()
)
);
}
}

View File

@@ -0,0 +1,100 @@
<?php
abstract class Segment_Consumer {
protected $type = "Consumer";
protected $options;
protected $secret;
/**
* Store our secret and options as part of this consumer
* @param string $secret
* @param array $options
*/
public function __construct($secret, $options = array()) {
$this->secret = $secret;
$this->options = $options;
}
/**
* Tracks a user action
*
* @param array $message
* @return boolean whether the track call succeeded
*/
abstract public function track(array $message);
/**
* Tags traits about the user.
*
* @param array $message
* @return boolean whether the identify call succeeded
*/
abstract public function identify(array $message);
/**
* Tags traits about the group.
*
* @param array $message
* @return boolean whether the group call succeeded
*/
abstract public function group(array $message);
/**
* Tracks a page view.
*
* @param array $message
* @return boolean whether the page call succeeded
*/
abstract public function page(array $message);
/**
* Tracks a screen view.
*
* @param array $message
* @return boolean whether the group call succeeded
*/
abstract public function screen(array $message);
/**
* Aliases from one user id to another
*
* @param array $message
* @return boolean whether the alias call succeeded
*/
abstract public function alias(array $message);
/**
* Check whether debug mode is enabled
* @return boolean
*/
protected function debug() {
return isset($this->options["debug"]) ? $this->options["debug"] : false;
}
/**
* Check whether we should connect to the API using SSL. This is enabled by
* default with connections which make batching requests. For connections
* which can save on round-trip times, you may disable it.
* @return boolean
*/
protected function ssl() {
return isset($this->options["ssl"]) ? $this->options["ssl"] : true;
}
/**
* On an error, try and call the error handler, if debugging output to
* error_log as well.
* @param string $code
* @param string $msg
*/
protected function handleError($code, $msg) {
if (isset($this->options['error_handler'])) {
$handler = $this->options['error_handler'];
$handler($code, $msg);
}
if ($this->debug()) {
error_log("[Analytics][" . $this->type . "] " . $msg);
}
}
}

View File

@@ -0,0 +1,120 @@
<?php
class Segment_Consumer_File extends Segment_Consumer {
protected $type = "File";
private $file_handle;
/**
* The file consumer writes track and identify calls to a file.
* @param string $secret
* @param array $options
* string "filename" - where to log the analytics calls
*/
public function __construct($secret, $options = array()) {
if (!isset($options["filename"])) {
$options["filename"] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "analytics.log";
}
parent::__construct($secret, $options);
try {
$this->file_handle = fopen($options["filename"], "a");
if (isset($options["filepermissions"])) {
chmod($options["filename"], $options["filepermissions"]);
} else {
chmod($options["filename"], 0777);
}
} catch (Exception $e) {
$this->handleError($e->getCode(), $e->getMessage());
}
}
public function __destruct() {
if ($this->file_handle &&
"Unknown" != get_resource_type($this->file_handle)) {
fclose($this->file_handle);
}
}
//define getter method for consumer type
public function getConsumer() {
return $this->type;
}
/**
* Tracks a user action
*
* @param array $message
* @return [boolean] whether the track call succeeded
*/
public function track(array $message) {
return $this->write($message);
}
/**
* Tags traits about the user.
*
* @param array $message
* @return [boolean] whether the identify call succeeded
*/
public function identify(array $message) {
return $this->write($message);
}
/**
* Tags traits about the group.
*
* @param array $message
* @return [boolean] whether the group call succeeded
*/
public function group(array $message) {
return $this->write($message);
}
/**
* Tracks a page view.
*
* @param array $message
* @return [boolean] whether the page call succeeded
*/
public function page(array $message) {
return $this->write($message);
}
/**
* Tracks a screen view.
*
* @param array $message
* @return [boolean] whether the screen call succeeded
*/
public function screen(array $message) {
return $this->write($message);
}
/**
* Aliases from one user id to another
*
* @param array $message
* @return boolean whether the alias call succeeded
*/
public function alias(array $message) {
return $this->write($message);
}
/**
* Writes the API call to a file as line-delimited json
* @param [array] $body post body content.
* @return [boolean] whether the request succeeded
*/
private function write($body) {
if (!$this->file_handle) {
return false;
}
$content = json_encode($body);
$content.= "\n";
return fwrite($this->file_handle, $content) == strlen($content);
}
}

View File

@@ -0,0 +1,100 @@
<?php
class Segment_Consumer_ForkCurl extends Segment_QueueConsumer {
protected $type = "ForkCurl";
/**
* Creates a new queued fork consumer which queues fork and identify
* calls before adding them to
* @param string $secret
* @param array $options
* boolean "debug" - whether to use debug output, wait for response.
* number "max_queue_size" - the max size of messages to enqueue
* number "flush_at" - how many messages to send in a single request
*/
public function __construct($secret, $options = array()) {
parent::__construct($secret, $options);
}
//define getter method for consumer type
public function getConsumer() {
return $this->type;
}
/**
* Make an async request to our API. Fork a curl process, immediately send
* to the API. If debug is enabled, we wait for the response.
* @param array $messages array of all the messages to send
* @return boolean whether the request succeeded
*/
public function flushBatch($messages) {
$body = $this->payload($messages);
$payload = json_encode($body);
// Escape for shell usage.
$payload = escapeshellarg($payload);
$secret = escapeshellarg($this->secret);
$protocol = $this->ssl() ? "https://" : "http://";
if ($this->host) {
$host = $this->host;
} else {
$host = "api.segment.io";
}
$path = "/v1/batch";
$url = $protocol . $host . $path;
$cmd = "curl -u ${secret}: -X POST -H 'Content-Type: application/json'";
$tmpfname = "";
if ($this->compress_request) {
// Compress request to file
$tmpfname = tempnam("/tmp", "forkcurl_");
$cmd2 = "echo " . $payload . " | gzip > " . $tmpfname;
exec($cmd2, $output, $exit);
if (0 != $exit) {
$this->handleError($exit, $output);
return false;
}
$cmd.= " -H 'Content-Encoding: gzip'";
$cmd.= " --data-binary '@" . $tmpfname . "'";
} else {
$cmd.= " -d " . $payload;
}
$cmd.= " '" . $url . "'";
// Verify message size is below than 32KB
if (strlen($payload) >= 32 * 1024) {
$msg = "Message size is larger than 32KB";
error_log("[Analytics][" . $this->type . "] " . $msg);
return false;
}
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
$library = $messages[0]['context']['library'];
$libName = $library['name'];
$libVersion = $library['version'];
$cmd.= " -H 'User-Agent: ${libName}/${libVersion}'";
if (!$this->debug()) {
$cmd .= " > /dev/null 2>&1 &";
}
exec($cmd, $output, $exit);
if (0 != $exit) {
$this->handleError($exit, $output);
}
if ($tmpfname != "") {
unlink($tmpfname);
}
return 0 == $exit;
}
}

View File

@@ -0,0 +1,114 @@
<?php
class Segment_Consumer_LibCurl extends Segment_QueueConsumer {
protected $type = "LibCurl";
/**
* Creates a new queued libcurl consumer
* @param string $secret
* @param array $options
* boolean "debug" - whether to use debug output, wait for response.
* number "max_queue_size" - the max size of messages to enqueue
* number "flush_at" - how many messages to send in a single request
*/
public function __construct($secret, $options = array()) {
parent::__construct($secret, $options);
}
//define getter method for consumer type
public function getConsumer() {
return $this->type;
}
/**
* Make a sync request to our API. If debug is
* enabled, we wait for the response
* and retry once to diminish impact on performance.
* @param array $messages array of all the messages to send
* @return boolean whether the request succeeded
*/
public function flushBatch($messages) {
$body = $this->payload($messages);
$payload = json_encode($body);
$secret = $this->secret;
if ($this->compress_request) {
$payload = gzencode($payload);
}
$protocol = $this->ssl() ? "https://" : "http://";
if ($this->host) {
$host = $this->host;
} else {
$host = "api.segment.io";
}
$path = "/v1/batch";
$url = $protocol . $host . $path;
$backoff = 100; // Set initial waiting time to 100ms
while ($backoff < $this->maximum_backoff_duration) {
$start_time = microtime(true);
// open connection
$ch = curl_init();
// set the url, number of POST vars, POST data
curl_setopt($ch, CURLOPT_USERPWD, $secret . ':');
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
// set variables for headers
$header = array();
$header[] = 'Content-Type: application/json';
if ($this->compress_request) {
$header[] = 'Content-Encoding: gzip';
}
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
$library = $messages[0]['context']['library'];
$libName = $library['name'];
$libVersion = $library['version'];
$header[] = "User-Agent: ${libName}/${libVersion}";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// retry failed requests just once to diminish impact on performance
$responseContent = curl_exec($ch);
$err = curl_error($ch);
if ($err) {
$this->handleError(curl_errno($ch), $err);
return;
}
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
//close connection
curl_close($ch);
$elapsed_time = microtime(true) - $start_time;
if (200 != $responseCode) {
// log error
$this->handleError($responseCode, $responseContent);
if (($responseCode >= 500 && $responseCode <= 600) || 429 == $responseCode) {
// If status code is greater than 500 and less than 600, it indicates server error
// Error code 429 indicates rate limited.
// Retry uploading in these cases.
usleep($backoff * 1000);
$backoff *= 2;
} elseif ($responseCode >= 400) {
break;
}
} else {
break; // no error
}
}
return $responseCode;
}
}

View File

@@ -0,0 +1,226 @@
<?php
class Segment_Consumer_Socket extends Segment_QueueConsumer {
protected $type = "Socket";
private $socket_failed;
/**
* Creates a new socket consumer for dispatching async requests immediately
* @param string $secret
* @param array $options
* number "timeout" - the timeout for connecting
* function "error_handler" - function called back on errors.
* boolean "debug" - whether to use debug output, wait for response.
*/
public function __construct($secret, $options = array()) {
if (!isset($options["timeout"])) {
$options["timeout"] = 5;
}
if (!isset($options["host"])) {
$options["host"] = "api.segment.io";
}
parent::__construct($secret, $options);
}
//define getter method for consumer type
public function getConsumer() {
return $this->type;
}
public function flushBatch($batch) {
$socket = $this->createSocket();
if (!$socket) {
return;
}
$payload = $this->payload($batch);
$payload = json_encode($payload);
$body = $this->createBody($this->options["host"], $payload);
if (false === $body) {
return false;
}
return $this->makeRequest($socket, $body);
}
private function createSocket() {
if ($this->socket_failed) {
return false;
}
$protocol = $this->ssl() ? "ssl" : "tcp";
$host = $this->options["host"];
$port = $this->ssl() ? 443 : 80;
$timeout = $this->options["timeout"];
try {
// Open our socket to the API Server.
// Since we're try catch'ing prevent PHP logs.
$socket = @pfsockopen(
$protocol . "://" . $host,
$port,
$errno,
$errstr,
$timeout
);
// If we couldn't open the socket, handle the error.
if (false === $socket) {
$this->handleError($errno, $errstr);
$this->socket_failed = true;
return false;
}
return $socket;
} catch (Exception $e) {
$this->handleError($e->getCode(), $e->getMessage());
$this->socket_failed = true;
return false;
}
}
/**
* Attempt to write the request to the socket, wait for response if debug
* mode is enabled.
* @param stream $socket the handle for the socket
* @param string $req request body
* @param boolean $retry
* @return boolean $success
*/
private function makeRequest($socket, $req, $retry = true) {
$bytes_written = 0;
$bytes_total = strlen($req);
$closed = false;
$success = true;
// Retries with exponential backoff until success
$backoff = 100; // Set initial waiting time to 100ms
while (true) {
// Send request to server
while (!$closed && $bytes_written < $bytes_total) {
try {
// Since we're try catch'ing prevent PHP logs.
$written = @fwrite($socket, substr($req, $bytes_written));
} catch (Exception $e) {
$this->handleError($e->getCode(), $e->getMessage());
$closed = true;
}
if (!isset($written) || !$written) {
$closed = true;
} else {
$bytes_written += $written;
}
}
// Get response for request
$statusCode = 0;
$errorMessage = "";
if (!$closed) {
$res = $this->parseResponse(fread($socket, 2048));
$statusCode = (int)$res["status"];
$errorMessage = $res["message"];
}
fclose($socket);
// If status code is 200, return true
if (200 == $statusCode) {
return true;
}
// If status code is greater than 500 and less than 600, it indicates server error
// Error code 429 indicates rate limited.
// Retry uploading in these cases.
if (($statusCode >= 500 && $statusCode <= 600) || 429 == $statusCode || 0 == $statusCode) {
if ($backoff >= $this->maximum_backoff_duration) {
break;
}
usleep($backoff * 1000);
} elseif ($statusCode >= 400) {
if ($this->debug()) {
$this->handleError($res["status"], $res["message"]);
}
break;
}
// Retry uploading...
$backoff *= 2;
$socket = $this->createSocket();
}
return $success;
}
/**
* Create the body to send as the post request.
* @param string $host
* @param string $content
* @return string body
*/
private function createBody($host, $content) {
$req = "";
$req.= "POST /v1/batch HTTP/1.1\r\n";
$req.= "Host: " . $host . "\r\n";
$req.= "Content-Type: application/json\r\n";
$req.= "Authorization: Basic " . base64_encode($this->secret . ":") . "\r\n";
$req.= "Accept: application/json\r\n";
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
$content_json = json_decode($content, true);
$library = $content_json['batch'][0]['context']['library'];
$libName = $library['name'];
$libVersion = $library['version'];
$req.= "User-Agent: ${libName}/${libVersion}\r\n";
// Compress content if compress_request is true
if ($this->compress_request) {
$content = gzencode($content);
$req.= "Content-Encoding: gzip\r\n";
}
$req.= "Content-length: " . strlen($content) . "\r\n";
$req.= "\r\n";
$req.= $content;
// Verify message size is below than 32KB
if (strlen($req) >= 32 * 1024) {
$msg = "Message size is larger than 32KB";
error_log("[Analytics][" . $this->type . "] " . $msg);
return false;
}
return $req;
}
/**
* Parse our response from the server, check header and body.
* @param string $res
* @return array
* string $status HTTP code, e.g. "200"
* string $message JSON response from the api
*/
private function parseResponse($res) {
$contents = explode("\n", $res);
// Response comes back as HTTP/1.1 200 OK
// Final line contains HTTP response.
$status = explode(" ", $contents[0], 3);
$result = $contents[count($contents) - 1];
return array(
"status" => isset($status[1]) ? $status[1] : null,
"message" => $result
);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,214 @@
<?php
abstract class Segment_QueueConsumer extends Segment_Consumer {
protected $type = "QueueConsumer";
protected $queue;
protected $max_queue_size = 10000;
protected $max_queue_size_bytes = 33554432; //32M
protected $flush_at = 100;
protected $max_batch_size_bytes = 512000; //500kb
protected $max_item_size_bytes = 32000; // 32kb
protected $maximum_backoff_duration = 10000; // Set maximum waiting limit to 10s
protected $host = "";
protected $compress_request = false;
protected $flush_interval_in_mills = 10000; //frequency in milliseconds to send data, default 10
/**
* Store our secret and options as part of this consumer
* @param string $secret
* @param array $options
*/
public function __construct($secret, $options = array()) {
parent::__construct($secret, $options);
if (isset($options["max_queue_size"])) {
$this->max_queue_size = $options["max_queue_size"];
}
if (isset($options["batch_size"])) {
if($options["batch_size"] < 1) {
$msg = "Batch Size must not be less than 1";
error_log("[Analytics][" . $this->type . "] " . $msg);
} else {
$msg = "WARNING: batch_size option to be depricated soon, please use new option flush_at";
error_log("[Analytics][" . $this->type . "] " . $msg);
$this->flush_at = $options["batch_size"];
}
}
if (isset($options["flush_at"])) {
if($options["flush_at"] < 1) {
$msg = "Flush at Size must not be less than 1";
error_log("[Analytics][" . $this->type . "] " . $msg);
} else {
$this->flush_at = $options["flush_at"];
}
}
if (isset($options["host"])) {
$this->host = $options["host"];
}
if (isset($options["compress_request"])) {
$this->compress_request = json_decode($options["compress_request"]);
}
if (isset($options["flush_interval"])) {
if($options["flush_interval"] < 1000) {
$msg = "Flush interval must not be less than 1 second";
error_log("[Analytics][" . $this->type . "] " . $msg);
} else {
$this->flush_interval_in_mills = $options["flush_interval"];
}
}
$this->queue = array();
}
public function __destruct() {
// Flush our queue on destruction
$this->flush();
}
/**
* Tracks a user action
*
* @param array $message
* @return boolean whether the track call succeeded
*/
public function track(array $message) {
return $this->enqueue($message);
}
/**
* Tags traits about the user.
*
* @param array $message
* @return boolean whether the identify call succeeded
*/
public function identify(array $message) {
return $this->enqueue($message);
}
/**
* Tags traits about the group.
*
* @param array $message
* @return boolean whether the group call succeeded
*/
public function group(array $message) {
return $this->enqueue($message);
}
/**
* Tracks a page view.
*
* @param array $message
* @return boolean whether the page call succeeded
*/
public function page(array $message) {
return $this->enqueue($message);
}
/**
* Tracks a screen view.
*
* @param array $message
* @return boolean whether the screen call succeeded
*/
public function screen(array $message) {
return $this->enqueue($message);
}
/**
* Aliases from one user id to another
*
* @param array $message
* @return boolean whether the alias call succeeded
*/
public function alias(array $message) {
return $this->enqueue($message);
}
/**
* Flushes our queue of messages by batching them to the server
*/
public function flush() {
$count = count($this->queue);
$success = true;
while ($count > 0 && $success) {
$batch = array_splice($this->queue, 0, min($this->flush_at, $count));
if (mb_strlen(serialize($batch), '8bit') >= $this->max_batch_size_bytes) {
$msg = "Batch size is larger than 500KB";
error_log("[Analytics][" . $this->type . "] " . $msg);
return false;
}
$success = $this->flushBatch($batch);
$count = count($this->queue);
if($count > 0)
usleep($this->flush_interval_in_mills * 1000);
}
return $success;
}
/**
* Adds an item to our queue.
* @param mixed $item
* @return boolean whether call has succeeded
*/
protected function enqueue($item) {
$count = count($this->queue);
if ($count > $this->max_queue_size) {
return false;
}
if (mb_strlen(serialize((array)$this->queue), '8bit') >= $this->max_queue_size_bytes) {
$msg = "Queue size is larger than 32MB";
error_log("[Analytics][" . $this->type . "] " . $msg);
return false;
}
if (mb_strlen(json_encode($item), '8bit') >= $this->max_item_size_bytes) {
$msg = "Item size is larger than 32KB";
error_log("[Analytics][" . $this->type . "] " . $msg);
return false;
}
$count = array_push($this->queue, $item);
if ($count >= $this->flush_at) {
return $this->flush(); // return ->flush() result: true on success
}
return true;
}
/**
* Given a batch of messages the method returns
* a valid payload.
*
* @param {Array} $batch
* @return {Array}
*/
protected function payload($batch){
return array(
"batch" => $batch,
"sentAt" => date("c"),
);
}
}

View File

@@ -0,0 +1,3 @@
<?php
global $SEGMENT_VERSION;
$SEGMENT_VERSION = "1.8.0";

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<description>The coding standard for analytics-php project.</description>
<file>./lib/</file>
<file>./test/</file>
<arg name="tab-width" value="2"/>
<rule ref="PSR2">
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
<exclude name="PSR1.Files.SideEffects"/>
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/>
<exclude name="PSR1.Classes.ClassDeclaration.MultipleClasses"/>
<!-- Enable opening braces can be placed at the same line of class definition -->
<exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine"/>
<!-- Disable camel caps classname -->
<exclude name="Squiz.Classes.ValidClassName.NotCamelCaps"/>
<!-- Enable opening braces can be placed at the same line of function definition -->
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine"/>
</rule>
<!-- Set indent size to 2 -->
<rule ref="Generic.WhiteSpace.ScopeIndent">
<properties>
<property name="indent" value="2"/>
<property name="tabIndent" value="false"/>
</properties>
</rule>
<!-- Set indent size for multi-line function -->
<rule ref="PSR2.Methods.FunctionCallSignature">
<properties>
<property name="indent" value="2"/>
</properties>
</rule>
<rule ref="PSR2.Methods.FunctionCallSignature.ContentAfterOpenBracket">
<severity phpcs-only="true">0</severity>
</rule>
</ruleset>

View File

@@ -0,0 +1,36 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.0/phpunit.xsd"
backupGlobals="true"
backupStaticAttributes="false"
cacheTokens="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
mapTestClassNameToCoveredClassName="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
stopOnRisky="false"
timeoutForSmallTests="1"
timeoutForMediumTests="10"
timeoutForLargeTests="60"
verbose="false">
<testsuites>
<testsuite name="analytics-php">
<directory>test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./lib</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="clover.xml"/>
</logging>
</phpunit>

View File

@@ -0,0 +1,119 @@
<?php
/**
* require client
*/
require_once(__DIR__ . "/lib/Segment.php");
/**
* Args
*/
$args = parse($argv);
/**
* Make sure both are set
*/
if (!isset($args["secret"])) die("--secret must be given");
if (!isset($args["file"])) die("--file must be given");
$file = $args["file"];
if ($file[0] != '/') $file = __DIR__ . "/" . $file;
/**
* Rename the file so we don't write the same calls
* multiple times
*/
$dir = dirname($file);
$old = $file;
$file = $dir . '/analytics-' . rand() . '.log';
if(!file_exists($old)) {
print("file: $old does not exist");
exit(0);
}
if (!rename($old, $file)) {
print("error renaming from $old to $file\n");
exit(1);
}
/**
* File contents.
*/
$contents = file_get_contents($file);
$lines = explode("\n", $contents);
/**
* Initialize the client.
*/
Segment::init($args["secret"], array(
"debug" => true,
"error_handler" => function($code, $msg){
print("$code: $msg\n");
exit(1);
}
));
/**
* Payloads
*/
$total = 0;
$successful = 0;
foreach ($lines as $line) {
if (!trim($line)) continue;
$total++;
$payload = json_decode($line, true);
$dt = new DateTime($payload["timestamp"]);
$ts = floatval($dt->getTimestamp() . "." . $dt->format("u"));
$payload["timestamp"] = date("c", (int) $ts);
$type = $payload["type"];
$currentBatch[] = $payload;
// flush before batch gets too big
if (mb_strlen((json_encode(array('batch' => $currentBatch, 'sentAt' => date("c")))), '8bit') >= 512000) {
$libCurlResponse = Segment::flush();
if ($libCurlResponse) {
$successful += count($currentBatch) - 1;
} else {
// todo: maybe write batch to analytics-error.log for more controlled errorhandling
}
$currentBatch = array();
}
$payload["timestamp"] = $ts;
call_user_func_array(array("Segment", $type), array($payload));
}
$libCurlResponse = Segment::flush();
if ($libCurlResponse) {
$successful += $total - $successful;
}
unlink($file);
/**
* Sent
*/
print("sent $successful from $total requests successfully");
exit(0);
/**
* Parse arguments
*/
function parse($argv){
$ret = array();
for ($i = 0; $i < count($argv); ++$i) {
$arg = $argv[$i];
if ('--' != substr($arg, 0, 2)) continue;
$ret[substr($arg, 2, strlen($arg))] = trim($argv[++$i]);
}
return $ret;
}

View File

@@ -0,0 +1,249 @@
<?php
require_once __DIR__ . "/../lib/Segment.php";
class AnalyticsTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
date_default_timezone_set("UTC");
Segment::init("oq0vdlg7yi", array("debug" => true));
}
public function testTrack()
{
$this->assertTrue(Segment::track(array(
"userId" => "john",
"event" => "Module PHP Event",
)));
}
public function testGroup()
{
$this->assertTrue(Segment::group(array(
"groupId" => "group-id",
"userId" => "user-id",
"traits" => array(
"plan" => "startup",
),
)));
}
public function testGroupAnonymous()
{
$this->assertTrue(Segment::group(array(
"groupId" => "group-id",
"anonymousId" => "anonymous-id",
"traits" => array(
"plan" => "startup",
),
)));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Segment::group() requires userId or anonymousId
*/
public function testGroupNoUser()
{
Segment::group(array(
"groupId" => "group-id",
"traits" => array(
"plan" => "startup",
),
));
}
public function testMicrotime()
{
$this->assertTrue(Segment::page(array(
"anonymousId" => "anonymous-id",
"name" => "analytics-php-microtime",
"category" => "docs",
"timestamp" => microtime(true),
"properties" => array(
"path" => "/docs/libraries/php/",
"url" => "https://segment.io/docs/libraries/php/",
),
)));
}
public function testPage()
{
$this->assertTrue(Segment::page(array(
"anonymousId" => "anonymous-id",
"name" => "analytics-php",
"category" => "docs",
"properties" => array(
"path" => "/docs/libraries/php/",
"url" => "https://segment.io/docs/libraries/php/",
),
)));
}
public function testBasicPage()
{
$this->assertTrue(Segment::page(array(
"anonymousId" => "anonymous-id",
)));
}
public function testScreen()
{
$this->assertTrue(Segment::screen(array(
"anonymousId" => "anonymous-id",
"name" => "2048",
"category" => "game built with php :)",
"properties" => array(
"points" => 300
),
)));
}
public function testBasicScreen()
{
$this->assertTrue(Segment::screen(array(
"anonymousId" => "anonymous-id"
)));
}
public function testIdentify()
{
$this->assertTrue(Segment::identify(array(
"userId" => "doe",
"traits" => array(
"loves_php" => false,
"birthday" => time(),
),
)));
}
public function testEmptyTraits()
{
$this->assertTrue(Segment::identify(array(
"userId" => "empty-traits",
)));
$this->assertTrue(Segment::group(array(
"userId" => "empty-traits",
"groupId" => "empty-traits",
)));
}
public function testEmptyArrayTraits()
{
$this->assertTrue(Segment::identify(array(
"userId" => "empty-traits",
"traits" => array(),
)));
$this->assertTrue(Segment::group(array(
"userId" => "empty-traits",
"groupId" => "empty-traits",
"traits" => array(),
)));
}
public function testEmptyProperties()
{
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "empty-properties",
)));
$this->assertTrue(Segment::page(array(
"category" => "empty-properties",
"name" => "empty-properties",
"userId" => "user-id",
)));
}
public function testEmptyArrayProperties()
{
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "empty-properties",
"properties" => array(),
)));
$this->assertTrue(Segment::page(array(
"category" => "empty-properties",
"name" => "empty-properties",
"userId" => "user-id",
"properties" => array(),
)));
}
public function testAlias()
{
$this->assertTrue(Segment::alias(array(
"previousId" => "previous-id",
"userId" => "user-id",
)));
}
public function testContextEmpty()
{
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "Context Test",
"context" => array(),
)));
}
public function testContextCustom()
{
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "Context Test",
"context" => array(
"active" => false,
),
)));
}
public function testTimestamps()
{
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "integer-timestamp",
"timestamp" => (int) mktime(0, 0, 0, date('n'), 1, date('Y')),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "string-integer-timestamp",
"timestamp" => (string) mktime(0, 0, 0, date('n'), 1, date('Y')),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "iso8630-timestamp",
"timestamp" => date(DATE_ATOM, mktime(0, 0, 0, date('n'), 1, date('Y'))),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "iso8601-timestamp",
"timestamp" => date(DATE_ATOM, mktime(0, 0, 0, date('n'), 1, date('Y'))),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "strtotime-timestamp",
"timestamp" => strtotime('1 week ago'),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "microtime-timestamp",
"timestamp" => microtime(true),
)));
$this->assertTrue(Segment::track(array(
"userId" => "user-id",
"event" => "invalid-float-timestamp",
"timestamp" => ((string) mktime(0, 0, 0, date('n'), 1, date('Y'))) . '.',
)));
}
}

View File

@@ -0,0 +1,31 @@
<?php
require_once __DIR__ . '/../lib/Segment/Client.php';
class ClientTest extends PHPUnit_Framework_TestCase
{
/** @test */
public function it_uses_the_lib_curl_consumer_as_default()
{
$client = new Segment_Client('foobar', []);
$this->assertInstanceOf(Segment_Consumer_LibCurl::class, $client->getConsumer());
}
/** @test */
public function can_provide_the_consumer_configuration_as_string()
{
$client = new Segment_Client('foobar', [
'consumer' => 'fork_curl',
]);
$this->assertInstanceOf(Segment_Consumer_ForkCurl::class, $client->getConsumer());
}
/** @test */
public function can_provide_a_class_namespace_as_consumer_configuration()
{
$client = new Segment_Client('foobar', [
'consumer' => Segment_Consumer_ForkCurl::class,
]);
$this->assertInstanceOf(Segment_Consumer_ForkCurl::class, $client->getConsumer());
}
}

View File

@@ -0,0 +1,166 @@
<?php
require_once __DIR__ . "/../lib/Segment/Client.php";
class ConsumerFileTest extends PHPUnit_Framework_TestCase
{
private $client;
private $filename = "/tmp/analytics.log";
public function setUp()
{
date_default_timezone_set("UTC");
if (file_exists($this->filename())) {
unlink($this->filename());
}
$this->client = new Segment_Client(
"oq0vdlg7yi",
array(
"consumer" => "file",
"filename" => $this->filename,
)
);
}
public function tearDown()
{
if (file_exists($this->filename)) {
unlink($this->filename);
}
}
public function testTrack()
{
$this->assertTrue($this->client->track(array(
"userId" => "some-user",
"event" => "File PHP Event - Microtime",
"timestamp" => microtime(true),
)));
$this->checkWritten("track");
}
public function testIdentify()
{
$this->assertTrue($this->client->identify(array(
"userId" => "Calvin",
"traits" => array(
"loves_php" => false,
"type" => "analytics.log",
"birthday" => time(),
),
)));
$this->checkWritten("identify");
}
public function testGroup()
{
$this->assertTrue($this->client->group(array(
"userId" => "user-id",
"groupId" => "group-id",
"traits" => array(
"type" => "consumer analytics.log test",
),
)));
}
public function testPage()
{
$this->assertTrue($this->client->page(array(
"userId" => "user-id",
"name" => "analytics-php",
"category" => "analytics.log",
"properties" => array(
"url" => "https://a.url/",
),
)));
}
public function testScreen()
{
$this->assertTrue($this->client->screen(array(
"userId" => "userId",
"name" => "grand theft auto",
"category" => "analytics.log",
"properties" => array(),
)));
}
public function testAlias()
{
$this->assertTrue($this->client->alias(array(
"previousId" => "previous-id",
"userId" => "user-id",
)));
$this->checkWritten("alias");
}
public function testSend()
{
for ($i = 0; $i < 200; ++$i) {
$this->client->track(array(
"userId" => "userId",
"event" => "event",
));
}
exec("php --define date.timezone=UTC send.php --secret oq0vdlg7yi --file /tmp/analytics.log", $output);
$this->assertSame("sent 200 from 200 requests successfully", trim($output[0]));
$this->assertFileNotExists($this->filename());
}
public function testProductionProblems()
{
// Open to a place where we should not have write access.
$client = new Segment_Client(
"oq0vdlg7yi",
array(
"consumer" => "file",
"filename" => "/dev/xxxxxxx",
)
);
$tracked = $client->track(array("userId" => "some-user", "event" => "my event"));
$this->assertFalse($tracked);
}
public function testFileSecurityCustom() {
$client = new Segment_Client(
"testsecret",
array(
"consumer" => "file",
"filename" => $this->filename,
"filepermissions" => 0700
)
);
$tracked = $client->track(array("userId" => "some_user", "event" => "File PHP Event"));
$this->assertEquals(0700, (fileperms($this->filename) & 0777));
}
public function testFileSecurityDefaults() {
$client = new Segment_Client(
"testsecret",
array(
"consumer" => "file",
"filename" => $this->filename
)
);
$tracked = $client->track(array("userId" => "some_user", "event" => "File PHP Event"));
$this->assertEquals(0777, (fileperms($this->filename) & 0777));
}
public function checkWritten($type)
{
exec("wc -l " . $this->filename, $output);
$out = trim($output[0]);
$this->assertSame($out, "1 " . $this->filename);
$str = file_get_contents($this->filename);
$json = json_decode(trim($str));
$this->assertSame($type, $json->type);
unlink($this->filename);
}
public function filename()
{
return '/tmp/analytics.log';
}
}

View File

@@ -0,0 +1,99 @@
<?php
require_once __DIR__ . "/../lib/Segment/Client.php";
class ConsumerForkCurlTest extends PHPUnit_Framework_TestCase
{
private $client;
public function setUp()
{
date_default_timezone_set("UTC");
$this->client = new Segment_Client(
"OnMMoZ6YVozrgSBeZ9FpkC0ixH0ycYZn",
array(
"consumer" => "fork_curl",
"debug" => true,
)
);
}
public function testTrack()
{
$this->assertTrue($this->client->track(array(
"userId" => "some-user",
"event" => "PHP Fork Curl'd\" Event",
)));
}
public function testIdentify()
{
$this->assertTrue($this->client->identify(array(
"userId" => "user-id",
"traits" => array(
"loves_php" => false,
"type" => "consumer fork-curl test",
"birthday" => time(),
),
)));
}
public function testGroup()
{
$this->assertTrue($this->client->group(array(
"userId" => "user-id",
"groupId" => "group-id",
"traits" => array(
"type" => "consumer fork-curl test",
),
)));
}
public function testPage()
{
$this->assertTrue($this->client->page(array(
"userId" => "userId",
"name" => "analytics-php",
"category" => "fork-curl",
"properties" => array(
"url" => "https://a.url/",
),
)));
}
public function testScreen()
{
$this->assertTrue($this->client->page(array(
"anonymousId" => "anonymous-id",
"name" => "grand theft auto",
"category" => "fork-curl",
"properties" => array(),
)));
}
public function testAlias()
{
$this->assertTrue($this->client->alias(array(
"previousId" => "previous-id",
"userId" => "user-id",
)));
}
public function testRequestCompression() {
$options = array(
"compress_request" => true,
"consumer" => "fork_curl",
"debug" => true,
);
// Create client and send Track message
$client = new Segment_Client("OnMMoZ6YVozrgSBeZ9FpkC0ixH0ycYZn", $options);
$result = $client->track(array(
"userId" => "some-user",
"event" => "PHP Fork Curl'd\" Event with compression",
));
$client->__destruct();
$this->assertTrue($result);
}
}

View File

@@ -0,0 +1,125 @@
<?php
require_once __DIR__ . "/../lib/Segment/Client.php";
class ConsumerLibCurlTest extends PHPUnit_Framework_TestCase
{
private $client;
public function setUp()
{
date_default_timezone_set("UTC");
$this->client = new Segment_Client(
"oq0vdlg7yi",
array(
"consumer" => "lib_curl",
"debug" => true,
)
);
}
public function testTrack()
{
$this->assertTrue($this->client->track(array(
"userId" => "lib-curl-track",
"event" => "PHP Lib Curl'd\" Event",
)));
}
public function testIdentify()
{
$this->assertTrue($this->client->identify(array(
"userId" => "lib-curl-identify",
"traits" => array(
"loves_php" => false,
"type" => "consumer lib-curl test",
"birthday" => time(),
),
)));
}
public function testGroup()
{
$this->assertTrue($this->client->group(array(
"userId" => "lib-curl-group",
"groupId" => "group-id",
"traits" => array(
"type" => "consumer lib-curl test",
),
)));
}
public function testPage()
{
$this->assertTrue($this->client->page(array(
"userId" => "lib-curl-page",
"name" => "analytics-php",
"category" => "fork-curl",
"properties" => array(
"url" => "https://a.url/",
),
)));
}
public function testScreen()
{
$this->assertTrue($this->client->page(array(
"anonymousId" => "lib-curl-screen",
"name" => "grand theft auto",
"category" => "fork-curl",
"properties" => array(),
)));
}
public function testAlias()
{
$this->assertTrue($this->client->alias(array(
"previousId" => "lib-curl-alias",
"userId" => "user-id",
)));
}
public function testRequestCompression() {
$options = array(
"compress_request" => true,
"consumer" => "lib_curl",
"error_handler" => function ($errno, $errmsg) {
throw new \RuntimeException($errmsg, $errno);
},
);
$client = new Segment_Client("x", $options);
# Should error out with debug on.
$client->track(array("user_id" => "some-user", "event" => "Socket PHP Event"));
$client->__destruct();
}
public function testLargeMessageSizeError()
{
$options = array(
"debug" => true,
"consumer" => "lib_curl",
);
$client = new Segment_Client("testlargesize", $options);
$big_property = "";
for ($i = 0; $i < 32 * 1024; ++$i) {
$big_property .= "a";
}
$this->assertFalse(
$client->track(
array(
"userId" => "some-user",
"event" => "Super Large PHP Event",
"properties" => array("big_property" => $big_property),
)
) && $client->flush()
);
$client->__destruct();
}
}

View File

@@ -0,0 +1,220 @@
<?php
require_once __DIR__ . "/../lib/Segment/Client.php";
class ConsumerSocketTest extends PHPUnit_Framework_TestCase
{
private $client;
public function setUp()
{
date_default_timezone_set("UTC");
$this->client = new Segment_Client(
"oq0vdlg7yi",
array("consumer" => "socket")
);
}
public function testTrack()
{
$this->assertTrue($this->client->track(array(
"userId" => "some-user",
"event" => "Socket PHP Event",
)));
}
public function testIdentify()
{
$this->assertTrue($this->client->identify(array(
"userId" => "Calvin",
"traits" => array(
"loves_php" => false,
"birthday" => time(),
),
)));
}
public function testGroup()
{
$this->assertTrue($this->client->group(array(
"userId" => "user-id",
"groupId" => "group-id",
"traits" => array(
"type" => "consumer socket test",
),
)));
}
public function testPage()
{
$this->assertTrue($this->client->page(array(
"userId" => "user-id",
"name" => "analytics-php",
"category" => "socket",
"properties" => array(
"url" => "https://a.url/",
),
)));
}
public function testScreen()
{
$this->assertTrue($this->client->screen(array(
"anonymousId" => "anonymousId",
"name" => "grand theft auto",
"category" => "socket",
"properties" => array(),
)));
}
public function testAlias()
{
$this->assertTrue($this->client->alias(array(
"previousId" => "some-socket",
"userId" => "new-socket",
)));
}
public function testShortTimeout()
{
$client = new Segment_Client(
"oq0vdlg7yi",
array(
"timeout" => 0.01,
"consumer" => "socket",
)
);
$this->assertTrue($client->track(array(
"userId" => "some-user",
"event" => "Socket PHP Event",
)));
$this->assertTrue($client->identify(array(
"userId" => "some-user",
"traits" => array(),
)));
$client->__destruct();
}
public function testProductionProblems()
{
$client = new Segment_Client("x",
array(
"consumer" => "socket",
"error_handler" => function () {
throw new Exception("Was called");
},
)
);
// Shouldn't error out without debug on.
$client->track(array("user_id" => "some-user", "event" => "Production Problems"));
$client->__destruct();
}
public function testDebugProblems()
{
$options = array(
"debug" => true,
"consumer" => "socket",
"error_handler" => function ($errno, $errmsg) {
if (400 != $errno) {
throw new Exception("Response is not 400");
}
},
);
$client = new Segment_Client("x", $options);
// Should error out with debug on.
$client->track(array("user_id" => "some-user", "event" => "Socket PHP Event"));
$client->__destruct();
}
public function testLargeMessage()
{
$options = array(
"debug" => true,
"consumer" => "socket",
);
$client = new Segment_Client("testsecret", $options);
$big_property = "";
for ($i = 0; $i < 10000; ++$i) {
$big_property .= "a";
}
$this->assertTrue($client->track(array(
"userId" => "some-user",
"event" => "Super Large PHP Event",
"properties" => array("big_property" => $big_property),
)));
$client->__destruct();
}
public function testLargeMessageSizeError()
{
$options = array(
"debug" => true,
"consumer" => "socket",
);
$client = new Segment_Client("testlargesize", $options);
$big_property = "";
for ($i = 0; $i < 32 * 1024; ++$i) {
$big_property .= "a";
}
$this->assertFalse(
$client->track(
array(
"userId" => "some-user",
"event" => "Super Large PHP Event",
"properties" => array("big_property" => $big_property),
)
) && $client->flush()
);
$client->__destruct();
}
/**
* @expectedException \RuntimeException
*/
public function testConnectionError()
{
$client = new Segment_Client("x", array(
"consumer" => "socket",
"host" => "api.segment.ioooooo",
"error_handler" => function ($errno, $errmsg) {
throw new \RuntimeException($errmsg, $errno);
},
));
$client->track(array("user_id" => "some-user", "event" => "Event"));
$client->__destruct();
}
public function testRequestCompression() {
$options = array(
"compress_request" => true,
"consumer" => "socket",
"error_handler" => function ($errno, $errmsg) {
throw new \RuntimeException($errmsg, $errno);
},
);
$client = new Segment_Client("x", $options);
# Should error out with debug on.
$client->track(array("user_id" => "some-user", "event" => "Socket PHP Event"));
$client->__destruct();
}
}

View File

@@ -0,0 +1,6 @@
{"userId":"some-user","event":"File PHP Event","context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"f8c9cda1-f21b-4d40-8198-085eaa99dedb","type":"track"}
{"userId":"Calvin","traits":{"loves_php":false,"type":"analytics.log","birthday":1399997957},"context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"fe37073d-40fa-41e1-b826-8659fe74a199","type":"identify"}
{"userId":"user-id","groupId":"group-id","traits":{"type":"consumer analytics.log test"},"context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"8e6bb92a-7347-424a-94d6-a4234617b070","type":"group"}
{"userId":"user-id","name":"analytics-php","category":"analytics.log","properties":{"url":"https:\/\/a.url\/"},"context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"b3db73cd-337b-4b50-8967-f653f9183e02","type":"page"}
{"userId":"userId","name":"grand theft auto","category":"analytics.log","properties":[],"context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"2527e1a0-e631-47b1-872e-f575f0722547","type":"page"}
{"previousId":"previous-id","userId":"user-id","context":{"library":{"name":"analytics-php","version":"1.0.0"}},"timestamp":"2014-05-13T16:19:17+00:00","messageId":"5f883f43-c15c-49c6-b4e6-59d5cb6d657b","type":"alias"}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;