OpenCart Kereső Gyorsítás & Javítás Fulltext indexelés + relevancia alapú rendezés – Journal3 kompatibilis
📖 Áttekintés
Az OpenCart alapértelmezett keresője LIKE '%keresőszó%' lekérdezéseket használ, amelyek nagy termékadatbázisnál rendkívül lassúak, és nem képesek relevanciaalapú sorrendet kialakítani. Ez a dokumentáció bemutatja, hogyan lehet MySQL FULLTEXT indexelés és Boolean Mode keresés segítségével drasztikusan javítani a keresési élményt és sebességet.
A módosítás egyetlen PHP fájlt érint (catalog/model/catalog/product.php), és adatbázis oldali index-létrehozással egészül ki. Nincs szükség bővítmény telepítésére vagy OpenCart core fájlok tömeges módosítására.
Az AJAX alapú azonnali keresés (instant search) ugyanezen getProducts() függvényt hívja, így a fejlesztés ott is azonnal érvényes lesz.
🎯 Mire jó?
A fejlesztés az alábbi problémákat oldja meg:
Lassú keresés nagy katalógusban
A LIKE '%szó%' full table scan-t végez. Fulltext index esetén a MySQL indexelt struktúrán keres.
Cikkszám nem jelenik meg elöl
Az alapértelmezett rendezés nem veszi figyelembe, hogy a találat termékszám-, SKU- vagy EAN-egyezés-e.
Részszavas keresés hiánya
A módosított logika +szó* prefixet alkalmaz, így a „kábel" kifejezés megtalálja a „kábelköteg" szót is.
Relevancia alapú sorrend
A termékek pontszámot kapnak: a cikkszám-egyezés 15×, a leírás-egyezés 5× súlyú. A lista a legjobb találattal kezdődik.
Mértékegység-szétválasztás
A „12mm" keresést automatikusan „12 mm"-ként dolgozza fel, elkerülve a tokenizálási hibákat.
Fallback rövid keresésekre
1–2 karakteres keresési kifejezés esetén az eredeti LIKE-alapú logika lép életbe automatikusan.
✅ Várt eredmények
Gyorsabb keresés nagy adatbázisnál
Cikkszám / SKU / EAN prioritás
Részszavas (prefix) találatok
Relevancia alapú sorrend
Journal3 AJAX kereső is javul
100k+ terméknél is stabil
📋 Rendszerkövetelmények
Adatbázis
| Feltétel | Szükséges | Megjegyzés |
|---|---|---|
| MySQL / MariaDB verzió | MySQL 5.6+ / MariaDB 10.0+ | Kötelező – InnoDB FULLTEXT támogatás |
| Tábla motor | InnoDB | Kötelező – MyISAM esetén más szintaxis |
| innodb_ft_min_token_size | 2 vagy 3 | Ajánlott – 3 az alapértelmezett, 2-re módosítható |
| MySQL hozzáférés | ALTER TABLE jog | Kötelező – index létrehozáshoz |
| my.cnf szerkesztési jog | Root / sudo | Opcionális – csak ha a token size-t módosítani kell |
OpenCart
| Feltétel | Szükséges | Megjegyzés |
|---|---|---|
| OpenCart verzió | 2.x vagy 3.x | Kötelező |
| Módosítandó fájl | catalog/model/catalog/product.php |
Kötelező |
| Biztonsági mentés | Fájl + adatbázis backup | Kötelező – módosítás előtt |
| Journal3 téma | – | Opcionális – kompatibilis, de nem szükséges |
Mind a product.php fájlból, mind az adatbázisból. A FULLTEXT index hozzáadása visszafordítható, de a kód módosítása igényel megfelelő állapotot.
🚀 Megvalósítás – lépések sorrendben
1. lépés – Fulltext indexek létrehozása
Az első és legfontosabb lépés az adatbázis szintű fulltext indexek felvétele. Ezt futtatsd az SQL konzolban (phpMyAdmin, MySQL Workbench, vagy CLI).
oc_product_description: a termék neve, leírása és tagek. | oc_product: cikkszám, SKU, UPC, EAN, JAN, ISBN, MPN azonosítók.
-- Termékleírás tábla: név, leírás, tag mezők indexelése ALTER TABLE oc_product_description ADD FULLTEXT ft_search_text (name, description, tag); -- Termék tábla: azonosítók (cikkszám, SKU, EAN stb.) indexelése ALTER TABLE oc_product ADD FULLTEXT ft_search_codes (model, sku, upc, ean, jan, isbn, mpn);
Nagy adatbázisnál (100k+ termék) az ALTER TABLE futása több percig is tarthat. Ezt lehetőleg alacsony forgalmú időszakban futtasd.
2. lépés – MySQL konfiguráció ellenőrzése
Az InnoDB FULLTEXT motor alapból 3 karakter hosszú minimális token size-t alkalmaz. Ez azt jelenti, hogy 2 karakteres szavakra nem keres. Ellenőrizd az aktuális értéket:
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';
Ha az érték 3 (alapértelmezett) – elfogadható
A rendszer így is megfelelően működik. A FULLTEXT + Boolean Mode keresés a 3+ karakteres szavakra hatékonyan dolgozik. Nincs szükség MySQL konfiguráció módosítására.
Ha 2-re szeretnéd módosítani (opcionális)
Ehhez szerver adminisztrátori hozzáférés szükséges:
# [mysqld] szekcióba kell beilleszteni
innodb_ft_min_token_size = 2
MySQL újraindítása után az indexeket újra kell építeni:
ALTER TABLE oc_product_description DROP INDEX ft_search_text; ALTER TABLE oc_product DROP INDEX ft_search_codes; ALTER TABLE oc_product_description ADD FULLTEXT ft_search_text (name, description, tag); ALTER TABLE oc_product ADD FULLTEXT ft_search_codes (model, sku, upc, ean, jan, isbn, mpn);
3. lépés – PHP fájl módosítása
A keresési logika módosítása a getProducts() függvény teljes cseréjét jelenti a következő fájlban:
catalog/model/catalog/product.php
Nevezd át product.php.bak-ra, vagy készíts belőle másolatot, mielőtt bármit módosítasz.
A módosítás logikája – mit és miért?
-
1
Boolean string előkészítés – A keresett szót tokenekre bontja, mértékegységeket szétválaszt (pl. „12mm" → „12 mm"), majd minden 3+ karakteres szóra
+szó*formátumú FULLTEXT keresési kifejezést épít. -
2
Relevancia számítás – Minden találathoz pontszámot számít: a leírás egyezése 5-szörös, a kódmező (cikkszám, EAN stb.) egyezése 15-szörös súlyú.
-
3
FULLTEXT keresés vagy LIKE fallback – Ha a boolean string nem üres (van elegendő hosszú szó), FULLTEXT-et használ. Ha nem (1–2 karakteres keresés), visszaesik az eredeti LIKE-alapú logikára.
-
4
Rendezés – Keresés esetén relevancia szerint csökkenő sorrendben adja vissza a termékeket. Egyéb esetekben az OpenCart eredeti rendezési logikája érvényes.
4. lépés – Cache törlése
A módosítás életbe lépéséhez az OpenCart cache-t törölni kell:
-
1
Bejelentkezés az Admin felületre
Navigálj az Admin → Dashboard oldalra.
-
2
Developer Settings megnyitása
Admin panel jobb felső sarkában keresd a fogaskerék ikont → Developer Settings.
Képernyőfotó helyeAdmin → Developer Settings panel – Theme Cache és SASS Cache opciók -
3
Theme Cache törlése
Kattints a „Clear Theme Files" gombra.
-
4
Modifications frissítése
Admin → Extensions → Modifications → kattints a „Refresh" gombra (kék frissítés ikon jobb felül).
Képernyőfotó helyeAdmin → Extensions → Modifications oldal – Refresh gomb kiemelve
5. lépés – Tesztelés
Ellenőrizd a következő eseteket a boltban:
| Teszteset | Várt viselkedés | Státusz |
|---|---|---|
| 3+ karakteres keresőszó | FULLTEXT találatok, relevancia szerint rendezve | Ellenőrizd |
| Cikkszámra keresés (pl. „ABC123") | A pontos cikkszámú termék kerül az első helyre | Ellenőrizd |
| Részszavas keresés (pl. „kábel") | „kábelköteg", „kábeltartó" is megjelenik | Ellenőrizd |
| Mértékegységes keresés (pl. „12mm") | Megtalálja a „12 mm" és „12mm" leírású termékeket | Ellenőrizd |
| 1–2 karakteres keresés | LIKE fallback működik, nem üres az eredmény | Ellenőrizd |
| Journal3 AJAX (instant) kereső | Keresés közben is relevancia alapú lista jelenik meg | Ellenőrizd |
📄 Teljes kód – getProducts()
Az alábbi a getProducts() függvény teljes, valóban beilleszthető kódja.
A catalog/model/catalog/product.php fájlban az eredeti public function getProducts($data = array()) { ... } blokkot kell erre cserélni.
Nevezd át product.php.bak-ra, vagy készíts FTP/szerver oldali biztonsági másolatot.
public function getProducts($data = array()) { // Boolean kereső string előkészítése $boolean = ''; if (!empty($data['filter_name'])) { $search = trim($data['filter_name']); $search = preg_replace('/(\d)(mm|cm|m|kg|g|db|pcs|w|a|v)/i', '$1 $2', $search); $words = explode(' ', preg_replace('/\s+/', ' ', $search)); foreach ($words as $word) { $word = preg_replace('/[^a-zA-Z0-9áéíóöőúüűÁÉÍÓÖŐÚÜŰ]/u', '', $word); if (strlen($word) >= 3) { $boolean .= '+' . $word . '* '; } } $boolean = trim($boolean); } $sql = "SELECT p.product_id, (SELECT AVG(rating) AS total FROM " . DB_PREFIX . "review r1 WHERE r1.product_id = p.product_id AND r1.status = '1' GROUP BY r1.product_id) AS rating, (SELECT price FROM " . DB_PREFIX . "product_discount pd2 WHERE pd2.product_id = p.product_id AND pd2.customer_group_id = '" . (int)$this->config->get('config_customer_group_id') . "' AND pd2.quantity = '1' AND ((pd2.date_start = '0000-00-00' OR pd2.date_start < NOW()) AND (pd2.date_end = '0000-00-00' OR pd2.date_end > NOW())) ORDER BY pd2.priority ASC, pd2.price ASC LIMIT 1) AS discount, (SELECT price FROM " . DB_PREFIX . "product_special ps WHERE ps.product_id = p.product_id AND ps.customer_group_id = '" . (int)$this->config->get('config_customer_group_id') . "' AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND (ps.date_end = '0000-00-00' OR ps.date_end > NOW())) ORDER BY ps.priority ASC, ps.price ASC LIMIT 1) AS special"; // Relevancia számítás hozzáadása, ha van keresési kifejezés if (!empty($boolean)) { $sql .= ", ( MATCH(pd.name, pd.description, pd.tag) AGAINST('" . $boolean . "' IN BOOLEAN MODE) * 5 + MATCH(p.model, p.sku, p.upc, p.ean, p.jan, p.isbn, p.mpn) AGAINST('" . $boolean . "' IN BOOLEAN MODE) * 15 ) AS relevance"; } if (!empty($data['filter_category_id'])) { if (!empty($data['filter_sub_category'])) { $sql .= " FROM " . DB_PREFIX . "category_path cp LEFT JOIN " . DB_PREFIX . "product_to_category p2c ON (cp.category_id = p2c.category_id)"; } else { $sql .= " FROM " . DB_PREFIX . "product_to_category p2c"; } if (!empty($data['filter_filter'])) { $sql .= " LEFT JOIN " . DB_PREFIX . "product_filter pf ON (p2c.product_id = pf.product_id) LEFT JOIN " . DB_PREFIX . "product p ON (pf.product_id = p.product_id)"; } else { $sql .= " LEFT JOIN " . DB_PREFIX . "product p ON (p2c.product_id = p.product_id)"; } } else { $sql .= " FROM " . DB_PREFIX . "product p"; } $sql .= " LEFT JOIN " . DB_PREFIX . "product_description pd ON (p.product_id = pd.product_id)" . " LEFT JOIN " . DB_PREFIX . "product_to_store p2s ON (p.product_id = p2s.product_id)" . " WHERE pd.language_id = '" . (int)$this->config->get('config_language_id') . "'" . " AND p.status = '1' AND p.date_available <= NOW()" . " AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "'"; if (!empty($data['filter_category_id'])) { if (!empty($data['filter_sub_category'])) { $sql .= " AND cp.path_id = '" . (int)$data['filter_category_id'] . "'"; } else { $sql .= " AND p2c.category_id = '" . (int)$data['filter_category_id'] . "'"; } if (!empty($data['filter_filter'])) { $implode = array(); $filters = explode(',', $data['filter_filter']); foreach ($filters as $filter_id) { $implode[] = (int)$filter_id; } $sql .= " AND pf.filter_id IN (" . implode(',', $implode) . ")"; } } // FULLTEXT keresés ha van elegendő hosszú szó, egyébként LIKE fallback if (!empty($boolean)) { $sql .= " AND ( MATCH(pd.name, pd.description, pd.tag) AGAINST('" . $boolean . "' IN BOOLEAN MODE) OR MATCH(p.model, p.sku, p.upc, p.ean, p.jan, p.isbn, p.mpn) AGAINST('" . $boolean . "' IN BOOLEAN MODE) )"; } elseif (!empty($data['filter_name']) || !empty($data['filter_tag'])) { // Fallback rövid kereséshez (1-2 karakter) $sql .= " AND ("; if (!empty($data['filter_name'])) { $implode = array(); $words = explode(' ', trim(preg_replace('/\s+/', ' ', $data['filter_name']))); foreach ($words as $word) { $word = $this->db->escape($word); $implode[] = "(pd.name LIKE '%$word%' OR pd.tag LIKE '%$word%')"; } if ($implode) { $sql .= " " . implode(" AND ", $implode) . ""; } if (!empty($data['filter_description'])) { $sql .= " OR pd.description LIKE '%" . $this->db->escape($data['filter_name']) . "%'"; } } if (!empty($data['filter_name']) && !empty($data['filter_tag'])) { $sql .= " OR "; } if (!empty($data['filter_tag'])) { $implode = array(); $words = explode(' ', trim(preg_replace('/\s+/', ' ', $data['filter_tag']))); foreach ($words as $word) { $implode[] = "pd.tag LIKE '%" . $this->db->escape($word) . "%'"; } if ($implode) { $sql .= " " . implode(" AND ", $implode) . ""; } } if (!empty($data['filter_name'])) { $sql .= " OR LCASE(p.model) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.sku) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.upc) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.ean) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.jan) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.isbn) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; $sql .= " OR LCASE(p.mpn) = '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "'"; } $sql .= ")"; } if (!empty($data['filter_manufacturer_id'])) { $sql .= " AND p.manufacturer_id = '" . (int)$data['filter_manufacturer_id'] . "'"; } $sql .= " GROUP BY p.product_id"; $sort_data = array( 'pd.name', 'p.model', 'p.quantity', 'p.price', 'rating', 'p.sort_order', 'p.date_added' ); // Relevancia alapú rendezés keresés esetén, egyébként az OpenCart eredeti logikája if (!empty($boolean)) { $sql .= " ORDER BY relevance DESC"; } elseif (isset($data['sort']) && in_array($data['sort'], $sort_data)) { if ($data['sort'] == 'pd.name' || $data['sort'] == 'p.model') { $sql .= " ORDER BY LCASE(" . $data['sort'] . ")"; } elseif ($data['sort'] == 'p.price') { $sql .= " ORDER BY (CASE WHEN special IS NOT NULL THEN special WHEN discount IS NOT NULL THEN discount ELSE p.price END)"; } else { $sql .= " ORDER BY " . $data['sort']; } } else { $sql .= " ORDER BY p.sort_order"; } if (isset($data['order']) && ($data['order'] == 'DESC')) { $sql .= " DESC, LCASE(pd.name) DESC"; } else { $sql .= " ASC, LCASE(pd.name) ASC"; } if (isset($data['start']) || isset($data['limit'])) { if ($data['start'] < 0) { $data['start'] = 0; } if ($data['limit'] < 1) { $data['limit'] = 20; } $sql .= " LIMIT " . (int)$data['start'] . "," . (int)$data['limit']; } $product_data = array(); $query = $this->db->query($sql); foreach ($query->rows as $result) { $product_data[$result['product_id']] = $this->getProduct($result['product_id']); } return $product_data; }
A fenti „Másolás" gombbal vágólapra másolható, majd a product.php fájlban az eredeti getProducts() függvény helyére illeszthető be.
🔧 Hibaelhárítás
Nem jelennek meg találatok kereséskor
Ellenőrizd az INFORMATION_SCHEMA.STATISTICS táblában, hogy az ft_search_text és ft_search_codes indexek valóban léteznek-e.
SELECT TABLE_NAME, INDEX_NAME, INDEX_TYPE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME IN ('ft_search_text', 'ft_search_codes');
SQL szintaxishiba a módosítás után
Ellenőrizd, hogy a tábla prefix helyes-e. Az oc_ prefix az OpenCart alapértelmezése, de ez konfigurációfüggő. A DB_PREFIX konstans értéke a config.php fájlban ellenőrizhető.
A Journal3 AJAX kereső nem változott
Töröld a böngésző cache-t, és ellenőrizd, hogy a Modifications refresh lefutott-e (4. lépés). Ha a Journal3 saját modellt használ, annak is módosítani kell a keresési logikáját.
Lassabb lett a keresés az indexelés után
Nagy adatbázisnál érdemes a innodb_ft_cache_size és innodb_ft_total_cache_size értékeket növelni a my.cnf fájlban.