Current File : /var/www/vinorea/modules/sendinblue/services/ProductService.php
<?php
/**
 * 2007-2025 Sendinblue
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * 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 contact@sendinblue.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to http://www.prestashop.com for more information.
 *
 * @author    Sendinblue <contact@sendinblue.com>
 * @copyright 2007-2025 Sendinblue
 * @license   http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Sendinblue
 */

namespace Sendinblue\Services;

use PrestaShop\PrestaShop\Adapter\Validate;
use PrestaShopBundle\Entity\AdminFilter;
use Symfony\Component\Process\Exception\LogicException;

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

class ProductService
{
    const FILTERING_LIKE_BOTH = 'LIKE \'%%%s%%\'';
    const FILTERING_LIKE_LEFT = 'LIKE \'%%%s\'';
    const FILTERING_EQUAL_NUMERIC = '= %s';
    const DEFAULT_LIMIT = 25;
    const DEFAULT_PAGE = 1;

    /**
     * @var int|null
     */
    private $idShop;

    /**
     * @var false|string
     */
    private $defaultLanguage;

    /**
     * @var false|int|string
     */
    private $idLang;

    public function __construct()
    {
        $this->idShop = \ContextCore::getContext()->shop->id;
        $this->defaultLanguage = \ConfigurationCore::get('PS_LANG_DEFAULT');
        $this->idLang = !empty($this->defaultLanguage) ? $this->defaultLanguage : \ContextCore::getContext()->language->id;
    }

