Current File : /var/www/vinorea/src/PrestaShopBundle/Bridge/Helper/Listing/HelperBridge/HelperListBridge.php
<?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 Open Software License (OSL 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/OSL-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.
 *
 * 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 https://devdocs.prestashop.com/ for more information.
 *
 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
 */

declare(strict_types=1);

namespace PrestaShopBundle\Bridge\Helper\Listing\HelperBridge;

use Context;
use Db;
use Exception;
use HelperList;
use PrestaShop\PrestaShop\Adapter\Configuration;
use PrestaShop\PrestaShop\Adapter\LegacyContext;
use PrestaShop\PrestaShop\Core\Hook\HookDispatcherInterface;
use PrestaShopBundle\Bridge\AdminController\FrameworkBridgeControllerInterface;
use PrestaShopBundle\Bridge\Exception\FailedToFetchListRecordsException;
use PrestaShopBundle\Bridge\Helper\Listing\FilterPrefix;
use PrestaShopBundle\Bridge\Helper\Listing\HelperListConfiguration;
use PrestaShopBundle\Bridge\Smarty\BreadcrumbsAndTitleConfigurator;
use PrestaShopBundle\Service\DataProvider\UserProvider;
use PrestaShopException;
use Shop;
use Tools;
use Validate;

/**
 * Acts as a bridge between symfony controller and the legacy HelperList to allow rendering the list
 *
 * @see HelperList
 * @see FrameworkBridgeControllerInterface
 */
class HelperListBridge
{
    /**
     * @var Context
     */
    private $context;

    /**
     * @var UserProvider
     */
    private $userProvider;

    /**
     * @var HookDispatcherInterface
     */
    private $hookDispatcher;

    /**
     * @var Configuration
     */
    private $configuration;

    /**
     * @var BreadcrumbsAndTitleConfigurator
     */
    private $breadcrumbsAndTitleConfigurator;

    /**
     * @param LegacyContext $legacyContext
     * @param UserProvider $userProvider
     * @param HookDispatcherInterface $hookDispatcher
     * @param Configuration $configuration
     * @param BreadcrumbsAndTitleConfigurator $breadcrumbsAndTitleConfigurator
     */
    public function __construct(
        LegacyContext $legacyContext,
        UserProvider $userProvider,
        HookDispatcherInterface $hookDispatcher,
        Configuration $configuration,
        BreadcrumbsAndTitleConfigurator $breadcrumbsAndTitleConfigurator
    ) {
        $this->context = $legacyContext->getContext();
        $this->userProvider = $userProvider;
        $this->hookDispatcher = $hookDispatcher;
        $this->configuration = $configuration;
        $this->breadcrumbsAndTitleConfigurator = $breadcrumbsAndTitleConfigurator;
    }

    /**
     * Generate the html for list using HelperList class
     *
     * @param HelperListConfiguration $helperListConfiguration
     *
     * @return string|null
     */
    public function generateList(
        HelperListConfiguration $helperListConfiguration
    ): ?string {
        if (!($helperListConfiguration->fieldsList && is_array($helperListConfiguration->fieldsList))) {
            return null;
        }

        return $this
            ->buildHelperList($helperListConfiguration)
            ->generateList($helperListConfiguration->list, $helperListConfiguration->fieldsList)
        ;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param int $idLang
     *
     * @return string
     */
    protected function generateListQuery(
        HelperListConfiguration $helperListConfiguration,
        int $idLang
    ): string {
        $this->hookDispatcher->dispatchWithParameters('action' . $helperListConfiguration->getLegacyControllerName() . 'ListingFieldsModifier', [
            'select' => &$helperListConfiguration->select,
            'join' => &$helperListConfiguration->join,
            'where' => &$helperListConfiguration->where,
            'group_by' => &$helperListConfiguration->group,
            'order_by' => &$helperListConfiguration->orderBy,
            'order_way' => &$helperListConfiguration->orderWay,
            'fields' => &$helperListConfiguration->fieldsList,
        ]);

        $tableName = $helperListConfiguration->getTableName();

        if (!Validate::isTableOrIdentifier($tableName)) {
            throw new PrestaShopException(sprintf('Table name %s is invalid:', $tableName));
        }

        $limit = $this->checkSqlLimit($helperListConfiguration);
        $listId = $helperListConfiguration->getListId();

        /* Determine offset from current page */
        $start = 0;
        if ((int) Tools::getValue('submitFilter' . $listId)) {
            $start = ((int) Tools::getValue('submitFilter' . $listId) - 1) * $limit;
        } elseif (
            isset($this->context->cookie->{$listId . '_start'})
            && Tools::isSubmit('export' . $tableName)
        ) {
            $start = $this->context->cookie->{$listId . '_start'};
        }

        // Either save or reset the offset in the cookie
        if ($start) {
            $this->context->cookie->{$listId . '_start'} = $start;
        } elseif (isset($this->context->cookie->{$listId . '_start'})) {
            unset($this->context->cookie->{$listId . '_start'});
        }

        // Add SQL shop restriction
        $select_shop = '';
        if ($helperListConfiguration->getShopLinkType()) {
            $select_shop = ', shop.name as shop_name ';
        }

        if ($helperListConfiguration->getMultiShopContext() && Shop::isTableAssociated($tableName) && !empty($helperListConfiguration->getObjectModelClassName())) {
            if (Shop::getContext() != Shop::CONTEXT_ALL || !$this->userProvider->getUser()->getData()->isSuperAdmin()) {
                $idKey = $helperListConfiguration->getIdentifierKey();
                $helperListConfiguration->where .= ' AND EXISTS (
                        SELECT 1
                        FROM `' . _DB_PREFIX_ . $tableName . '_shop` sa
                        WHERE a.`' . bqSQL($idKey) . '` = sa.`' . bqSQL($idKey) . '`
                         AND sa.id_shop IN (' . implode(', ', Shop::getContextListShopID()) . ')
                    )';
            }
        }

        $fromClause = $this->getFromClause($helperListConfiguration);
        $joinClause = $this->getJoinClause($helperListConfiguration, $idLang);
        $whereClause = $this->getWhereClause($helperListConfiguration);
        $orderByClause = $this->getOrderByClause($helperListConfiguration, $helperListConfiguration->orderBy, $helperListConfiguration->orderWay);

        $shouldLimitSqlResults = $this->shouldLimitSqlResults($limit);

        do {
            $listSql = '';

            if ($helperListConfiguration->isExplicitSelect()) {
                foreach ($helperListConfiguration->fieldsList as $key => $array_value) {
                    if (preg_match('/[\s]`?' . preg_quote($key, '/') . '`?\s*,/', $helperListConfiguration->select)) {
                        continue;
                    }

                    if (isset($array_value['filter_key'])) {
                        $listSql .= str_replace('!', '.`', $array_value['filter_key']) . '` AS `' . $key . '`, ';
                    } elseif ($key == 'id_' . $tableName) {
                        $listSql .= 'a.`' . bqSQL($key) . '`, ';
                    } elseif ($key != 'image' && !preg_match('/' . preg_quote($key, '/') . '/i', $helperListConfiguration->select)) {
                        $listSql .= '`' . bqSQL($key) . '`, ';
                    }
                }
                $listSql = rtrim(trim($listSql), ',');
            } else {
                $listSql .= ($helperListConfiguration->autoJoinLanguageTable() ? 'b.*,' : '') . ' a.*';
            }

            $listSql .= "\n" . (!empty($helperListConfiguration->select) ? ', ' . rtrim($helperListConfiguration->select, ', ') : '') . $select_shop;

            $limitClause = ' ' . (($shouldLimitSqlResults) ? ' LIMIT ' . (int) $start . ', ' . (int) $limit : '');

            if ($helperListConfiguration->useFoundRows()) {
                $listSql = 'SELECT SQL_CALC_FOUND_ROWS ' .
                    $listSql .
                    $fromClause .
                    $joinClause .
                    $whereClause .
                    $orderByClause .
                    $limitClause;

                $list_count = 'SELECT FOUND_ROWS() AS `' . _DB_PREFIX_ . $tableName . '`';
            } else {
                $listSql = 'SELECT ' .
                    $listSql .
                    $fromClause .
                    $joinClause .
                    $whereClause .
                    $orderByClause .
                    $limitClause;

                $list_count = 'SELECT COUNT(*) AS `' . _DB_PREFIX_ . $tableName . '` ' .
                    $fromClause .
                    $joinClause .
                    $whereClause;
            }

            try {
                $records = Db::getInstance()->executeS($listSql, true, false);
            } catch (Exception $e) {
                throw new FailedToFetchListRecordsException(
                    'Failed to fetch list records from database.',
                    0,
                    $e
                );
            }

            if (!is_array($records)) {
                throw new FailedToFetchListRecordsException(sprintf(
                    'Fetched list records expected to be array. Got %s',
                    var_export($records, true)
                ));
            }

            $helperListConfiguration->list = $records;
            $helperListConfiguration->listTotal = (int) Db::getInstance()->getValue($list_count, false);

            if ($shouldLimitSqlResults) {
                $start = (int) $start - (int) $limit;
                if ($start < 0) {
                    break;
                }
            } else {
                break;
            }
        } while (empty($helperListConfiguration->list));

        $this->hookDispatcher->dispatchWithParameters('action' . $helperListConfiguration->getLegacyControllerName() . 'ListingResultsModifier', [
            'list' => &$helperListConfiguration->list,
            'list_total' => &$helperListConfiguration->listTotal,
        ]);

        return $listSql;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param string|null $limit
     *
     * @return int
     */
    private function checkSqlLimit(HelperListConfiguration $helperListConfiguration, ?string $limit = null): int
    {
        $listId = $helperListConfiguration->getListId();

        if (empty($limit)) {
            if (
                isset($this->context->cookie->{$listId . '_pagination'}) &&
                $this->context->cookie->{$listId . '_pagination'}
            ) {
                $limit = $this->context->cookie->{$listId . '_pagination'};
            } else {
                $limit = $helperListConfiguration->getDefaultPaginationLimit();
            }
        }

        $limit = (int) Tools::getValue($listId . '_pagination', $limit);
        if (in_array($limit, $helperListConfiguration->getPaginationLimits()) && $limit != $helperListConfiguration->getDefaultPaginationLimit()) {
            $this->context->cookie->{$listId . '_pagination'} = $limit;
        } else {
            unset($this->context->cookie->{$listId . '_pagination'});
        }

        if (!is_numeric($limit)) {
            throw new PrestaShopException('Invalid limit. It should be a numeric.');
        }

        return $limit;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     *
     * @return string
     */
    private function getFromClause(HelperListConfiguration $helperListConfiguration)
    {
        $tableName = $helperListConfiguration->getTableName();
        // for some reason Order object model db table name is plural, so it is handled here (all other tables matches object model singular name)
        $sqlTable = $tableName == 'order' ? 'orders' : $tableName;

        return "\n" . 'FROM `' . _DB_PREFIX_ . $sqlTable . '` a ';
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param int $idLang
     * @param int|bool $idLangShop
     *
     * @return string
     */
    private function getJoinClause(HelperListConfiguration $helperListConfiguration, $idLang, $idLangShop = false)
    {
        $shopJoinClause = '';
        if ($shopLinkType = $helperListConfiguration->getShopLinkType()) {
            $shopJoinClause = ' LEFT JOIN `' . _DB_PREFIX_ . bqSQL($shopLinkType) . '` shop
                ON a.`id_' . bqSQL($shopLinkType) . '` = shop.`id_' . bqSQL($shopLinkType) . '`';
        }

        return "\n" . $this->getLanguageJoinClause($helperListConfiguration, $idLang, $idLangShop) .
            "\n" . $helperListConfiguration->join . ' ' .
            "\n" . $shopJoinClause;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param int $idLang
     * @param int $idLangShop
     *
     * @return string
     */
    private function getLanguageJoinClause(HelperListConfiguration $helperListConfiguration, $idLang, $idLangShop)
    {
        $languageJoinClause = '';
        if ($helperListConfiguration->autoJoinLanguageTable()) {
            $languageJoinClause = 'LEFT JOIN `' . _DB_PREFIX_ . bqSQL($helperListConfiguration->getTableName()) . '_lang` b
                ON (b.`' . bqSQL($helperListConfiguration->getIdentifierKey()) . '` = a.`' . bqSQL($helperListConfiguration->getIdentifierKey()) . '` AND b.`id_lang` = ' . (int) $idLang;

            if ($idLangShop) {
                if (!Shop::isFeatureActive()) {
                    $languageJoinClause .= ' AND b.`id_shop` = ' . (int) $this->configuration->get('PS_SHOP_DEFAULT');
                } elseif (Shop::getContext() == Shop::CONTEXT_SHOP) {
                    $languageJoinClause .= ' AND b.`id_shop` = ' . (int) $idLangShop;
                } else {
                    $languageJoinClause .= ' AND b.`id_shop` = a.id_shop_default';
                }
            }
            $languageJoinClause .= ')';
        }

        return $languageJoinClause;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     *
     * @return string
     */
    private function getWhereClause(HelperListConfiguration $helperListConfiguration): string
    {
        $whereShop = '';
        if ($helperListConfiguration->getShopLinkType()) {
            $whereShop = Shop::addSqlRestriction($helperListConfiguration->isShopShareData(), 'a');
        }

        return ' WHERE 1 ' . $helperListConfiguration->where . ' ' .
            ($helperListConfiguration->isDeleted() ? 'AND a.`deleted` = 0 ' : '') .
            $helperListConfiguration->filter . $whereShop . "\n" .
            $helperListConfiguration->group . ' ' . "\n" .
            $this->getHavingClause($helperListConfiguration);
    }

    /**
     * @return string
     */
    private function getHavingClause(HelperListConfiguration $helperListConfiguration): string
    {
        $havingClause = '';
        if (!$helperListConfiguration->filterHaving && !$helperListConfiguration->having) {
            return $havingClause;
        }

        $havingClause = ' HAVING ';
        if ($helperListConfiguration->filterHaving) {
            $havingClause .= ltrim($helperListConfiguration->filterHaving, ' AND ');
        }

        if ($helperListConfiguration->having) {
            $havingClause .= $helperListConfiguration->having . ' ';
        }

        return $havingClause;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param string $orderBy
     * @param string $orderDirection
     *
     * @return string
     */
    private function getOrderByClause(HelperListConfiguration $helperListConfiguration, $orderBy, $orderDirection)
    {
        $helperListConfiguration->orderBy = $this->checkOrderBy($helperListConfiguration, $orderBy);
        $helperListConfiguration->orderWay = $this->checkOrderDirection($helperListConfiguration, $orderDirection);

        return ' ORDER BY '
            . ((str_replace('`', '', $helperListConfiguration->orderBy) == $helperListConfiguration->getIdentifierKey()) ? 'a.' : '')
            . $helperListConfiguration->orderBy
            . ' '
            . $helperListConfiguration->orderWay;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param string $orderBy
     *
     * @return false|string
     */
    private function checkOrderBy(HelperListConfiguration $helperListConfiguration, $orderBy)
    {
        if (empty($orderBy)) {
            $prefix = FilterPrefix::getByClassName($helperListConfiguration->getLegacyControllerName());
            $listId = $helperListConfiguration->getListId();

            if ($this->context->cookie->{$prefix . $listId . 'Orderby'}) {
                $orderBy = $this->context->cookie->{$prefix . $listId . 'Orderby'};
            } elseif ($helperListConfiguration->orderBy) {
                $orderBy = $helperListConfiguration->orderBy;
            } else {
                $orderBy = $helperListConfiguration->getDefaultOrderBy();
            }
        }

        /* Check params validity */
        if (!Validate::isOrderBy($orderBy)) {
            throw new PrestaShopException('Invalid "order by" clause.');
        }

        if (!isset($helperListConfiguration->fieldsList[$orderBy]['order_key']) && isset($helperListConfiguration->fieldsList[$orderBy]['filter_key'])) {
            $helperListConfiguration->fieldsList[$orderBy]['order_key'] = $helperListConfiguration->fieldsList[$orderBy]['filter_key'];
        }

        if (isset($helperListConfiguration->fieldsList[$orderBy]['order_key'])) {
            $orderBy = $helperListConfiguration->fieldsList[$orderBy]['order_key'];
        }

        if (preg_match('/[.!]/', $orderBy)) {
            $orderBySplit = preg_split('/[.!]/', $orderBy);
            $orderBy = bqSQL($orderBySplit[0]) . '.`' . bqSQL($orderBySplit[1]) . '`';
        } elseif ($orderBy) {
            $orderBy = bqSQL($orderBy);
        }

        return $orderBy;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     * @param string $orderDirection
     *
     * @return mixed|string
     */
    private function checkOrderDirection(HelperListConfiguration $helperListConfiguration, $orderDirection)
    {
        $prefix = FilterPrefix::getByClassName($helperListConfiguration->getLegacyControllerName());
        if (empty($orderDirection)) {
            $listId = $helperListConfiguration->getListId();
            if ($this->context->cookie->{$prefix . $listId . 'Orderway'}) {
                $orderDirection = $this->context->cookie->{$prefix . $listId . 'Orderway'};
            } elseif ($helperListConfiguration->orderWay) {
                $orderDirection = $helperListConfiguration->orderWay;
            } else {
                $orderDirection = $helperListConfiguration->getDefaultOrderWay();
            }
        }

        if (!Validate::isOrderWay($orderDirection)) {
            throw new PrestaShopException('Invalid order direction.');
        }

        return pSQL(Tools::strtoupper($orderDirection));
    }

    /**
     * @param int $limit
     *
     * @return bool
     */
    private function shouldLimitSqlResults($limit): bool
    {
        return $limit !== false;
    }

    /**
     * @param HelperListConfiguration $helperListConfiguration
     *
     * @return HelperList
     */
    private function buildHelperList(
        HelperListConfiguration $helperListConfiguration
    ): HelperList {
        $helper = new HelperList();
        $helper->sql = $this->generateListQuery($helperListConfiguration, $this->context->language->id);
        $breadcrumbs = $this->breadcrumbsAndTitleConfigurator->getBreadcrumbs($helperListConfiguration->getTabId());
        $helper->title = $breadcrumbs['tab']['name'] ?? '';
        $helper->toolbar_btn = $helperListConfiguration->getToolbarActions();
        $helper->actions = $helperListConfiguration->getRowActions();
        $helper->bulk_actions = $helperListConfiguration->getBulkActions();
        $helper->show_toolbar = true;
        $helper->currentIndex = $helperListConfiguration->getLegacyCurrentIndex();
        $helper->table = $helperListConfiguration->getTableName();
        $helper->orderBy = $helperListConfiguration->orderBy;
        $helper->orderWay = $helperListConfiguration->orderWay;
        $helper->listTotal = $helperListConfiguration->listTotal;
        $helper->identifier = $helperListConfiguration->getIdentifierKey();
        $helper->token = $helperListConfiguration->getToken();
        $helper->position_identifier = $helperListConfiguration->getPositionIdentifierKey();
        $helper->controller_name = $helperListConfiguration->getLegacyControllerName();
        $helper->list_id = $helperListConfiguration->getListId();
        $helper->bootstrap = $helperListConfiguration->isBootstrap();
        $helper->_default_pagination = $helperListConfiguration->getDefaultPaginationLimit();
        $helper->_pagination = $helperListConfiguration->getPaginationLimits();
        $helper->tpl_delete_link_vars = $helperListConfiguration->getDeleteLinkVars();
        $helper->frameworkIndexUrl = $helperListConfiguration->getIndexUrl();

        return $helper;
    }
}