- Created `pp_carousel.tpl` for rendering product carousel with Swiper integration. - Added `plan.md` detailing module architecture, database schema, and implementation steps. - Initialized log files for development and production environments.
8.6 KiB
Plan: Moduł Project-Pro Karuzela Produktów
Analiza screenshota
Karuzela wyświetla produkty jako karty z: dużym zdjęciem, etykietą kategorii (label w lewym górnym rogu), nazwą produktu, ceną z sufiksem /m2. Układ 3 kolumny na desktop, slider Swiper.
Kluczowe decyzje architektoniczne
Dlaczego tabela SQL zamiast Configuration?
Moduł wymaga dowolnej liczby karuzeli — Configuration przechowuje pary klucz-wartość, nie nadaje się do list rekordów o zmiennej długości. Dedykowana tabela pp_carousel to jedyne poprawne rozwiązanie.
Dlaczego dynamiczne hooki?
Użytkownik chce przypisywać karuzele do hooków, w tym niestandardowych. Moduł rejestruje się na displayHeader + catch-all actionDispatcher, a wyświetlanie realizuje poprzez Hook::exec() z warunkiem WHERE hook_name = ? w bazie. Niestandardowe hooki dodawane są przez Hook::getIdByName() / ręczne INSERT.
Struktura plików modułu
modules/pp_carousel/
├── pp_carousel.php # Główna klasa modułu
├── config.xml
├── logo.png
├── sql/
│ ├── install.sql # CREATE TABLE
│ └── uninstall.sql # DROP TABLE
├── views/
│ ├── css/
│ │ └── pp_carousel.css # Style karuzeli (front)
│ ├── js/
│ │ └── pp_carousel.js # Inicjalizacja Swiper (front)
│ ├── lib/
│ │ └── swiper/ # swiper-bundle.min.js/css (kopiujemy z project_pro_news)
│ └── templates/
│ ├── hook/
│ │ └── pp_carousel.tpl # Szablon frontu karuzeli
│ └── admin/
│ ├── list.tpl # Lista karuzeli w BO
│ └── form.tpl # Formularz dodawania/edycji karuzeli
Tabela bazy danych: ps_pp_carousel
| Kolumna | Typ | Opis |
|---|---|---|
id_carousel |
INT AUTO_INCREMENT PK | ID karuzeli |
hook_name |
VARCHAR(128) | Nazwa hooka (np. displayHome, custom) |
source_type |
ENUM('new','bestseller','category','manual') | Źródło produktów |
id_category |
INT DEFAULT 0 | ID kategorii (gdy source_type=category) |
product_ids |
TEXT | Ręczne ID produktów rozdzielone przecinkami |
limit_products |
INT DEFAULT 12 | Limit produktów |
title |
VARCHAR(255) | Tytuł karuzeli |
subtitle |
VARCHAR(255) | Podtytuł |
button_label |
VARCHAR(255) | Tekst przycisku |
button_url |
VARCHAR(512) | URL przycisku |
price_suffix |
VARCHAR(64) | Sufiks ceny (np. /m²) |
position |
INT DEFAULT 0 | Kolejność sortowania |
active |
TINYINT(1) DEFAULT 1 | Aktywna/nieaktywna |
id_shop |
INT DEFAULT 1 | Multishop |
date_add |
DATETIME | Data utworzenia |
date_upd |
DATETIME | Data modyfikacji |
Wielojęzyczność: pola
title,subtitle,button_label,price_suffix— dodatkowa tabelaps_pp_carousel_langz kolumnamiid_carousel,id_lang,title,subtitle,button_label,price_suffix.
Implementacja krok po kroku
Krok 1: Plik główny pp_carousel.php — szkielet
- Klasa
Pp_Carousel extends Module - Konstruktor: name=
pp_carousel, author=Project-Pro, version=1.0.0, displayName=Project-Pro Karuzela Produktów install(): wykonuje SQL, rejestruje hooki:displayHeader,displayHome,displayBackOfficeHeaderuninstall(): DROP tabeli, usunięcie konfiguracji- Metoda
getContent()→ panel admin z listą karuzeli + formularz CRUD
Krok 2: SQL install/uninstall
install.sql:
CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel` (
`id_carousel` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`hook_name` VARCHAR(128) NOT NULL DEFAULT 'displayHome',
`source_type` VARCHAR(20) NOT NULL DEFAULT 'new',
`id_category` INT(11) UNSIGNED NOT NULL DEFAULT 0,
`product_ids` TEXT,
`limit_products` INT(11) UNSIGNED NOT NULL DEFAULT 12,
`button_url` VARCHAR(512) DEFAULT '',
`position` INT(11) UNSIGNED NOT NULL DEFAULT 0,
`active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
`id_shop` INT(11) UNSIGNED NOT NULL DEFAULT 1,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id_carousel`),
KEY `hook_name` (`hook_name`),
KEY `active` (`active`)
) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel_lang` (
`id_carousel` INT(11) UNSIGNED NOT NULL,
`id_lang` INT(11) UNSIGNED NOT NULL,
`title` VARCHAR(255) DEFAULT '',
`subtitle` VARCHAR(255) DEFAULT '',
`button_label` VARCHAR(255) DEFAULT '',
`price_suffix` VARCHAR(64) DEFAULT '',
PRIMARY KEY (`id_carousel`, `id_lang`)
) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4;
Krok 3: Panel administracyjny (getContent())
Widok listy (domyślny):
- Tabela HelperList: ID | Tytuł | Hook | Źródło | Limit | Aktywna | Akcje (edytuj/usuń)
- Przycisk "Dodaj karuzelę"
Widok formularza (dodaj/edytuj):
- Hook — select z listą dostępnych hooków + pole tekstowe "Niestandardowy hook" (jeśli wpisany, tworzony jest nowy hook w
ps_hook) - Źródło produktów — select: Nowości / Bestsellery / Kategoria / Ręczne ID
- Kategoria — select z drzewem kategorii (widoczny gdy source_type=category)
- Ręczne ID produktów — textarea, oddzielone przecinkami (widoczny gdy source_type=manual)
- Limit produktów — input number
- Tytuł — input text (wielojęzyczny)
- Podtytuł — input text (wielojęzyczny)
- Tekst przycisku — input text (wielojęzyczny)
- URL przycisku — input text
- Sufiks ceny — input text (wielojęzyczny), np.
/m² - Aktywna — switch tak/nie
Krok 4: Pobieranie produktów — metoda getProductsByCarousel($carousel)
Switch po source_type:
new→Product::getNewProducts($idLang, 0, $limit)(wzorzec zps_newproducts)bestseller→ProductSale::getBestSales($idLang, 0, $limit)(wzorzec zps_bestsellers)category→Category::getProducts($idCategory, $idLang, 1, $limit)(wzorzec zproject_pro_news)manual→SELECTpoid_product IN (...), potem prezentacja
Wszystkie wyniki przechodzą przez ProductAssembler + ProductListingPresenter (identycznie jak w istniejących modułach).
Krok 5: Renderowanie hooków
Mechanizm: moduł rejestruje się na najczęstsze hooki (displayHome, displayFooterBefore, displayLeftColumn itp.). W każdym hooku:
public function hookDisplayHome($params) {
return $this->renderCarouselsForHook('displayHome');
}
Metoda renderCarouselsForHook($hookName):
- SELECT z
ps_pp_carouselWHEREhook_name= $hookName ANDactive= 1 ORDER BYposition - Dla każdej karuzeli: pobierz produkty, przypisz do Smarty, fetch szablonu
- Zwróć połączony HTML
Niestandardowe hooki: przy zapisie karuzeli z nowym hookiem → $this->registerHook($hookName) + ewentualnie INSERT do ps_hook.
Krok 6: Szablon frontu pp_carousel.tpl
Wzorowany na project_pro_news.tpl — ten sam layout Swiper:
- Section z unikalnym
id="pp-carousel-{$carousel.id}" - Nagłówek: tytuł + podtytuł
- Swiper container z kartami produktów
- Karta: zdjęcie (home_default), etykieta kategorii, nazwa, cena + sufiks
- Nawigacja prev/next
- Przycisk "Zobacz więcej"
Krok 7: CSS + JS
CSS — bazowany na project_pro_news.css, dostosowany do nowego namespace .pp-carousel.
JS — inicjalizacja wielu instancji Swipera:
document.querySelectorAll('.pp-carousel__slider').forEach(function(el) {
new Swiper(el, { /* config */ });
});
Krok 8: Rejestracja assetów
hookDisplayHeader() — rejestracja CSS/JS (swiper + pp_carousel) identycznie jak w project_pro_news.
Hooki do rejestracji (install)
Obowiązkowe:
displayHeader(assety CSS/JS)displayHome(główna pozycja)displayBackOfficeHeader(admin CSS/JS jeśli potrzebne)
Dodatkowe (rejestrowane dynamicznie przy tworzeniu karuzeli z niestandardowym hookiem):
displayFooterBefore,displayTopColumn,displayLeftColumn,displayFooteritd.- Dowolne niestandardowe hooki
Kolejność wdrożenia
- Pliki SQL — tabele
pp_carousel+pp_carousel_lang - pp_carousel.php — konstruktor, install/uninstall, hookDisplayHeader
- Panel admin — getContent() z listą + formularzem CRUD
- Logika produktów — getProductsByCarousel() z 4 źródłami
- Rendering hooków — renderCarouselsForHook() + rejestracja dynamiczna
- Szablon .tpl — karta produktu zgodna ze screenshotem
- CSS/JS — style + Swiper init (wieloinstancyjny)
- Testowanie — dodanie karuzeli w BO, weryfikacja na froncie