772 lines
30 KiB
PHP
772 lines
30 KiB
PHP
<?php
|
|
if (!defined('_PS_VERSION_')) {
|
|
exit;
|
|
}
|
|
|
|
use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever;
|
|
use PrestaShop\PrestaShop\Adapter\Product\PriceFormatter;
|
|
use PrestaShop\PrestaShop\Adapter\Product\ProductColorsRetriever;
|
|
use PrestaShop\PrestaShop\Core\Product\ProductListingPresenter;
|
|
|
|
class Pp_Carousel extends Module
|
|
{
|
|
public function __construct()
|
|
{
|
|
$this->name = 'pp_carousel';
|
|
$this->tab = 'front_office_features';
|
|
$this->version = '1.0.0';
|
|
$this->author = 'Project-Pro';
|
|
$this->author_uri = 'https://www.project-pro.pl';
|
|
$this->need_instance = 0;
|
|
$this->bootstrap = true;
|
|
|
|
parent::__construct();
|
|
|
|
$this->displayName = $this->l('Project-Pro Karuzela Produktów');
|
|
$this->description = $this->l('Wyświetla konfigurowalne karuzele produktów w dowolnych hookach.');
|
|
$this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_];
|
|
}
|
|
|
|
public function install()
|
|
{
|
|
return $this->executeSqlFile('install')
|
|
&& parent::install()
|
|
&& $this->registerHook('displayHeader')
|
|
&& $this->registerHook('displayHome')
|
|
&& $this->registerHook('displayFooterBefore')
|
|
&& $this->registerHook('displayTopColumn')
|
|
&& $this->registerHook('displayLeftColumn')
|
|
&& $this->registerHook('displayRightColumn')
|
|
&& $this->registerHook('displayFooter');
|
|
}
|
|
|
|
public function uninstall()
|
|
{
|
|
return $this->executeSqlFile('uninstall') && parent::uninstall();
|
|
}
|
|
|
|
private function executeSqlFile($filename)
|
|
{
|
|
$path = dirname(__FILE__) . '/sql/' . $filename . '.sql';
|
|
if (!file_exists($path)) {
|
|
return false;
|
|
}
|
|
|
|
$sql = file_get_contents($path);
|
|
$sql = str_replace('PREFIX_', _DB_PREFIX_, $sql);
|
|
$sql = str_replace('ENGINE_TYPE', _MYSQL_ENGINE_, $sql);
|
|
|
|
$queries = preg_split('/;\s*[\r\n]+/', $sql);
|
|
foreach ($queries as $query) {
|
|
$query = trim($query);
|
|
if (!empty($query)) {
|
|
if (!Db::getInstance()->execute($query)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ─── ADMIN PANEL ────────────────────────────────────────────
|
|
|
|
public function getContent()
|
|
{
|
|
$output = '';
|
|
|
|
if (Tools::isSubmit('deletepp_carousel')) {
|
|
$output .= $this->deleteCarousel((int) Tools::getValue('id_carousel'));
|
|
}
|
|
|
|
if (Tools::isSubmit('statuspp_carousel')) {
|
|
$output .= $this->toggleCarouselStatus((int) Tools::getValue('id_carousel'));
|
|
}
|
|
|
|
if (Tools::isSubmit('submitPpCarousel')) {
|
|
$output .= $this->saveCarousel();
|
|
}
|
|
|
|
if (Tools::isSubmit('addpp_carousel') || Tools::isSubmit('updatepp_carousel') || Tools::getValue('id_carousel')) {
|
|
return $output . $this->renderForm((int) Tools::getValue('id_carousel'));
|
|
}
|
|
|
|
return $output . $this->renderList();
|
|
}
|
|
|
|
private function renderList()
|
|
{
|
|
$fieldsList = [
|
|
'id_carousel' => ['title' => 'ID', 'align' => 'center', 'class' => 'fixed-width-xs'],
|
|
'title' => ['title' => $this->l('Tytuł')],
|
|
'hook_name' => ['title' => $this->l('Hook')],
|
|
'source_type' => ['title' => $this->l('Źródło')],
|
|
'limit_products' => ['title' => $this->l('Limit'), 'align' => 'center', 'class' => 'fixed-width-xs'],
|
|
'active' => ['title' => $this->l('Aktywna'), 'active' => 'status', 'align' => 'center', 'class' => 'fixed-width-sm', 'type' => 'bool'],
|
|
];
|
|
|
|
$helper = new HelperList();
|
|
$helper->shopLinkType = '';
|
|
$helper->simple_header = false;
|
|
$helper->actions = ['edit', 'delete'];
|
|
$helper->identifier = 'id_carousel';
|
|
$helper->show_toolbar = true;
|
|
$helper->title = $this->l('Karuzele produktów');
|
|
$helper->table = $this->name;
|
|
$helper->token = Tools::getAdminTokenLite('AdminModules');
|
|
$helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name;
|
|
|
|
$helper->toolbar_btn['new'] = [
|
|
'href' => AdminController::$currentIndex . '&configure=' . $this->name . '&add' . $this->name . '&token=' . Tools::getAdminTokenLite('AdminModules'),
|
|
'desc' => $this->l('Dodaj karuzelę'),
|
|
];
|
|
|
|
return $helper->generateList($this->getCarouselList(), $fieldsList);
|
|
}
|
|
|
|
private function getCarouselList()
|
|
{
|
|
$idLang = (int) $this->context->language->id;
|
|
|
|
$sql = 'SELECT c.*, cl.title
|
|
FROM `' . _DB_PREFIX_ . 'pp_carousel` c
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'pp_carousel_lang` cl
|
|
ON c.id_carousel = cl.id_carousel AND cl.id_lang = ' . $idLang . '
|
|
WHERE c.id_shop = ' . (int) $this->context->shop->id . '
|
|
ORDER BY c.position ASC, c.id_carousel ASC';
|
|
|
|
return Db::getInstance()->executeS($sql) ?: [];
|
|
}
|
|
|
|
private function renderForm($idCarousel = 0)
|
|
{
|
|
$carousel = $idCarousel ? $this->getCarousel($idCarousel) : [];
|
|
$defaultLang = (int) Configuration::get('PS_LANG_DEFAULT');
|
|
$langs = Language::getLanguages(false);
|
|
$categories = $this->flattenCategories(Category::getCategories($defaultLang, true, false));
|
|
|
|
$hookOptions = $this->getAvailableHooks();
|
|
$predefinedHookIds = array_column($hookOptions, 'id');
|
|
|
|
// Detect if saved hook_name is a custom hook
|
|
$savedHookName = isset($carousel['hook_name']) ? $carousel['hook_name'] : 'displayHome';
|
|
$isCustomHook = !empty($savedHookName) && !in_array($savedHookName, $predefinedHookIds);
|
|
|
|
$sourceOptions = [
|
|
['id' => 'new', 'name' => $this->l('Nowości')],
|
|
['id' => 'bestseller', 'name' => $this->l('Bestsellery')],
|
|
['id' => 'category', 'name' => $this->l('Produkty z kategorii')],
|
|
['id' => 'manual', 'name' => $this->l('Ręczne ID produktów')],
|
|
];
|
|
|
|
$fieldsForm = [
|
|
'form' => [
|
|
'legend' => [
|
|
'title' => $idCarousel ? $this->l('Edytuj karuzelę') : $this->l('Dodaj karuzelę'),
|
|
'icon' => 'icon-cogs',
|
|
],
|
|
'input' => [
|
|
[
|
|
'type' => 'hidden',
|
|
'name' => 'id_carousel',
|
|
],
|
|
[
|
|
'type' => 'select',
|
|
'label' => $this->l('Hook (miejsce wyświetlania)'),
|
|
'name' => 'hook_name',
|
|
'options' => ['query' => $hookOptions, 'id' => 'id', 'name' => 'name'],
|
|
'desc' => $this->l('Wybierz hook lub wpisz niestandardowy poniżej.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Niestandardowy hook'),
|
|
'name' => 'custom_hook',
|
|
'desc' => $this->l('Jeśli wypełnione, zostanie użyte zamiast wybranego powyżej. Hook zostanie utworzony automatycznie.'),
|
|
],
|
|
[
|
|
'type' => 'select',
|
|
'label' => $this->l('Źródło produktów'),
|
|
'name' => 'source_type',
|
|
'options' => ['query' => $sourceOptions, 'id' => 'id', 'name' => 'name'],
|
|
'id' => 'source_type_select',
|
|
],
|
|
[
|
|
'type' => 'select',
|
|
'label' => $this->l('Kategoria'),
|
|
'name' => 'id_category',
|
|
'options' => ['query' => $categories, 'id' => 'id', 'name' => 'name'],
|
|
'desc' => $this->l('Widoczne gdy źródło = "Produkty z kategorii".'),
|
|
'form_group_class' => 'pp-field-category',
|
|
],
|
|
[
|
|
'type' => 'textarea',
|
|
'label' => $this->l('ID produktów (ręczne)'),
|
|
'name' => 'product_ids',
|
|
'desc' => $this->l('ID produktów rozdzielone przecinkami, np. 12,45,67. Widoczne gdy źródło = "Ręczne ID".'),
|
|
'form_group_class' => 'pp-field-manual',
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Limit produktów'),
|
|
'name' => 'limit_products',
|
|
'class' => 'fixed-width-sm',
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Tytuł'),
|
|
'name' => 'title',
|
|
'lang' => true,
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Podtytuł'),
|
|
'name' => 'subtitle',
|
|
'lang' => true,
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Tekst przycisku'),
|
|
'name' => 'button_label',
|
|
'lang' => true,
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('URL przycisku'),
|
|
'name' => 'button_url',
|
|
'desc' => $this->l('Pozostaw puste, aby linkować do kategorii automatycznie.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Sufiks ceny'),
|
|
'name' => 'price_suffix',
|
|
'lang' => true,
|
|
'desc' => $this->l('Np. /m²'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Pozycja'),
|
|
'name' => 'position',
|
|
'class' => 'fixed-width-sm',
|
|
],
|
|
[
|
|
'type' => 'switch',
|
|
'label' => $this->l('Aktywna'),
|
|
'name' => 'active',
|
|
'values' => [
|
|
['id' => 'active_on', 'value' => 1, 'label' => $this->l('Tak')],
|
|
['id' => 'active_off', 'value' => 0, 'label' => $this->l('Nie')],
|
|
],
|
|
],
|
|
],
|
|
'submit' => [
|
|
'title' => $this->l('Zapisz'),
|
|
'name' => 'submitPpCarousel',
|
|
],
|
|
],
|
|
];
|
|
|
|
$helper = new HelperForm();
|
|
$helper->show_toolbar = false;
|
|
$helper->table = $this->table;
|
|
$helper->module = $this;
|
|
$helper->default_form_language = $defaultLang;
|
|
$helper->allow_employee_form_lang = (int) Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG');
|
|
$helper->identifier = 'id_carousel';
|
|
$helper->submit_action = 'submitPpCarousel';
|
|
$helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name;
|
|
$helper->token = Tools::getAdminTokenLite('AdminModules');
|
|
$helper->languages = $this->context->controller->getLanguages();
|
|
$helper->id_language = (int) $this->context->language->id;
|
|
|
|
// Fill form values
|
|
$helper->fields_value['id_carousel'] = $idCarousel;
|
|
$helper->fields_value['hook_name'] = $isCustomHook ? 'displayHome' : $savedHookName;
|
|
$helper->fields_value['custom_hook'] = $isCustomHook ? $savedHookName : '';
|
|
$helper->fields_value['source_type'] = isset($carousel['source_type']) ? $carousel['source_type'] : 'new';
|
|
$helper->fields_value['id_category'] = isset($carousel['id_category']) ? (int) $carousel['id_category'] : 0;
|
|
$helper->fields_value['product_ids'] = isset($carousel['product_ids']) ? $carousel['product_ids'] : '';
|
|
$helper->fields_value['limit_products'] = isset($carousel['limit_products']) ? (int) $carousel['limit_products'] : 12;
|
|
$helper->fields_value['button_url'] = isset($carousel['button_url']) ? $carousel['button_url'] : '';
|
|
$helper->fields_value['position'] = isset($carousel['position']) ? (int) $carousel['position'] : 0;
|
|
$helper->fields_value['active'] = isset($carousel['active']) ? (int) $carousel['active'] : 1;
|
|
|
|
foreach ($langs as $lang) {
|
|
$id = (int) $lang['id_lang'];
|
|
$langData = $idCarousel ? $this->getCarouselLang($idCarousel, $id) : [];
|
|
$helper->fields_value['title'][$id] = isset($langData['title']) ? $langData['title'] : '';
|
|
$helper->fields_value['subtitle'][$id] = isset($langData['subtitle']) ? $langData['subtitle'] : '';
|
|
$helper->fields_value['button_label'][$id] = isset($langData['button_label']) ? $langData['button_label'] : '';
|
|
$helper->fields_value['price_suffix'][$id] = isset($langData['price_suffix']) ? $langData['price_suffix'] : '';
|
|
}
|
|
|
|
$formHtml = $helper->generateForm([$fieldsForm]);
|
|
|
|
// Inject JS for conditional field visibility
|
|
$formHtml .= $this->getAdminFormJs();
|
|
|
|
return $formHtml;
|
|
}
|
|
|
|
private function getAdminFormJs()
|
|
{
|
|
return '
|
|
<script>
|
|
(function() {
|
|
function toggleSourceFields() {
|
|
var val = document.querySelector("[name=source_type]").value;
|
|
var catRow = document.querySelector(".pp-field-category");
|
|
var manualRow = document.querySelector(".pp-field-manual");
|
|
if (catRow) catRow.style.display = (val === "category") ? "" : "none";
|
|
if (manualRow) manualRow.style.display = (val === "manual") ? "" : "none";
|
|
}
|
|
var sel = document.querySelector("[name=source_type]");
|
|
if (sel) {
|
|
sel.addEventListener("change", toggleSourceFields);
|
|
toggleSourceFields();
|
|
}
|
|
})();
|
|
</script>';
|
|
}
|
|
|
|
private function saveCarousel()
|
|
{
|
|
$idCarousel = (int) Tools::getValue('id_carousel');
|
|
$hookName = trim(Tools::getValue('custom_hook'));
|
|
if (empty($hookName)) {
|
|
$hookName = trim(Tools::getValue('hook_name'));
|
|
}
|
|
if (empty($hookName)) {
|
|
$hookName = 'displayHome';
|
|
}
|
|
|
|
$sourceType = Tools::getValue('source_type');
|
|
$idCategory = (int) Tools::getValue('id_category');
|
|
$productIds = trim(Tools::getValue('product_ids'));
|
|
$limitProducts = (int) Tools::getValue('limit_products');
|
|
$buttonUrl = trim(Tools::getValue('button_url'));
|
|
$position = (int) Tools::getValue('position');
|
|
$active = (int) Tools::getValue('active');
|
|
|
|
if ($limitProducts <= 0) {
|
|
$limitProducts = 12;
|
|
}
|
|
|
|
// Sanitize manual product IDs
|
|
if ($sourceType === 'manual' && $productIds) {
|
|
$ids = array_filter(array_map('intval', explode(',', $productIds)));
|
|
$productIds = implode(',', $ids);
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$db = Db::getInstance();
|
|
|
|
if ($idCarousel > 0) {
|
|
$db->update('pp_carousel', [
|
|
'hook_name' => pSQL($hookName),
|
|
'source_type' => pSQL($sourceType),
|
|
'id_category' => $idCategory,
|
|
'product_ids' => pSQL($productIds),
|
|
'limit_products' => $limitProducts,
|
|
'button_url' => pSQL($buttonUrl),
|
|
'position' => $position,
|
|
'active' => $active,
|
|
'date_upd' => $now,
|
|
], 'id_carousel = ' . $idCarousel);
|
|
} else {
|
|
$db->insert('pp_carousel', [
|
|
'hook_name' => pSQL($hookName),
|
|
'source_type' => pSQL($sourceType),
|
|
'id_category' => $idCategory,
|
|
'product_ids' => pSQL($productIds),
|
|
'limit_products' => $limitProducts,
|
|
'button_url' => pSQL($buttonUrl),
|
|
'position' => $position,
|
|
'active' => $active,
|
|
'id_shop' => (int) $this->context->shop->id,
|
|
'date_add' => $now,
|
|
'date_upd' => $now,
|
|
]);
|
|
$idCarousel = (int) $db->Insert_ID();
|
|
}
|
|
|
|
// Save lang fields
|
|
$langs = Language::getLanguages(false);
|
|
foreach ($langs as $lang) {
|
|
$id = (int) $lang['id_lang'];
|
|
$title = Tools::substr(trim(Tools::getValue('title_' . $id)), 0, 255);
|
|
$subtitle = Tools::substr(trim(Tools::getValue('subtitle_' . $id)), 0, 255);
|
|
$buttonLabel = Tools::substr(trim(Tools::getValue('button_label_' . $id)), 0, 255);
|
|
$priceSuffix = Tools::substr(trim(Tools::getValue('price_suffix_' . $id)), 0, 64);
|
|
|
|
$exists = $db->getValue(
|
|
'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'pp_carousel_lang`
|
|
WHERE id_carousel = ' . $idCarousel . ' AND id_lang = ' . $id
|
|
);
|
|
|
|
$langData = [
|
|
'title' => pSQL($title),
|
|
'subtitle' => pSQL($subtitle),
|
|
'button_label' => pSQL($buttonLabel),
|
|
'price_suffix' => pSQL($priceSuffix),
|
|
];
|
|
|
|
if ($exists) {
|
|
$db->update('pp_carousel_lang', $langData, 'id_carousel = ' . $idCarousel . ' AND id_lang = ' . $id);
|
|
} else {
|
|
$langData['id_carousel'] = $idCarousel;
|
|
$langData['id_lang'] = $id;
|
|
$db->insert('pp_carousel_lang', $langData);
|
|
}
|
|
}
|
|
|
|
// Register custom hook if needed
|
|
$this->ensureHookRegistered($hookName);
|
|
|
|
return $this->displayConfirmation($this->l('Karuzela została zapisana.'));
|
|
}
|
|
|
|
private function deleteCarousel($idCarousel)
|
|
{
|
|
if ($idCarousel <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$db = Db::getInstance();
|
|
$db->delete('pp_carousel_lang', 'id_carousel = ' . $idCarousel);
|
|
$db->delete('pp_carousel', 'id_carousel = ' . $idCarousel);
|
|
|
|
return $this->displayConfirmation($this->l('Karuzela została usunięta.'));
|
|
}
|
|
|
|
private function toggleCarouselStatus($idCarousel)
|
|
{
|
|
if ($idCarousel <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$current = (int) Db::getInstance()->getValue(
|
|
'SELECT active FROM `' . _DB_PREFIX_ . 'pp_carousel` WHERE id_carousel = ' . $idCarousel
|
|
);
|
|
|
|
Db::getInstance()->update('pp_carousel', [
|
|
'active' => $current ? 0 : 1,
|
|
], 'id_carousel = ' . $idCarousel);
|
|
|
|
return $this->displayConfirmation($this->l('Status karuzeli został zmieniony.'));
|
|
}
|
|
|
|
private function getCarousel($idCarousel)
|
|
{
|
|
return Db::getInstance()->getRow(
|
|
'SELECT * FROM `' . _DB_PREFIX_ . 'pp_carousel` WHERE id_carousel = ' . (int) $idCarousel
|
|
);
|
|
}
|
|
|
|
private function getCarouselLang($idCarousel, $idLang)
|
|
{
|
|
return Db::getInstance()->getRow(
|
|
'SELECT * FROM `' . _DB_PREFIX_ . 'pp_carousel_lang`
|
|
WHERE id_carousel = ' . (int) $idCarousel . ' AND id_lang = ' . (int) $idLang
|
|
) ?: [];
|
|
}
|
|
|
|
private function getAvailableHooks()
|
|
{
|
|
$hooks = [
|
|
'displayHome', 'displayTopColumn', 'displayFooterBefore',
|
|
'displayFooter', 'displayLeftColumn', 'displayRightColumn',
|
|
'displayOrderConfirmation2', 'displayCrossSellingShoppingCart',
|
|
];
|
|
|
|
$options = [];
|
|
foreach ($hooks as $h) {
|
|
$options[] = ['id' => $h, 'name' => $h];
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
private function flattenCategories($tree, $depth = 0, &$out = [])
|
|
{
|
|
foreach ($tree as $node) {
|
|
if (!isset($node['id_category'], $node['name'])) {
|
|
continue;
|
|
}
|
|
$prefix = str_repeat('— ', max(0, $depth));
|
|
$out[] = [
|
|
'id' => (int) $node['id_category'],
|
|
'name' => $prefix . $node['name'],
|
|
];
|
|
if (!empty($node['children'])) {
|
|
$this->flattenCategories($node['children'], $depth + 1, $out);
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
private function ensureHookRegistered($hookName)
|
|
{
|
|
$idHook = (int) Hook::getIdByName($hookName);
|
|
if (!$idHook) {
|
|
Db::getInstance()->insert('hook', [
|
|
'name' => pSQL($hookName),
|
|
'title' => pSQL($hookName),
|
|
]);
|
|
}
|
|
if (!$this->isRegisteredInHook($hookName)) {
|
|
$this->registerHook($hookName);
|
|
}
|
|
|
|
// Clear PrestaShop hook cache
|
|
if (Cache::isStored('hook_module_list')) {
|
|
Cache::clean('hook_module_list');
|
|
}
|
|
}
|
|
|
|
public function isRegisteredInHook($hookName)
|
|
{
|
|
$idHook = (int) Hook::getIdByName($hookName);
|
|
if (!$idHook) {
|
|
return false;
|
|
}
|
|
$count = (int) Db::getInstance()->getValue(
|
|
'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'hook_module`
|
|
WHERE id_hook = ' . $idHook . ' AND id_module = ' . (int) $this->id
|
|
);
|
|
return $count > 0;
|
|
}
|
|
|
|
// ─── FRONT HOOKS ────────────────────────────────────────────
|
|
|
|
public function hookDisplayHeader()
|
|
{
|
|
$this->context->controller->registerStylesheet(
|
|
'pp_carousel_swiper_css',
|
|
'modules/' . $this->name . '/views/lib/swiper/swiper-bundle.min.css',
|
|
['media' => 'all', 'priority' => 150]
|
|
);
|
|
$this->context->controller->registerStylesheet(
|
|
'pp_carousel_css',
|
|
'modules/' . $this->name . '/views/css/pp_carousel.css',
|
|
['media' => 'all', 'priority' => 151]
|
|
);
|
|
$this->context->controller->registerJavascript(
|
|
'pp_carousel_swiper_js',
|
|
'modules/' . $this->name . '/views/lib/swiper/swiper-bundle.min.js',
|
|
['position' => 'bottom', 'priority' => 150]
|
|
);
|
|
$this->context->controller->registerJavascript(
|
|
'pp_carousel_js',
|
|
'modules/' . $this->name . '/views/js/pp_carousel.js',
|
|
['position' => 'bottom', 'priority' => 151]
|
|
);
|
|
}
|
|
|
|
public function hookDisplayHome($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayHome');
|
|
}
|
|
|
|
public function hookDisplayTopColumn($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayTopColumn');
|
|
}
|
|
|
|
public function hookDisplayFooterBefore($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayFooterBefore');
|
|
}
|
|
|
|
public function hookDisplayFooter($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayFooter');
|
|
}
|
|
|
|
public function hookDisplayLeftColumn($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayLeftColumn');
|
|
}
|
|
|
|
public function hookDisplayRightColumn($params)
|
|
{
|
|
return $this->renderCarouselsForHook('displayRightColumn');
|
|
}
|
|
|
|
/**
|
|
* Catch-all: render carousels for any hook not explicitly defined above.
|
|
*/
|
|
public function __call($method, $args)
|
|
{
|
|
if (strpos($method, 'hook') === 0) {
|
|
$hookName = lcfirst(substr($method, 4));
|
|
return $this->renderCarouselsForHook($hookName);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// ─── RENDERING ──────────────────────────────────────────────
|
|
|
|
private function renderCarouselsForHook($hookName)
|
|
{
|
|
$carousels = Db::getInstance()->executeS(
|
|
'SELECT c.*, cl.title, cl.subtitle, cl.button_label, cl.price_suffix
|
|
FROM `' . _DB_PREFIX_ . 'pp_carousel` c
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'pp_carousel_lang` cl
|
|
ON c.id_carousel = cl.id_carousel AND cl.id_lang = ' . (int) $this->context->language->id . '
|
|
WHERE c.hook_name = "' . pSQL($hookName) . '"
|
|
AND c.active = 1
|
|
AND c.id_shop = ' . (int) $this->context->shop->id . '
|
|
ORDER BY c.position ASC, c.id_carousel ASC'
|
|
);
|
|
|
|
if (empty($carousels)) {
|
|
return '';
|
|
}
|
|
|
|
$html = '';
|
|
foreach ($carousels as $carousel) {
|
|
$products = $this->getProductsByCarousel($carousel);
|
|
if (empty($products)) {
|
|
continue;
|
|
}
|
|
|
|
$buttonUrl = trim($carousel['button_url']);
|
|
if (empty($buttonUrl) && $carousel['source_type'] === 'category' && $carousel['id_category'] > 0) {
|
|
$cat = new Category((int) $carousel['id_category'], (int) $this->context->language->id);
|
|
if (Validate::isLoadedObject($cat)) {
|
|
$buttonUrl = $this->context->link->getCategoryLink($cat);
|
|
}
|
|
}
|
|
|
|
$this->context->smarty->assign([
|
|
'ppc_id' => (int) $carousel['id_carousel'],
|
|
'ppc_title' => $carousel['title'] ?: '',
|
|
'ppc_subtitle' => $carousel['subtitle'] ?: '',
|
|
'ppc_button_label' => $carousel['button_label'] ?: '',
|
|
'ppc_button_url' => $buttonUrl,
|
|
'ppc_price_suffix' => $carousel['price_suffix'] ?: '',
|
|
'ppc_products' => $products,
|
|
]);
|
|
|
|
$html .= $this->fetch('module:' . $this->name . '/views/templates/hook/pp_carousel.tpl');
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
// ─── PRODUCT SOURCES ────────────────────────────────────────
|
|
|
|
private function getProductsByCarousel($carousel)
|
|
{
|
|
$limit = (int) $carousel['limit_products'];
|
|
if ($limit <= 0) {
|
|
$limit = 12;
|
|
}
|
|
|
|
switch ($carousel['source_type']) {
|
|
case 'new':
|
|
return $this->getNewProducts($limit);
|
|
case 'bestseller':
|
|
return $this->getBestsellers($limit);
|
|
case 'category':
|
|
return $this->getCategoryProducts((int) $carousel['id_category'], $limit);
|
|
case 'manual':
|
|
return $this->getManualProducts($carousel['product_ids'], $limit);
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function getNewProducts($limit)
|
|
{
|
|
$idLang = (int) $this->context->language->id;
|
|
$raw = Product::getNewProducts($idLang, 0, $limit);
|
|
return is_array($raw) ? $this->presentProducts($raw) : [];
|
|
}
|
|
|
|
private function getBestsellers($limit)
|
|
{
|
|
$idLang = (int) $this->context->language->id;
|
|
$raw = ProductSale::getBestSales($idLang, 0, $limit);
|
|
return is_array($raw) ? $this->presentProducts($raw) : [];
|
|
}
|
|
|
|
private function getCategoryProducts($idCategory, $limit)
|
|
{
|
|
if ($idCategory <= 0) {
|
|
return [];
|
|
}
|
|
$idLang = (int) $this->context->language->id;
|
|
$category = new Category($idCategory, $idLang);
|
|
if (!Validate::isLoadedObject($category)) {
|
|
return [];
|
|
}
|
|
$raw = $category->getProducts($idLang, 1, $limit, 'position', 'asc');
|
|
return is_array($raw) ? $this->presentProducts($raw) : [];
|
|
}
|
|
|
|
private function getManualProducts($productIdsStr, $limit)
|
|
{
|
|
if (empty($productIdsStr)) {
|
|
return [];
|
|
}
|
|
|
|
$ids = array_filter(array_map('intval', explode(',', $productIdsStr)));
|
|
if (empty($ids)) {
|
|
return [];
|
|
}
|
|
|
|
$idLang = (int) $this->context->language->id;
|
|
$idShop = (int) $this->context->shop->id;
|
|
|
|
$sql = 'SELECT p.*, pl.`name`, pl.`description_short`, pl.`link_rewrite`,
|
|
cl.`name` AS category_default, cl.`link_rewrite` AS category_link_rewrite,
|
|
i.`id_image`, il.`legend`,
|
|
m.`name` AS manufacturer_name,
|
|
p.`id_category_default`
|
|
FROM `' . _DB_PREFIX_ . 'product` p
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl
|
|
ON p.id_product = pl.id_product AND pl.id_lang = ' . $idLang . ' AND pl.id_shop = ' . $idShop . '
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
|
|
ON p.id_category_default = cl.id_category AND cl.id_lang = ' . $idLang . ' AND cl.id_shop = ' . $idShop . '
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'image` i
|
|
ON p.id_product = i.id_product AND i.cover = 1
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il
|
|
ON i.id_image = il.id_image AND il.id_lang = ' . $idLang . '
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m
|
|
ON p.id_manufacturer = m.id_manufacturer
|
|
LEFT JOIN `' . _DB_PREFIX_ . 'product_shop` ps
|
|
ON p.id_product = ps.id_product AND ps.id_shop = ' . $idShop . '
|
|
WHERE p.id_product IN (' . implode(',', $ids) . ')
|
|
AND ps.active = 1
|
|
LIMIT ' . (int) $limit;
|
|
|
|
$raw = Db::getInstance()->executeS($sql);
|
|
return is_array($raw) ? $this->presentProducts($raw) : [];
|
|
}
|
|
|
|
private function presentProducts(array $rawProducts)
|
|
{
|
|
$assembler = new \ProductAssembler($this->context);
|
|
$presenterFactory = new \ProductPresenterFactory($this->context);
|
|
$presentationSettings = $presenterFactory->getPresentationSettings();
|
|
$presenter = $presenterFactory->getPresenter();
|
|
|
|
$products = [];
|
|
foreach ($rawProducts as $raw) {
|
|
try {
|
|
$assembled = $assembler->assembleProduct($raw);
|
|
$products[] = $presenter->present(
|
|
$presentationSettings,
|
|
$assembled,
|
|
$this->context->language
|
|
);
|
|
} catch (Exception $e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return $products;
|
|
}
|
|
}
|