Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 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,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,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,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,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,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;
}