Current File : /var/www/vinorea/modules/klaviyopsautomation/classes/HooksHandler.php
<?php

/**
 * Klaviyo
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Commercial License
 * you can't distribute, modify or sell this code
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file
 * If you need help please contact extensions@klaviyo.com
 *
 * @author    Klaviyo
 * @copyright Klaviyo
 * @license   commercial
 */

namespace KlaviyoPs\Classes;

if (!defined('_PS_VERSION_')) {
    exit;
}

use Address;
use Country;
use Configuration;
use Context;
use Customer;
use CustomerCore;
use Exception;
use FormField;
use Tools;
use Validate;
use KlaviyoPsModule;
use KlaviyoPs\Classes\KlaviyoServices\ProfileEventService;
use KlaviyoPs\Classes\PrestashopServices\ContextService;
use KlaviyoPs\Classes\PrestashopServices\CustomerService;
use KlaviyoPs\Classes\PrestashopServices\LoggerService;
use KlaviyoV3Sdk\Exception\KlaviyoApiException;

class HooksHandler
{
    /**
     * @var KlaviyoPsModule
     */
    const NEWSLETTER_SUBSCRIPTION = 0;
    const NEWSLETTER_UNSUBSCRIPTION = 1;
    const ERROR_SUBSCRIBING_CUSTOMER = 'Error while trying to subscribe to Klaviyo list, customer with email: ';
    const ERROR_SUBSCRIBING_SMS_CUSTOMER = 'Error while trying to subscribe to Klaviyo list, customer with sms: ';
    const ERROR_UPDATING_CUSTOM_PROPERTIES = 'Error while trying to update custom properties for customer with email: ';

    private $klaviyoModule;

    /**
     * HooksHandler constructor.
     *
     * @param KlaviyoPsModule $klaviyopsModule
     */
    public function __construct(KlaviyoPsModule $klaviyopsModule)
    {
        $this->klaviyoModule = $klaviyopsModule;
    }

    /**
     * Handle actionCustomerAccount hooks. Includes add and update. Subscribe customer
     * to the Klaviyo list selected in module settings if they subscribed, are active
     * and aren't deleted.
     *
     * @param array $params
     * @param string $event
     * @return void
     */
    public function handleActionCustomerAccount(array $params, $event)
    {
        try {
            /** @var ContextService $contextService */
            $contextService = $this->klaviyoModule->getService('klaviyops.prestashop_services.context');
            /** @var CustomerService $customerService */
            $customerService = $this->klaviyoModule->getService('klaviyops.prestashop_services.customer');
            /** @var ProfileEventService $profileEventService */
            $profileEventService = $this->klaviyoModule->getService('klaviyops.klaviyo_service.profile_event');

            $api = new KlaviyoApiWrapper();
            $customer = $this->getCustomerFromHookParams($params);

            if ($customer === null) {
                return;
            }

            $normalizedContext = $contextService->normalize(); // Current context
            $normalizedCustomer = $customerService->normalize(
                $customer,
                $normalizedContext
            );

            if (
                $customer->newsletter &&
                $customer->active &&
                !$customer->deleted &&
                Configuration::get('KLAVIYO_PRIVATE_API')
            ) {
                try {
                    $api->subscribeCustomer($customer->email);
                } catch (KlaviyoApiException $e) {
                    /** @var LoggerService $logger */
                    $logger = $this->klaviyoModule->getService('klaviyops.prestashop_services.logger');

                    $logger->log('error', self::ERROR_SUBSCRIBING_CUSTOMER . $customer->email);
                }
            }

            try {
                $profileEventService->track($event, $normalizedCustomer);
            } catch (KlaviyoApiException $e) {
                /** @var LoggerService $logger */
                $logger = $this->klaviyoModule->getService('klaviyops.prestashop_services.logger');

                $logger->log('error', self::ERROR_UPDATING_CUSTOM_PROPERTIES . $customer->email);
            }
        } catch (Exception $e) {
            /** @var LoggerService $logger */
            $logger = $this->klaviyoModule->getService('klaviyops.prestashop_services.logger');

            if (Validate::isLoadedObject($customer)) {
                $logger->log('error', "An error occured in handleActionCustomerAccount for customer {$customer->email}");
            } else {
                $logger->log('error', 'An error occured in handleActionCustomerAccount');
            }
        }
    }

