Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SEGMENT_PREPROD_KEY=S3Vk6KAg6wilMfSKfr4JKNGgPYD7L97W
SEGMENT_PROD_KEY=Xn1VxRGYASE3pFBODAmSOs3qkXQ6mKW5
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ PrestaShop: `9.0.0` or later.
- front office AJAX endpoints for guest initialization and address form refresh,
- back office configuration for enabling or disabling one-page checkout,
- checkout layout configuration assets and templates shipped by the module,
- checkout runtime flag exposure for the order page.
- checkout runtime flag exposure for the order page,
- optional Segment PHP client bootstrap (`segmentio/analytics-php`) on module configuration pages (see [`docs/SEGMENT.md`](./docs/SEGMENT.md)).

## Code map

Expand All @@ -41,11 +42,12 @@ Before opening a PR:

## Local development

### PHP autoload
### PHP dependencies and autoload

From the repository root:
From the repository root, install Composer dependencies (required for `segmentio/analytics-php` and the module autoload):

```bash
composer install -d
composer dump-autoload -d
```

Expand Down Expand Up @@ -101,6 +103,7 @@ For end-to-end checks, use the dedicated runbook:

- implementation rules: [`docs/RULES.md`](./docs/RULES.md)
- architectural decisions: [`docs/DECISIONS.md`](./docs/DECISIONS.md)
- Segment (configuration & usage): [`docs/SEGMENT.md`](./docs/SEGMENT.md)
- contributors: [`CONTRIBUTORS.md`](./CONTRIBUTORS.md)

