Current File : /var/www/vinorea/modules/klaviyopsautomation/classes/PrestashopServices/CustomerService.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\PrestashopServices;

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

use ArrayObject;
use Configuration;
use Customer;
use DateTime;
use Db;
use DbQuery;
use Gender;
use Group;
use KlaviyoV3Sdk\Exception\KlaviyoException;
use Shop;
use ShopGroup;
use Validate;

class CustomerService
{
    /**
     * @var ValidateService
     */
    protected $validateService;

    /**
     * @var DateTimeService
     */
    protected $dateTimeService;

    /**
     * @var ContextService
     */
    protected $contextService;

    public function __construct(
        ValidateService $validateService,
        DateTimeService $dateTimeService,
        ContextService $contextService
    ) {
        $this->validateService = $validateService;
        $this->dateTimeService = $dateTimeService;
        $this->contextService = $contextService;
    }

    /**
     * Normalizing data from PrestaShop allows to use them in a uniform and secure way.
     * To use the methods of this class, start by normalizing data.
     *
     * @param Customer|array $customer
     * @return ArrayObject
     */
    public function normalize(
        $customer,
        ArrayObject $context
    ) {
        $customerObject = null;

        if (is_object($customer)) {
            $customerObject = $customer;

            $customer = (array) $customer;
            $customer['id_customer'] = $customerObject->id;
            $customer['cache_object'] = $customerObject;

            unset($customer['id']);
        }

        if (!isset($customer['id_customer'])) {
            $customer['id_customer'] = 0;
        }

        if (!isset($customer['id_shop'])) {
            $customer['id_shop'] = $context['id_shop'];
        }

        if (!isset($customer['id_shop_group'])) {
            $customer['id_shop_group'] = $context['id_shop_group'];
        }

        if (!isset($customer['id_gender'])) {
            $customer['id_gender'] = 0;
        }

        if (!isset($customer['id_default_group'])) {
            $customer['id_default_group'] = 0;
        }

        $customer['id_customer'] = (int) $customer['id_customer'];
        $customer['id_gender'] = (int) $customer['id_gender'];
        $customer['id_default_group'] = (int) $customer['id_default_group'];

        if ($customer['id_default_group'] === 0) {
            // If the current customer has not yet an account
            // We must set the default group which corresponds with the "unidentified" group
            // This allows us to have the same data returned by getAllGroups in this case
            // Because it's the behaviour of Customer::getGroups
            if ($customer['id_customer'] === 0) {
                $customer['id_default_group'] = (int) Configuration::get('PS_UNIDENTIFIED_GROUP');
            } else {
                $customer['id_default_group'] = (int) Customer::getDefaultGroupId(
                    $customer['id_customer']
                );
            }
        }

        $customer['context'] = $context;

        // Do not use these data as context
        // Indeed, This represents the context that user belongs to
        // But not the current context
        // There's the "context" entry for this
        $customer['id_shop'] = (int) $customer['id_shop'];
        $customer['id_shop_group'] = (int) $customer['id_shop_group'];

        return new ArrayObject($customer);
    }

    /**
     * Get Customer object from normalized data
     *
     * @param ArrayObject $customer
     * @return Customer
     * @throws KlaviyoException
     */
    public function getObject(ArrayObject $customer)
    {
        if (!$customer->offsetExists('cache_object')) {
            $customer['cache_object'] = new Customer($customer['id_customer']);

            // If the current customer has not yet an account
            // We must return a not "loaded" Customer object
            // And set email and shops properties
            if (!Validate::isLoadedObject($customer['cache_object'])) {
                $customer['cache_object']->email = $this->getEmail($customer);
                // By default, shop data are retrieved from the context
                // See self::normalize
                $customer['cache_object']->id_shop = $customer['id_shop'];
                $customer['cache_object']->id_shop_group = $customer['id_shop_group'];
            }
        }

        return $customer['cache_object'];
    }

    /**
     * Get the email address of a customer.
     *
     * Removed validation of email address as of 1.9.0 preferring
     * to let Klaviyo validate email addresses during event processing.
     *
     * @param ArrayObject $customer
     * @return string|null
     */
    public function getEmail(ArrayObject $customer)
    {
        return $customer['email'] ?? null;
    }

    /**
     * @return DateTime|null
     */
    public function getRegisterDate(ArrayObject $customer)
    {
        if (!$customer->offsetExists('date_add')) {
            return null;
        }

        return $this->dateTimeService->convertFromUTC($customer['date_add']);
    }

    /**
     * @return string|null
     */
    public function getBirthday(ArrayObject $customer)
    {
        if (
            !$customer->offsetExists('birthday') ||
            !Validate::isBirthDate($customer['birthday']) ||
            $customer['birthday'] === '0000-00-00'
        ) {
            return null;
        }

        return $customer['birthday'];
    }

    /**
     * @return Group[]
     * @throws KlaviyoException
     */
    public function getAllGroups(ArrayObject $customer)
    {
        $result = [];
        $allAccounts = $this->getAllAccounts($customer);

        foreach ($allAccounts as $account) {
            $accountObject = $this->getObject($account);

            foreach ($accountObject->getGroups() as $idGroup) {
                $group = $this->getGroupById(
                    (int) $idGroup,
                    $customer['context']
                );

                if ($group === null) {
                    continue;
                }

                // Avoids duplication by indexing by id_shop
                $result[(int) $group->id] = $group;
            }
        }

        // Removing indexing by id_shop
        return array_values($result);
    }