    /**
     * logic cloned from PrestaShop\PrestaShop\Adapter\Product\AdminProductDataProvider::getCatalogProductList()
     * @param string $term
     * @param int $page
     * @param int $itemsPerPage
     * @return array|bool|mysqli_result|PDOStatement|resource|null
     */
    public function getProducts($term, $page, $itemsPerPage)
    {
        $orderBy = 'id_product';
        $sortOrder = 'desc';
        $offset = $this->calculateOffset($page, $itemsPerPage);

        $filterParams = [
            'filter_column_name' => $term,
            'filter_column_active' => 1,
            'last_offset' => $offset,
            'last_limit' => $itemsPerPage,
            'last_orderBy' => $orderBy,
            'last_sortOrder' => $sortOrder,
        ];
        $filterParams = AdminFilter::sanitizeFilterParameters($filterParams);

        $sqlSelect = [
            'id_product' => ['table' => 'p', 'field' => 'id_product', 'filtering' => ' %s '],
            'price' => ['table' => 'sa', 'field' => 'price', 'filtering' => ' %s '],
            'id_shop_default' => ['table' => 'p', 'field' => 'id_shop_default'],
            'name' => ['table' => 'pl', 'field' => 'name', 'filtering' => self::FILTERING_LIKE_BOTH],
            'id_language' => ['table' => 'pl', 'field' => 'id_lang'],
            'description' => ['table' => 'pl', 'field' => 'description', 'filtering' => self::FILTERING_LIKE_BOTH],
            'link_rewrite' => ['table' => 'pl', 'field' => 'link_rewrite', 'filtering' => self::FILTERING_LIKE_BOTH],
            'active' => ['table' => 'sa', 'field' => 'active', 'filtering' => self::FILTERING_EQUAL_NUMERIC],
            'id_image' => ['table' => 'image_shop', 'field' => 'id_image'],
            'name_category' => ['table' => 'cl', 'field' => 'name', 'filtering' => self::FILTERING_LIKE_BOTH],
            'id_category' => ['table' => 'cl', 'field' => 'id_category', 'filtering' => self::FILTERING_LIKE_BOTH],
            'link_rewrite_category' => [
                'table' => 'cl',
                'field' => 'link_rewrite',
                'filtering' => self::FILTERING_LIKE_BOTH,
            ],
            'price_final' => '0',
        ];
        $sqlTable = [
            'p' => 'product',
            'pl' => [
                'table' => 'product_lang',
                'join' => 'LEFT JOIN',
                'on' => sprintf(
                    'pl.`id_product` = p.`id_product` AND pl.`id_lang` = %s AND pl.`id_shop` = %s',
                    (int) $this->idLang,
                    (int) $this->idShop
                ),
            ],
            'sa' => [
                'table' => 'product_shop',
                'join' => 'JOIN',
                'on' => sprintf(
                    'p.`id_product` = sa.`id_product` AND sa.id_shop = %s',
                    (int) $this->idShop
                ),
            ],
            'cl' => [
                'table' => 'category_lang',
                'join' => 'LEFT JOIN',
                'on' => sprintf(
                    'sa.`id_category_default` = cl.`id_category` AND cl.`id_lang` = %s AND cl.id_shop = %s',
                    (int) $this->idLang,
                    (int) $this->idShop
                ),
            ],
            'c' => [
                'table' => 'category',
                'join' => 'LEFT JOIN',
                'on' => 'c.`id_category` = cl.`id_category`',
            ],
            'shop' => [
                'table' => 'shop',
                'join' => 'LEFT JOIN',
                'on' => sprintf('shop.id_shop = %s', (int) $this->idShop),
            ],
            'image_shop' => [
                'table' => 'image_shop',
                'join' => 'LEFT JOIN',
                'on' => sprintf(
                    'image_shop.`id_product` = p.`id_product` AND image_shop.`cover` = 1 AND image_shop.id_shop = %s',
                    (int) $this->idShop
                ),
            ],
            'i' => [
                'table' => 'image',
                'join' => 'LEFT JOIN',
                'on' => 'i.`id_image` = image_shop.`id_image`',
            ],
            'pd' => [
                'table' => 'product_download',
                'join' => 'LEFT JOIN',
                'on' => 'pd.`id_product` = p.`id_product`',
            ],
        ];
        $sqlWhere = ['AND', 1];
        $sqlOrder = [$orderBy . ' ' . $sortOrder];
        if ($orderBy != 'id_product') {
            $sqlOrder[] = 'id_product asc';
        }
        $sqlLimit = (int) $offset . ', ' . (int) $itemsPerPage;

        $sqlGroupBy = [];

        foreach ($filterParams as $filterParam => $filterValue) {
            if (!$filterValue && $filterValue !== '0') {
                continue;
            }
            if (strpos($filterParam, 'filter_column_') === 0) {
                $field = \Tools::substr($filterParam, 14); // 'filter_column_' takes 14 chars
                if ($sqlSelect[$field]['filtering'] == self::FILTERING_EQUAL_NUMERIC) {
                    $filterValue = (int) $filterValue;
                } else {
                    $filterValue = \DbCore::getInstance()->escape($filterValue, in_array($filterParam, [
                        'filter_column_id_product',
                        'filter_column_price',
                    ]), true);
                }
                if (isset($sqlSelect[$field]['table'])) {
                    $where = $sqlSelect[$field]['table'] . '.`' . $sqlSelect[$field]['field'];
                    $where .= '` ' . sprintf($sqlSelect[$field]['filtering'], $filterValue);

                    $sqlWhere[] = $where;
                } else {
                    $sqlWhere[] = '(' . sprintf($sqlSelect[$field]['filtering'], $filterValue) . ')';
                }
            }
        }
        $sqlWhere[] = 'state = ' . \ProductCore::STATE_SAVED;

        $sql = $this->compileSqlQuery($sqlSelect, $sqlTable, $sqlWhere, $sqlGroupBy, $sqlOrder, $sqlLimit);
        $products = \DbCore::getInstance()->executeS($sql, true, false);

        foreach ($products as &$product) {
            $nothing = null;
            $product['price_final'] = \ProductCore::getPriceStatic(
                $product['id_product'],
                true,
                null,
                2,
                null,
                false,
                true,
                1,
                true,
                null,
                null,
                null,
                $nothing,
                true,
                true
            );

            $product['currency'] = (new \CurrencyCore(\ConfigurationCore::get('PS_CURRENCY_DEFAULT')))->iso_code;
            $product['image_link'] = $this->getProductImageUrl($product['id_product'], $product['link_rewrite']);
        }

        return $products;
    }

