Current File : //var/www/prestashop/modules/ps_eventbus/src/Controller/AbstractApiController.php
<?php

namespace PrestaShop\Module\PsEventbus\Controller;

use PrestaShop\Module\PsEventbus\Config\Config;
use PrestaShop\Module\PsEventbus\Exception\EnvVarException;
use PrestaShop\Module\PsEventbus\Exception\FirebaseException;
use PrestaShop\Module\PsEventbus\Exception\QueryParamsException;
use PrestaShop\Module\PsEventbus\Exception\UnauthorizedException;
use PrestaShop\Module\PsEventbus\Handler\ErrorHandler\ErrorHandler;
use PrestaShop\Module\PsEventbus\Provider\PaginatedApiDataProviderInterface;
use PrestaShop\Module\PsEventbus\Repository\EventbusSyncRepository;
use PrestaShop\Module\PsEventbus\Repository\IncrementalSyncRepository;
use PrestaShop\Module\PsEventbus\Repository\LanguageRepository;
use PrestaShop\Module\PsEventbus\Service\ApiAuthorizationService;
use PrestaShop\Module\PsEventbus\Service\ProxyService;
use PrestaShop\Module\PsEventbus\Service\PsAccountsAdapterService;
use PrestaShop\Module\PsEventbus\Service\SynchronizationService;

const MYSQL_DATE_FORMAT = 'Y-m-d H:i:s';

abstract class AbstractApiController extends \ModuleFrontController
{
    /**
     * Endpoint name
     *
     * @var string
     */
    public $type = '';
    /**
     * Timestamp when script started
     *
     * @var int
     */
    public $startTime;
    /**
     * @var ApiAuthorizationService
     */
    protected $authorizationService;
    /**
     * @var ProxyService
     */
    protected $proxyService;
    /**
     * @var EventbusSyncRepository
     */
    protected $eventbusSyncRepository;
    /**
     * @var LanguageRepository
     */
    private $languageRepository;
    /**
     * @var PsAccountsAdapterService
     */
    private $psAccountsAdapterService;
    /**
     * @var IncrementalSyncRepository
     */
    protected $incrementalSyncRepository;
    /**
     * @var SynchronizationService
     */
    private $synchronizationService;
    /**
     * @var \Ps_eventbus
     */
    public $module;
    /**
     * @var bool
     */
    public $psAccountsInstalled = true;

    /**
     * @var ErrorHandler
     */
    public $errorHandler;

    public function __construct()
    {
        parent::__construct();

        $this->ajax = true;
        $this->content_only = true;
        $this->controller_type = 'module';

        $this->errorHandler = $this->module->getService(ErrorHandler::class);
        try {
            $this->psAccountsAdapterService = $this->module->getService(PsAccountsAdapterService::class);
            $this->proxyService = $this->module->getService(ProxyService::class);
            $this->authorizationService = $this->module->getService(ApiAuthorizationService::class);
            $this->synchronizationService = $this->module->getService(SynchronizationService::class);
        } catch (\Exception $exception) {
            $this->errorHandler->handle($exception);
            $this->exitWithExceptionMessage($exception);
        }

        $this->eventbusSyncRepository = $this->module->getService(EventbusSyncRepository::class);
        $this->languageRepository = $this->module->getService(LanguageRepository::class);
        $this->incrementalSyncRepository = $this->module->getService(IncrementalSyncRepository::class);
    }

    /**
     * @return bool|void
     *
     * @throws UnauthorizedException
     */
    public function init()
    {
        $this->startTime = time();

        try {
            $this->authorize();
        } catch (\Exception $exception) {
            // For ApiHealthCheck, handle the error, and throw UnauthorizedException directly, to catch-up at top level.
            if (strpos($this->page_name, 'apiHealthCheck') !== false) {
                $this->errorHandler->handle($exception);
                throw new UnauthorizedException('You are not allowed to access to this resource');
            }

            if ($exception instanceof \PrestaShopDatabaseException) {
                $this->errorHandler->handle($exception);
                $this->exitWithExceptionMessage($exception);
            } elseif ($exception instanceof EnvVarException) {
                $this->errorHandler->handle($exception);
                $this->exitWithExceptionMessage($exception);
            } elseif ($exception instanceof FirebaseException) {
                $this->errorHandler->handle($exception);
                $this->exitWithExceptionMessage($exception);
            }
        }
    }

    /**
     * @return void
     *
     * @throws \PrestaShopDatabaseException|EnvVarException|FirebaseException
     */
    private function authorize()
    {
        /** @var string $jobId */
        $jobId = \Tools::getValue('job_id', 'empty_job_id');

        $authorizationResponse = $this->authorizationService->authorizeCall($jobId);

        if (is_array($authorizationResponse)) {
            $this->exitWithResponse($authorizationResponse);
        } elseif (!$authorizationResponse) {
            throw new \PrestaShopDatabaseException('Failed saving job id to database');
        }

        try {
            $token = $this->psAccountsAdapterService->getOrRefreshToken();
        } catch (\Exception $exception) {
            throw new FirebaseException($exception->getMessage());
        }

        if (!$token) {
            throw new FirebaseException('Invalid token');
        }
    }