    /**
     * @return Group[]
     * @throws KlaviyoException
     */
    public function getDefaultGroups(ArrayObject $customer)
    {
        $result = [];
        // A customer can have an account on differents shops
        // And the same customer can be on differents customer group
        // So, a customer can have multiple default customer groups
        // Therefore, we need to retrieve the accounts on the differents shops for the customer
        // And get default group for each account
        $allAccounts = $this->getAllAccounts($customer);

        foreach ($allAccounts as $account) {
            // We need to get associated shops for an account
            // In order to provide the default group for each shops
            // Where the customer have an account
            $allShops = $this->getAssociatedShops($account);

            foreach ($allShops as $shop) {
                $group = $this->getGroupById(
                    $account['id_default_group'],
                    $account['context']
                );

                if ($group === null) {
                    continue;
                }

                // Indexing by shop id to have the default group for each shop
                $result[(int) $shop->id] = $group;
            }
        }

        return $result;
    }

    /**
     * @return Gender|null
     */
    public function getGender(ArrayObject $customer)
    {
        if (!$customer->offsetExists('cache_gender')) {
            $gender = new Gender(
                $customer['id_gender'],
                $customer['context']['id_default_lang']
            );

            if (!Validate::isLoadedObject($gender)) {
                $gender = null;
            }

            $customer['cache_gender'] = $gender;
        }

        return $customer['cache_gender'];
    }

    /**
     * A customer can have account in another shop
     * So, we get all its accounts by his mail
     *
     * @return ArrayObject[]
     * @throws KlaviyoException
     */
    public function getAllAccounts(ArrayObject $customer)
    {
        static $cache = [];

        // If multishop isn't enabled
        // Then, there's no need to get accounts over the shops
        if (!$this->contextService->hasMultishop()) {
            return [$customer];
        }

        $email = $this->getEmail($customer);

        if (!array_key_exists($email, $cache)) {
            $result = [];

            $emailEscaped = pSQL($email);

            $query = (new DbQuery())
                ->select('c.id_customer')
                ->from('customer', 'c')
                ->where("c.email = '{$emailEscaped}'")
            ;
            $otherAccounts = Db::getInstance()->executeS($query);

            if (!is_array($otherAccounts)) {
                throw new KlaviyoException('Impossible to get other accounts from the customer');
            }

            foreach ($otherAccounts as $account) {
                $o = new Customer((int) $account['id_customer']);

                if (!Validate::isLoadedObject($o)) {
                    continue;
                }

                $accountContext = $this->contextService->normalize([
                    'id_shop' => $o->id_shop,
                    'id_lang' => $o->id_lang,
                    'id_customer' => $o->id,
                ]);
                $result[] = $this->normalize(
                    $o,
                    $accountContext
                );
            }

            $cache[$email] = $result;
        }

        return $cache[$email];
    }

    /**
     * Retrieve the associated shops for this customer
     *
     * @return Shop[]
     * @throws KlaviyoException
     */
    public function getAssociatedShops(ArrayObject $customer)
    {
        if (!$customer->offsetExists('cache_associated_shops')) {
            // By default, return the shop where the customer is registered
            $result = [
                (new Shop($customer['id_shop'])),
            ];

            // If multishop isn't enabled
            // Then, there's no need to get associated shops for the customer
            // Get the shop where the customer is registered is enough
            if ($this->contextService->hasMultishop()) {
                $shopGroup = new ShopGroup($customer['id_shop_group']);

                if (!Validate::isLoadedObject($shopGroup)) {
                    throw new KlaviyoException('Customer doesn\'t have associated shop');
                }

                // If the store group is configured to share customers
                // Then, we must return all Shops in group
                // Else, we must return only the shop where the customer is
                if ($shopGroup->share_customer) {
                    $result = $this->contextService->getAllShops([
                        'id_shop_group' => $customer['id_shop_group'],
                    ]);
                }
            }

            $customer['cache_associated_shops'] = $result;
        }

        return $customer['cache_associated_shops'];
    }

    /**
     * Retrieves all stores where the customer has an account
     *
     * @return Shop[]
     * @throws KlaviyoException
     * @see self::getAllAccounts
     * @see self::getAssociatedShops
     */
    public function getAllShops(ArrayObject $customer)
    {
        if (!$customer->offsetExists('cache_all_shops')) {
            $result = [];
            /** @var Shop[] $temp */
            $temp = [];

            $otherAccounts = $this->getAllAccounts($customer);

            foreach ($otherAccounts as $account) {
                $temp = array_merge(
                    $temp,
                    $this->getAssociatedShops($account)
                );
            }

            // Avoids duplication by indexing by id_shop
            foreach ($temp as $shop) {
                $result[(int) $shop->id] = $shop;
            }

            // Removing indexing by id_shop
            $customer['cache_all_shops'] = array_values($result);
        }

        return $customer['cache_all_shops'];
    }

    /**
     * @param int $idGroup
     * @return Group|null
     */
    protected function getGroupById(
        $idGroup,
        ArrayObject $context
    ) {
        static $cache = [];

        if (!array_key_exists($idGroup, $cache)) {
            $group = new Group(
                $idGroup,
                $context['id_default_lang']
            );

            if (!Validate::isLoadedObject($group)) {
                $group = null;
            }

            $cache[$idGroup] = $group;
        }

        return $cache[$idGroup];
    }
}