    /**
     * @param int $page
     * @param int $itemsPerPage
     * @return array
     */
    public function getProductsForFullSync($offset, $itemsPerPage)
    {
        $context = \ContextCore::getContext();
        $id_lang = (int) $context->language->id;
        $context->controller = new \FrontController();
        $order_by = 'id_product';
        $order_way = 'ASC';
        $id_category = false;
        $only_active = true;

        $all_products = \Product::getProducts($id_lang, $offset, $itemsPerPage, $order_by, $order_way, $id_category, $only_active, $context);

        if (empty($all_products)) {
            return [];
        }

        foreach ($all_products as $key => $product) {
            $products[$key]['id'] = (int) $product['id_product'];
            $products[$key]['name'] = $product['name'];
            $products[$key]['price'] = (string) \ProductCore::getPriceStatic($product['id_product']);
            $products[$key]['permalink'] = $this->getProductUrl($product);
            $products[$key]['date_created'] = gmdate('Y-m-d\TH:i:s', strtotime($product['date_add']));
            $products[$key]['date_modified'] = gmdate('Y-m-d\TH:i:s', strtotime($product['date_upd']));
            $products[$key]['status'] = isset($product['state']) ? (string) ($product['state']) : '';
            $products[$key]['type'] = isset($product['product_type']) ? $product['product_type'] : '';
            $products[$key]['description'] = isset($product['description']) ? $product['description'] : '';
            $products[$key]['short_description'] = isset($product['description_short']) ? $product['description_short'] : '';
            $products[$key]['categories'][] = $product['id_category_default'];

            $category = new \Category($product['id_category_default'], $id_lang);
            $products[$key]['categories'][0] = [
                'id' => (int) $product['id_category_default'],
                'name' => $category->name,
                'description' => $category->description,
                'parent' => (int) $category->id_parent,
                'slug' => $category->link_rewrite,
            ];
            $images = $this->getProductImageUrl($product['id_product'], $product['link_rewrite']);

            foreach ($images as $k => $v) {
                $products[$key]['images'][]['src'] = $v;
            }

            $products[$key]['currency'] = (new \CurrencyCore(\ConfigurationCore::get('PS_CURRENCY_DEFAULT')))->iso_code;
            $products[$key]['quantity'] = (string) \ProductCore::getQuantity($product['id_product']);
            $products[$key]['sku'] = $product['reference'];
        }

        return $products;
    }

    /**
     * @param string $productId
     * @param string $productLink
     * @return array
     */
    private function getProductImageUrl($productId, $productLink)
    {
        $imageLinks = [];
        try {
            $imagesQuery = sprintf('SELECT id_image FROM %simage_shop WHERE id_product = %s', _DB_PREFIX_, (int) $productId);
            $images = \DbCore::getInstance()->executeS($imagesQuery, true, false);
            foreach ($images as $image) {
                $imageLinks[] = \ContextCore::getContext()->link->getImageLink($productLink, $image['id_image']);
            }
        } catch (\Exception $e) {
            \PrestaShopLoggerCore::addLog($e->getMessage(), ConfigService::ERROR_LEVEL);
        }

        return $imageLinks;
    }

    /**
     * @param array $select
     * @param array $table
     * @param array $where
     * @param array $groupBy
     * @param array $order
     * @param null $limit
     * @return string
     */
    protected function compileSqlQuery($select, $table, $where = [], $groupBy = [], $order = [], $limit = null)
    {
        $sql = [];

        // SELECT
        $s = [];
        foreach ($select as $alias => $field) {
            $a = is_string($alias) ? ' AS `' . $alias . '`' : '';
            if (is_array($field)) {
                if (isset($field['table'])) {
                    $s[] = ' ' . $field['table'] . '.`' . $field['field'] . '` ' . $a;
                } elseif (isset($field['select'])) {
                    $s[] = ' ' . $field['select'] . $a;
                }
            } else {
                $s[] = ' ' . $field . $a;
            }
        }
        if (count($s) === 0) {
            throw new LogicException('Compile SQL failed: No field to SELECT!');
        }
        $sql[] = 'SELECT SQL_CALC_FOUND_ROWS' . implode(',' . PHP_EOL, $s);

        // FROM / JOIN
        $s = [];
        foreach ($table as $alias => $join) {
            if (!is_array($join)) {
                if (count($s) > 0) {
                    throw new LogicException(sprintf('Compile SQL failed: cannot join the table %s into SQL query without JOIN sepcs.', $join));
                }
                $s[0] = ' `' . _DB_PREFIX_ . $join . '` ' . $alias;
            } else {
                if (count($s) === 0) {
                    throw new LogicException(sprintf('Compile SQL failed: cannot join the table alias %s into SQL query before to insert initial table.', $alias));
                }

                $query = ' ' . $join['join'] . ' `' . _DB_PREFIX_ . $join['table'] . '` ' . $alias;
                $query .= ((isset($join['on'])) ? ' ON (' . $join['on'] . ')' : '');

                $s[] = $query;
            }
        }
        if (count($s) === 0) {
            throw new LogicException('Compile SQL failed: No table to insert into FROM!');
        }
        $sql[] = 'FROM ' . implode(' ' . PHP_EOL, $s);

        // WHERE (recursive call)
        if (count($where)) {
            $s = $this->compileSqlWhere($where);
            if (\Tools::strlen($s) > 0) {
                $sql[] = 'WHERE ' . $s . PHP_EOL;
            }
        }

        // GROUP BY
        if (!empty($groupBy)) {
            $sql[] = 'GROUP BY ' . implode('`, `', array_map('bqSQL', $groupBy)) . PHP_EOL;
        }

        // ORDER
        if (count($order) > 0) {
            $goodOrder = [];
            foreach ($order as $o) {
                $value = explode(' ', $o);
                if (!empty($value) && 2 === count($value)
                    && Validate::isOrderBy($value[0])
                    && Validate::isOrderWay($value[1])
                ) {
                    $goodOrder[] = ' `' . bqSQL($value[0]) . '` ' . $value[1];
                }
            }

            if (count($goodOrder) > 0) {
                $sql[] = 'ORDER BY ' . implode(', ', $goodOrder) . PHP_EOL;
            }
        }

        // LIMIT
        if ($limit) {
            $sql[] = 'LIMIT ' . $limit . PHP_EOL;
        }

        return implode(' ' . PHP_EOL, $sql) . ';';
    }

