Skip to content
Draft
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.

61 changes: 61 additions & 0 deletions docs/SEGMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 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 **en dur** dans `Analytics::SEGMENT_CLIENT_KEY_PHP` → `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 |
|--------|-------------|------|--------|
| Code PHP | `Analytics::SEGMENT_CLIENT_KEY_PHP` | Write key de la **source PHP** Segment — **seule source de vérité** (pas de `configuration`). | `''` |

## 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`.
- La clé **PHP** (`SEGMENT_CLIENT_KEY_PHP`) n’est 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 en dur dans le dépôt : politique de secret / environnements à définir côté équipe.
- `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);
}
}
58 changes: 58 additions & 0 deletions src/Analytics/Analytics.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?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}
* (constant write key).
*
* Event tracking (track) will be added in follow-up work.
*/
final class Analytics
{
/**
* Segment PHP source write key — single source of truth (not stored in configuration).
*/
public const SEGMENT_CLIENT_KEY_PHP = '';

private static bool $clientInitialized = false;

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

$writeKey = trim((string) self::SEGMENT_CLIENT_KEY_PHP);
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é

}
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
34 changes: 34 additions & 0 deletions tests/php/Unit/Analytics/AnalyticsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Tests\Unit\Analytics;

use PHPUnit\Framework\TestCase;
use PrestaShop\Module\PsOnePageCheckout\Analytics\Analytics;

class AnalyticsTest extends TestCase
{
public function testBootstrapDoesNothingWhenModuleSegmentDisabled(): void
{
Analytics::bootstrap(false);

self::assertTrue(true);
}

public function testBootstrapDoesNothingWhenWriteKeyConstantEmpty(): void
{
if (trim((string) Analytics::SEGMENT_CLIENT_KEY_PHP) !== '') {
self::markTestSkipped('SEGMENT_CLIENT_KEY_PHP is set; empty-key path not exercised.');
}

Analytics::bootstrap(true);

self::assertTrue(true);
}
}
Loading
Loading