## License
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
}
],
"require": {
"php": ">=8.1"
"php": ">=8.1",
"segmentio/analytics-php": "^3.0"
},
"require-dev": {
"prestashop/php-dev-tools": "^4.3"
Expand Down
75 changes: 70 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions docs/SEGMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Segment dans `ps_onepagecheckout`

Ce document décrit la **configuration** et l’**intégration technique** de Segment via le **SDK PHP** (`segmentio/analytics-php`), sur le même principe que le module [PrestaShop autoupgrade](https://github.com/PrestaShop/autoupgrade) (classe [`Analytics`](https://github.com/PrestaShop/autoupgrade/blob/dev/classes/Analytics.php)).

**Comportement actuel** : initialisation de `Segment::init()` lorsque les conditions sont réunies. **Aucun** appel `track` / `flush` n’est encore envoyé depuis le module — cela fera l’objet de tickets ultérieurs.

**Résumé** : write key lue depuis un fichier/variables d’environnement (`SEGMENT_PREPROD_KEY`, `SEGMENT_PROD_KEY`) → `Segment::init()` sur les requêtes BO qui passent par le hook concerné, **dès que le module est activé**.

La classe PHP s’appelle **`Analytics`** (nom volontairement **générique**) pour limiter les renommages si le fournisseur change.

**Front office** : aucun chargement du SDK navigateur (`analytics.js`) ; l’ancien bundle `opc-segment-init` a été retiré.

## Dépendance Composer

- `segmentio/analytics-php` (voir `composer.json`). Après clone : `composer install` à la racine du module pour disposer du vendeur et de l’autoload.

## Clés et constantes

| Source | Identifiant | Rôle | Défaut |
|--------|-------------|------|--------|
| Environnement | `SEGMENT_PREPROD_KEY` | Write key de la **source PHP** Segment (préprod) — **seule source de vérité** (pas de `configuration`). | `''` |
| Environnement | `SEGMENT_PROD_KEY` | Write key de la **source PHP** Segment (prod) — **seule source de vérité** (pas de `configuration`). | `''` |

### Règle de sélection de la clé

- Si `_PS_MODE_DEV_` est `false` → utilisation de `SEGMENT_PROD_KEY`
- Si `_PS_MODE_DEV_` est `true` → utilisation de `SEGMENT_PREPROD_KEY`

## Architecture PHP

| Fichier | Rôle |
|---------|------|
| `src/Analytics/Analytics.php` | `bootstrap(bool $moduleSegmentEnabled)` : vérifie activation module, clé non vide, puis `Segment\Segment::init($writeKey)`. Garde statique pour n’initialiser qu’une fois par requête. |

`ps_onepagecheckout.php` appelle `Analytics::bootstrap(true)` depuis `bootstrapPhpSegmentClient()` (Segment activé tant que le module est activé), invoqué dans `hookActionAdminControllerSetMedia` lorsque `isBackOfficeConfigurationContext()` est vrai.

### Différences notables avec l’ancienne version (navigateur)

- Plus de `window.psopc_segment` ni de `opc-segment-init.bundle.js`.
- Les clés **PHP** (`SEGMENT_PREPROD_KEY` / `SEGMENT_PROD_KEY`) ne sont pas la même source Segment que l’ancienne clé **JavaScript** ; à configurer dans l’espace Segment (source PHP).

## Où cela s’exécute

Hook **`actionAdminControllerSetMedia`**, uniquement sur la **configuration du module** en BO (`AdminPsOnePageCheckout`, `configure=ps_onepagecheckout`, ou `AdminPsOnePageCheckoutController`), comme auparavant pour le chargement JS — sauf qu’il ne s’agit plus que d’initialiser le client PHP.

## Configuration Back Office

Segment est considéré **activé** tant que le module est activé (et si la write key est non vide).

## Événements (`track`) — à venir

Les appels `Segment::track()` / `flush()` seront ajoutés dans de futurs tickets, une fois les événements métier définis.

## Cycle de vie du module

Pas de clé de configuration dédiée à Segment : l’activation suit l’activation du module.

## Fichiers utiles

- `src/Analytics/Analytics.php`
- `ps_onepagecheckout.php` — `hookActionAdminControllerSetMedia`, `bootstrapPhpSegmentClient()`
- `composer.json` — dépendance `segmentio/analytics-php`

## Limites

- La write key est fournie par environnement (local / préprod / prod) : à configurer côté plateforme (variable d’environnement / secret).
- `Segment::init` est appelé dans le contexte des requêtes qui déclenchent le hook (typiquement pages de config du module en BO).
34 changes: 34 additions & 0 deletions ps_onepagecheckout.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require __DIR__ . '/vendor/autoload.php';
}

use PrestaShop\Module\PsOnePageCheckout\Analytics\Analytics;
use PrestaShop\Module\PsOnePageCheckout\Checkout\OnePageCheckoutAvailability;
use PrestaShop\Module\PsOnePageCheckout\Checkout\OnePageCheckoutProcessBuilder;
use PrestaShop\Module\PsOnePageCheckout\Form\BackOfficeConfigurationForm;
Expand Down Expand Up @@ -66,6 +67,7 @@ public function install()
&& $this->initializeCheckoutProcessProviderConfiguration()
&& $this->registerHook('actionCheckoutBuildProcess')
&& $this->registerHook('actionFrontControllerSetMedia')
&& $this->registerHook('actionAdminControllerSetMedia')
&& $this->registerHook('actionFrontControllerSetVariables');
}

Expand Down Expand Up @@ -155,6 +157,15 @@ public function hookActionFrontControllerSetMedia(): void
$this->registerOpcJavascriptAssets();
}

public function hookActionAdminControllerSetMedia(): void
{
if (!isset($this->context->controller) || !$this->isBackOfficeConfigurationContext()) {
return;
}

$this->bootstrapPhpSegmentClient();
}