    /**
     * @param array $whereArray
     * @return mixed|string
     */
    private function compileSqlWhere($whereArray)
    {
        $operator = 'AND';
        $s = [];
        while ($item = array_shift($whereArray)) {
            if ($item == 'OR') {
                $operator = 'OR';
            } elseif ($item == 'AND') {
                $operator = 'AND';
            } else {
                $s[] = (is_array($item) ? $this->compileSqlWhere($item) : $item);
            }
        }
        if (count($s) == 1) {
            return $s[0];
        }

        return '(' . implode(' ' . $operator . ' ', $s) . ')';
    }

    /**
     * @param string $term
     * @return int
     */
    public function getTotalCount($term)
    {
        $result = \DbCore::getInstance()->executeS(
            sprintf(
                'SELECT count(*)  AS `count` FROM  `%sproduct` p LEFT JOIN `%sproduct_lang` pl ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = %s AND pl.`id_shop` = %s ) JOIN `%sproduct_shop` sa ON (p.`id_product` = sa.`id_product` AND sa.id_shop = 1) WHERE (1 AND pl.`name` LIKE "%%%s%%" AND sa.`active` = 1 AND state = 1)',
                _DB_PREFIX_,
                _DB_PREFIX_,
                (int) $this->idLang,
                (int) $this->idShop,
                _DB_PREFIX_,
                pSQL($term)
            )
        );

        return isset($result[0]['count']) ? (int) $result[0]['count'] : 0;
    }

    /**
     * @param string $term
     * @return int
     */
    public function getTotalProductCount()
    {
        $result = \DbCore::getInstance()->executeS(
            sprintf(
                'SELECT count(*)  AS `count` FROM  `%sproduct` p LEFT JOIN `%sproduct_lang` pl ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = %s AND pl.`id_shop` = %s ) JOIN `%sproduct_shop` sa ON (p.`id_product` = sa.`id_product` AND sa.id_shop = 1) WHERE (sa.`active` = 1 AND state = 1 AND sa.`visibility` IN ("both", "catalog"))',
                _DB_PREFIX_,
                _DB_PREFIX_,
                (int) $this->idLang,
                (int) $this->idShop,
                _DB_PREFIX_
            )
        );

        return isset($result[0]['count']) ? (int) $result[0]['count'] : 0;
    }

    /**
     * @param int $itemsPerPage
     * @return int
     */
    public function fixItemsPerPage($itemsPerPage)
    {
        return empty($itemsPerPage) ? self::DEFAULT_LIMIT : $itemsPerPage;
    }

    /**
     * @param \ProductCore|\Product $product
     * @return string
     */
    private function getProductUrl($product)
    {
        return \ContextCore::getContext()->link->getProductLink($product);
    }

    /**
     * @param int $page
     * @return int
     */
    public function fixPage($page)
    {
        return empty($page) ? self::DEFAULT_PAGE : $page;
    }

    /**
     * @param int $page
     * @param int $itemsPerPage
     * @return int
     */
    public function calculateOffset($page, $itemsPerPage)
    {
        return $page === 1 ? 0 : ($itemsPerPage * $page) - $itemsPerPage;
    }
}