    /**
     * Handle actionNewsletterSubscriptionAfter hook used in the default PrestaShop
     * Newsletter Subscription module.
     *
     * @param array $params
     */
    public function handleActionNewsletterSubscription(array $params)
    {
        if (
            $params['action'] == static::NEWSLETTER_SUBSCRIPTION &&
            !$params['error'] &&
            Configuration::get('KLAVIYO_PRIVATE_API')
        ) {
            try {
                $api = new KlaviyoApiWrapper();
                $api->subscribeCustomer($params['email']);
            } catch (KlaviyoApiException $e) {
                $logger = new LoggerService();
                $logger->log('error', self::ERROR_SUBSCRIBING_CUSTOMER . $params['email']);
            }
        }
    }

    /**
     * Return new Webservice Resource definition to use specific management interface.
     *
     * @param array $resources
     * @return array[]
     */
    public function handleAddWebserviceResources(array $resources)
    {
        return [
            'klaviyo' => [
                'description' => 'Klaviyo custom endpoints',
                'specific_management' => true,
            ]
        ];
    }

    /**
     * Add SMS Consent to customer address form
     *
     * @param  array $params
     * @return array
     */
    public function handleAdditionalCustomerAddressFields(array $params)
    {
        try {
            if (!$this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_IS_SYNCING_SMS_SUBSCRIBERS')) {
                throw new KlaviyoApiException('Sync SMS Subscribers disabled');
            }

            $trigger = $this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_SMS_SUBSCRIBE_TRIGGER');
            if (!$trigger) {
                throw new KlaviyoApiException('SMS Subscribe trigger not configured');
            }

            $idLang = (int) Context::getContext()->language->id;

            $smsConsentLabel = $this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_SMS_CONSENT_LABEL', $idLang);
            $smsConsentDisclosure = $this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_SMS_CONSENT_DISCLOSURE_TEXT', $idLang);
            $label = "{$smsConsentLabel}<br><em>{$smsConsentDisclosure}</em>";

            $formField = new FormField();
            $formField->setName('kl_sms_consent');
            $formField->setType('checkbox');
            $formField->setLabel($label);
            $formField->setRequired(false);

            return [$formField];
        } catch (KlaviyoApiException $e) {
            return [];
        }
    }

    /**
     * Subscribe mobile to klaviyo list
     *
     * @param  array $params
     * @return void
     */
    public function handleActionSubmitCustomerAddressForm(array $params)
    {
        if (
            !empty($params['address']) &&
            is_object($params['address']) &&
            Configuration::get('KLAVIYO_PRIVATE_API') &&
            isset($params['address']->kl_sms_consent) &&
            !empty($params['address']->kl_sms_consent)
        ) {
            $context = Context::getContext();

            if (!$this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_IS_SYNCING_SMS_SUBSCRIBERS')) {
                return;
            }

            $trigger = $this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_SMS_SUBSCRIBE_TRIGGER');
            if ($trigger === 'place_order') {
                // Save information in cookie to be triggered after payment (place order)
                $cookie = $context->cookie;
                $addressFields = [
                    $params['address']->phone ?? '',
                    $params['address']->phone_mobile ?? '',
                ];
                $addressToHash = implode('', array_map('strval', $addressFields));
                $cookie->kl_sms_consent = Tools::hash($addressToHash);
                return;
            }

            // Trigger start checkout
            $mobile = $params['address']->phone_mobile ?? $params['address']->phone ?? '';
            $isoCode = '';
            if (!empty($params['address']->id_country)) {
                $country = new Country($params['address']->id_country);
                if (Validate::isLoadedObject($country)) {
                    $isoCode = $country->iso_code;
                }
            }

            $attributes = ['mobile' => $mobile];

            $customer = new Customer((int)$params['address']->id_customer);

            if (!Validate::isLoadedObject($customer)) {
                $customer = $context->customer;
            }

            if (Validate::isLoadedObject($customer)) {
                $attributes['email'] = $customer->email;
            }

            $this->sendSMSConsentRequestAPI($attributes, $isoCode);
        }
    }

