Current File : //var/www/prestashop/modules/psxdesign/src/Tracker/SegmentTracker.php |
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
declare(strict_types=1);
namespace PrestaShop\Module\PsxDesign\Tracker;
if (!defined('_PS_VERSION_')) {
exit;
}
use Context;
use Exception;
use Language;
use PrestaShop\Module\PsxDesign\Account\Provider\PsAccountDataProvider;
use PrestaShop\Module\PsxDesign\Account\Provider\TokenDecoder;
use PrestaShop\Module\PsxDesign\Exception\PsxDesignAccountsException;
use PrestaShop\Module\PsxDesign\Exception\PsxDesignApiException;
use PrestaShop\Module\PsxDesign\Exception\PsxDesignTokenDecoderException;
use Ramsey\Uuid\Uuid;
use Segment;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ServerBag;
class SegmentTracker implements TrackerInterface
{
private const COOKIE_ANONYMOUS_ID = 'psxdesign_anonymous_id';
/**
* @var PsAccountDataProvider
*/
private $accountDataProvider;
/**
* @var Context
*/
private $context;
/**
* @var Language
*/
private $language;
/**
* @var TokenDecoder
*/
private $decoder;
/**
* @var string
*/
private $segmentKey;
/**
* @var string
*/
private $moduleVersion;
/**
* @var string
*/
private $moduleName;
/**
* @var string
*/
private $currentTheme;
/**
* @var string
*/
private $event = '';
/**
* @var string|null
*/
private static $anonymousId;
/**
* @var array<string, mixed>
*/
private $properties = [];
public function __construct(
PsAccountDataProvider $accountDataProvider,
Context $context,
TokenDecoder $decoder,
string $segmentKey,
string $moduleVersion,
string $moduleName,
string $currentTheme
) {
$this->accountDataProvider = $accountDataProvider;
$this->context = $context;
$this->language = $this->context->language;
$this->segmentKey = $segmentKey;
$this->decoder = $decoder;
$this->moduleVersion = $moduleVersion;
$this->moduleName = $moduleName;
$this->currentTheme = $currentTheme;
$this->initSegment();
$this->initAnonymousId();
}
/**
* Track event on segment
*
* @param string $event
* @param array $properties
* @param ServerBag|null $serverBag
*
* @return void
*/
public function track(string $event, array $properties = [], ServerBag $serverBag = null): void
{
$this->setProperties($properties);
$this->setEvent($event);
if (!$serverBag) {
$serverBag = $this->getServerBag();
}
$message = $this->buildMessage($this->getUserId(), $serverBag);
$this->segmentTrack($message);
}
/**
* @param array $message
*
* @return void
*/
public function segmentTrack(array $message): void
{
try {
Segment::track($message);
Segment::flush();
} catch (Exception $e) {
throw new PsxDesignApiException('Failed to send data to segment', PsxDesignApiException::FAILED_TO_SEND_DATA_TO_SEGMENT);
}
}
/**
* Init segment client with the api key
*/
private function initSegment(): void
{
Segment::init($this->segmentKey);
}
/**
* @param string|null $userId
* @param ServerBag $serverBag
*
* @return array
*/
public function buildMessage(?string $userId, ServerBag $serverBag): array
{
$userAgent = $serverBag->get('HTTP_USER_AGENT');
$referer = $serverBag->get('HTTP_REFERER');
$httpHost = $serverBag->get('HTTP_HOST');
$requestUri = $serverBag->get('REQUEST_URI');
$url = ($serverBag->get('HTTPS') !== null && $serverBag->get('HTTPS') === 'on' ? 'https' : 'http') . "://$httpHost$requestUri";
return [
'userId' => $userId,
'anonymousId' => $this->getAnonymousId(),
'event' => $this->getEvent(),
'channel' => 'browser',
'context' => [
'userAgent' => $userAgent,
'locale' => $this->language->iso_code,
'page' => [
'referrer' => $referer,
'url' => $url,
],
],
'properties' => array_merge([
'module' => $this->moduleName,
'module_version' => $this->moduleVersion,
], $this->getProperties()),
];
}
/**
* @return string
*/
public function getEvent(): string
{
return $this->event;
}
/**
* @param string $event
*/
public function setEvent(string $event): void
{
$this->event = $event;
}
/**
* @return array<string, mixed>
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* @param array<string, mixed> $properties
*/
public function setProperties(array $properties): void
{
if (!isset($properties['theme_name'])) {
$properties['theme_name'] = $this->currentTheme;
}
$this->properties = $properties;
}
/**
* @return string
*/
public function getAnonymousId(): string
{
$this->initAnonymousId();
return self::$anonymousId;
}
/**
* Returns server bag if not provided
*
* @return ServerBag
*/
private function getServerBag(): ServerBag
{
return Request::createFromGlobals()->server;
}
/**
* Returns Cookies
*
* @return ParameterBag
*/
private function getCookies(): ParameterBag
{
return Request::createFromGlobals()->cookies;
}
/**
* Initialize anonymous id which is generated in front end.
*
* @return void
*/
private function initAnonymousId(): void
{
try {
$allCookies = $this->getCookies();
$cookie = $allCookies->get(self::COOKIE_ANONYMOUS_ID);
} catch (Exception $e) {
$cookie = $this->context->cookie->{self::COOKIE_ANONYMOUS_ID} ?? null;
}
if (!isset(self::$anonymousId)) {
if ($cookie) {
self::$anonymousId = $cookie;
} else {
$this->generateAnonymousId();
}
}
}
/**
* Cookie generation in front end is asynchronously, so could be that cookie still does not exist
* we need to wait and check again in case cookie still do not exist we generate new one.
*
* @return void
*/
private function generateAnonymousId(): void
{
sleep(3);
$cookie = $this->context->cookie->{self::COOKIE_ANONYMOUS_ID} ?? null;
if (!$cookie) {
self::$anonymousId = Uuid::uuid4()->toString();
/* we ignore next line cause it's not an error, we just follow the PS dev doc => https://devdocs.prestashop-project.org/8/development/components/cookie/ */
/* @phpstan-ignore-next-line */
$this->context->cookie->{self::COOKIE_ANONYMOUS_ID} = self::$anonymousId;
} else {
self::$anonymousId = $cookie;
}
}
/**
* @return string|null
*/
private function getUserId(): ?string
{
$userId = null;
try {
$accessToken = $this->accountDataProvider->getOrRefreshAccessToken();
if ($accessToken) {
$userId = $this->decoder->decode($accessToken)->getUserId();
}
} catch (PsxDesignTokenDecoderException|PsxDesignAccountsException $e) {
return null;
}
return $userId;
}
}