public function hookActionFrontControllerSetVariables(array $params): void
{
if (!isset($this->context->controller) || $this->context->controller->php_self !== 'order') {
Expand Down Expand Up @@ -331,4 +342,27 @@ protected function uninstallOnePageCheckoutConfiguration(): bool
{
return Configuration::deleteByName(self::CONFIG_ONE_PAGE_CHECKOUT_ENABLED);
}

protected function bootstrapPhpSegmentClient(): void
{
// Segment PHP bootstrap is enabled whenever the module is enabled.
// Bootstrap is disabled only when the write key is empty.
Analytics::bootstrap(true);
}

protected function isBackOfficeConfigurationContext(): bool
{
$controllerName = (string) Tools::getValue('controller');
if ($controllerName === 'AdminPsOnePageCheckout') {
return true;
}

$configuredModule = trim((string) Tools::getValue('configure'));
if ($configuredModule === $this->name) {
return true;
}

return isset($this->context->controller)
&& in_array(get_class($this->context->controller), ['AdminPsOnePageCheckoutController'], true);
}
}
76 changes: 76 additions & 0 deletions src/Analytics/Analytics.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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 Academic Free License version 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/AFL-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.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

declare(strict_types=1);

namespace PrestaShop\Module\PsOnePageCheckout\Analytics;

use Segment\Segment;

/**
* Segment PHP SDK bootstrap, same approach as
* {@link https://github.com/PrestaShop/autoupgrade/blob/dev/classes/Analytics.php PrestaShop\Module\AutoUpgrade\Analytics}.
*
* Event tracking (track) will be added in follow-up work.
*/
final class Analytics
{
/**
* Segment PHP source write keys env vars — single source of truth (not stored in configuration).
*/
public const SEGMENT_PREPROD_KEY = 'SEGMENT_PREPROD_KEY';
public const SEGMENT_PROD_KEY = 'SEGMENT_PROD_KEY';

private static bool $clientInitialized = false;

public static function bootstrap(bool $moduleSegmentEnabled): void
{
if (!$moduleSegmentEnabled || self::$clientInitialized) {
return;
}

$writeKey = self::getWriteKey();
if ($writeKey === '') {
return;
}

Segment::init($writeKey);
self::$clientInitialized = true;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(blocking)

On pourrait ajouter une méthode générique pour initialiser & tracker les événements
Tu n'aurais plu besoin de la logique dans hookActionAdminControllerSetMedia
L’initialisation de Segment ne devrait pas dépendre d'un hook.

Exemple:

private const EVENT_GUEST_INIT_SUCCEEDED = 'opc_guest_init_succeeded';

 public static function bootstrap(): void
 {
        if (self::$clientInitialized === true) {
            return;
        }

        $writeKey = trim((string) self::SEGMENT_CLIENT_KEY_PHP);
        if ($writeKey === '') {
            return;
        }

        Segment::init($writeKey);
        self::$clientInitialized = true;
}

/**
 * @param array<string, mixed> $properties
 * @param array<string, mixed> $context
 */
private static function track(
    string $event,
    array $properties = [],
    array $context = []
): void {
    try {
        self::bootstrap();

        if (self::$clientInitialized === false) {
            return;
        }

        Segment::track([
            'event' => $event,
            'properties' => $properties,
            'context' => $context,
        ]);
    } catch (Throwable $exception) {
        PrestaShopLogger::addLog(
            sprintf('ps_onepagecheckout analytics track failed for "%s": %s', $event, $exception->getMessage()),
            2
        );
    }
}

public static function trackGuestInitSucceeded(array $properties = []): void
{
    self::track(EVENT_GUEST_INIT_SUCCEEDED, $properties);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dylanDenizonPresta ce commentaire est bloquant et doit être résolu afin d'avoir un tracking robuste, réutilisable et centralisé


private static function getWriteKey(): string
{
$isDevMode = defined('_PS_MODE_DEV_') && (bool) _PS_MODE_DEV_;
$writeKeyEnvVar = $isDevMode ? self::SEGMENT_PREPROD_KEY : self::SEGMENT_PROD_KEY;

return self::getEnv($writeKeyEnvVar);
}

private static function getEnv(string $name): string
{
$value = getenv($name);
if ($value === false) {
$value = $_ENV[$name] ?? '';
}

return trim((string) $value);
}
}
29 changes: 29 additions & 0 deletions src/Analytics/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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 Academic Free License version 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/AFL-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.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
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;
6 changes: 4 additions & 2 deletions src/Form/BackOfficeConfigurationForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ class BackOfficeConfigurationForm
private \Module $module;
private string $configurationKey;

public function __construct(\Module $module, string $configurationKey)
{
public function __construct(
\Module $module,
string $configurationKey,
) {
$this->module = $module;
$this->configurationKey = $configurationKey;
}
Expand Down
Loading
Loading