    /**
     * Subscribe mobile to klaviyo list (place order trigger)
     *
     * @param  array $params
     * @return void
     */
    public function handleDisplayOrderConfirmation($params)
    {
        $cookie = Context::getContext()->cookie;

        if (
            !empty($params['order']) &&
            Validate::isLoadedObject($params['order']) &&
            Configuration::get('KLAVIYO_PRIVATE_API') &&
            isset($cookie->kl_sms_consent) &&
            !empty($cookie->kl_sms_consent)
        ) {
            if (!$this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_IS_SYNCING_SMS_SUBSCRIBERS')) {
                return;
            }

            $trigger = $this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_SMS_SUBSCRIBE_TRIGGER');
            if ($trigger !== 'place_order') {
                return;
            }

            // Check consent to invoice address
            $consentAddress = null;
            foreach (
                [
                    $params['order']->id_address_invoice,
                    $params['order']->id_address_delivery
                ] as $idAddress
            ) {
                $address = new Address((int)$idAddress);
                if (Validate::isLoadedObject($address)) {
                    $addressFields = [
                        $address->phone ?? '',
                        $address->phone_mobile ?? '',
                    ];
                    $addressToHash = implode('', array_map('strval', $addressFields));
                    $addressHash = Tools::hash($addressToHash);

                    if ($cookie->kl_sms_consent === $addressHash) {
                        $consentAddress = $address;
                        break;
                    }
                }
            }

            // Get mobile on invoice address
            if (is_null($consentAddress)) {
                return;
            }

            $mobile = $address->phone_mobile ?? $address->phone ?? '';
            $isoCode = '';
            if (!empty($address->id_country)) {
                $country = new Country($address->id_country);
                if (Validate::isLoadedObject($country)) {
                    $isoCode = $country->iso_code;
                }
            }

            $attributes = ['mobile' => $mobile];

            $customer = new Customer((int)$params['order']->id_customer);
            if (Validate::isLoadedObject($customer)) {
                $attributes['email'] = $customer->email;
            }

            $this->sendSMSConsentRequestAPI($attributes, $isoCode);
        }
    }

    /**
     * Callback for hook that fires after MailAlert resource is created. Supports
     * Back in Stock subscription in Klaviyo.
     *
     * @param $params
     * @return void
     */
    public function handleActionObjectMailAlertAddAfter($params)
    {
        if ((int)$this->klaviyoModule->getConfigurationValueOrNull('KLAVIYO_BIS_ENABLED') !== 1) {
            return;
        }

        $mailAlertObject = $params['object'];
        // Klaviyo catalogs expect a constructed variant identifier of the
        // format {product_id}:{variant_id}, e.g. 19-3 or 21-0 (simple product).
        $constructedId =  "$mailAlertObject->id_product-$mailAlertObject->id_product_attribute";

        $api = new KlaviyoApiWrapper();
        try {
            $api->createBackInStockSubscription($mailAlertObject->customer_email, $constructedId, $mailAlertObject->id_shop, $mailAlertObject->id_lang);
        } catch (KlaviyoApiException $e) {
            $logger = $this->klaviyoModule->getService('klaviyops.prestashop_services.logger');
            $logger->error("{$e->getCode()} error while sending Back in Stock subscription event. {$e->getMessage()}");
        }
    }

    /**
     * Extract Customer object from hook params.
     *
     * @param array $hookParams
     * @return Customer|null
     */
    private function getCustomerFromHookParams(array $hookParams)
    {
        if (isset($hookParams['customer']) && $hookParams['customer'] instanceof CustomerCore) {
            return $hookParams['customer'];
        }

        if (isset($hookParams['newCustomer']) && $hookParams['newCustomer'] instanceof CustomerCore) {
            return $hookParams['newCustomer'];
        }

        return null;
    }

    /**
     * Subscribe mobile to a list
     *
     * @param  array $address
     * @param  string $isoCode
     * @return void
     */
    private function sendSMSConsentRequestAPI($address = [], $isoCode = '')
    {
        if (empty($address) || !is_array($address)) {
            return;
        }
        if (empty($address['mobile'])) {
            return;
        }

        try {
            $address['mobile'] = KlaviyoUtils::formatPhone($address['mobile'], $isoCode);

            $api = new KlaviyoApiWrapper();
            $api->subscribeSMSCustomer($address);
        } catch (KlaviyoApiException $e) {
            $logger = new LoggerService();
            $logger->log('error', self::ERROR_SUBSCRIBING_SMS_CUSTOMER . $address['mobile']);
        }
    }
}