    /**
     * @param PaginatedApiDataProviderInterface $dataProvider
     *
     * @return array<mixed>
     */
    protected function handleDataSync(PaginatedApiDataProviderInterface $dataProvider)
    {
        /** @var bool $debug */
        $debug = \Tools::getValue('debug') == 1;

        /** @var string $jobId */
        $jobId = \Tools::getValue('job_id');
        /** @var string $langIso */
        $langIso = \Tools::getValue('lang_iso', $this->languageRepository->getDefaultLanguageIsoCode());
        /** @var int $limit */
        $limit = \Tools::getValue('limit', 50);

        if ($limit < 0) {
            $this->exitWithExceptionMessage(new QueryParamsException('Invalid URL Parameters', Config::INVALID_URL_QUERY));
        }

        /** @var bool $initFullSync */
        $initFullSync = \Tools::getValue('full', 0) == 1;

        /** @var \PrestaShop\Module\PsEventbus\Repository\ConfigurationRepository $configurationRepository */
        $configurationRepository = $this->module->getService(\PrestaShop\Module\PsEventbus\Repository\ConfigurationRepository::class);
        $timezone = (string) $configurationRepository->get('PS_TIMEZONE');

        $dateNow = (new \DateTime('now', new \DateTimeZone($timezone)))->format(MYSQL_DATE_FORMAT);
        $offset = 0;
        $incrementalSync = false;
        $response = [];

        try {
            $typeSync = $this->eventbusSyncRepository->findTypeSync($this->type, $langIso);

            if ($debug) {
                $response = $dataProvider->getQueryForDebug($offset, $limit, $langIso);

                return array_merge(
                    [
                        'object_type' => $this->type,
                    ],
                    $response
                );
            }

            if ($typeSync !== false && is_array($typeSync)) {
                $offset = (int) $typeSync['offset'];

                if ((int) $typeSync['full_sync_finished'] === 1 && !$initFullSync) {
                    $incrementalSync = true;
                } elseif ($initFullSync) {
                    $offset = 0;
                    $this->eventbusSyncRepository->updateTypeSync(
                        $this->type,
                        $offset,
                        $dateNow,
                        false,
                        $langIso
                    );

                    $this->incrementalSyncRepository->removeIncrementaSyncObjectByType($this->type);
                }
            } else {
                $this->eventbusSyncRepository->insertTypeSync($this->type, $offset, $dateNow, $langIso);
            }

            if ($incrementalSync) {
                $response = $this->synchronizationService->handleIncrementalSync(
                    $dataProvider,
                    $this->type,
                    $jobId,
                    $limit,
                    $langIso,
                    $this->startTime,
                    $initFullSync
                );
            } else {
                $response = $this->synchronizationService->handleFullSync(
                    $dataProvider,
                    $this->type,
                    $jobId,
                    $langIso,
                    $offset,
                    $limit,
                    $dateNow,
                    $this->startTime,
                    $initFullSync
                );
            }

            return array_merge(
                [
                    'job_id' => $jobId,
                    'object_type' => $this->type,
                    'syncType' => $incrementalSync ? 'incremental' : 'full',
                ],
                $response
            );
        } catch (\PrestaShopDatabaseException $exception) {
            $this->errorHandler->handle($exception);
            $this->exitWithExceptionMessage($exception);
        } catch (EnvVarException $exception) {
            $this->errorHandler->handle($exception);
            $this->exitWithExceptionMessage($exception);
        } catch (FirebaseException $exception) {
            $this->errorHandler->handle($exception);
            $this->exitWithExceptionMessage($exception);
        } catch (\Exception $exception) {
            $this->errorHandler->handle($exception);
            $this->exitWithExceptionMessage($exception);
        }

        return $response;
    }

    /**
     * @param array<mixed>|null $value
     * @param string|null $controller
     * @param string|null $method
     *
     * @return void
     *
     * @throws \PrestaShopException
     */
    public function ajaxDie($value = null, $controller = null, $method = null)
    {
        parent::ajaxDie(json_encode($value) ?: null, $controller, $method);
    }

    /**
     * @param array<mixed> $response
     *
     * @return void
     */
    protected function exitWithResponse($response)
    {
        $httpCode = isset($response['httpCode']) ? (int) $response['httpCode'] : 200;

        $this->dieWithResponse($response, $httpCode);
    }

    /**
     * @param \Exception $exception
     *
     * @return void
     */
    protected function exitWithExceptionMessage(\Exception $exception)
    {
        $code = $exception->getCode() == 0 ? 500 : $exception->getCode();

        if ($exception instanceof \PrestaShopDatabaseException) {
            $code = Config::DATABASE_QUERY_ERROR_CODE;
        } elseif ($exception instanceof EnvVarException) {
            $code = Config::ENV_MISCONFIGURED_ERROR_CODE;
        } elseif ($exception instanceof FirebaseException) {
            $code = Config::REFRESH_TOKEN_ERROR_CODE;
        } elseif ($exception instanceof QueryParamsException) {
            $code = Config::INVALID_URL_QUERY;
        }

        $response = [
            'object_type' => $this->type,
            'status' => false,
            'httpCode' => $code,
            'message' => $exception->getMessage(),
        ];

        $this->dieWithResponse($response, (int) $code);
    }

    /**
     * @param array<mixed> $response
     * @param int $code
     *
     * @return void
     */
    private function dieWithResponse($response, $code)
    {
        $httpStatusText = "HTTP/1.1 $code";

        if (array_key_exists((int) $code, Config::HTTP_STATUS_MESSAGES)) {
            $httpStatusText .= ' ' . Config::HTTP_STATUS_MESSAGES[(int) $code];
        } elseif (isset($response['body']['statusText'])) {
            $httpStatusText .= ' ' . $response['body']['statusText'];
        }

        $response['httpCode'] = (int) $code;

        header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Content-Type: application/json;charset=utf-8');
        header($httpStatusText);

        echo json_encode($response, JSON_UNESCAPED_SLASHES);

        exit;
    }
}