From 2a98067d9eb9d859306021fdd6bbb469d00a61f1 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Mon, 23 Feb 2026 23:24:48 +0100 Subject: [PATCH] Add Custom Feature Tab module with database integration and AJAX support - Created main module file `customfeaturetab.php` to manage product tabs based on feature values. - Implemented database installation and uninstallation methods to create necessary tables. - Added admin controller files for handling redirects and admin functionalities. - Introduced AJAX functionality in `admin.js` for dynamic feature value selection based on selected features. - Included temporary query script for testing feature values. - Added language support for the module with Polish translations. - Created necessary view files and JavaScript files for module functionality. - Added logo image for the module. --- .claude/settings.local.json | 10 + .vscode/ftp-kr.json | 3 +- .../classes/CustomFeatureTabRule.php | 99 +++++++ modules/customfeaturetab/classes/index.php | 8 + modules/customfeaturetab/config.xml | 12 + .../admin/AdminCustomFeatureTabController.php | 246 ++++++++++++++++++ .../controllers/admin/index.php | 8 + .../customfeaturetab/controllers/index.php | 8 + modules/customfeaturetab/customfeaturetab.php | 148 +++++++++++ modules/customfeaturetab/dbquery_temp.php | 23 ++ modules/customfeaturetab/index.php | 14 + modules/customfeaturetab/logo.png | Bin 0 -> 8803 bytes modules/customfeaturetab/views/index.php | 8 + modules/customfeaturetab/views/js/admin.js | 56 ++++ modules/customfeaturetab/views/js/index.php | 8 + .../InterBlue/templates/catalog/product.tpl | 28 +- 16 files changed, 664 insertions(+), 15 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 modules/customfeaturetab/classes/CustomFeatureTabRule.php create mode 100644 modules/customfeaturetab/classes/index.php create mode 100644 modules/customfeaturetab/config.xml create mode 100644 modules/customfeaturetab/controllers/admin/AdminCustomFeatureTabController.php create mode 100644 modules/customfeaturetab/controllers/admin/index.php create mode 100644 modules/customfeaturetab/controllers/index.php create mode 100644 modules/customfeaturetab/customfeaturetab.php create mode 100644 modules/customfeaturetab/dbquery_temp.php create mode 100644 modules/customfeaturetab/index.php create mode 100644 modules/customfeaturetab/logo.png create mode 100644 modules/customfeaturetab/views/index.php create mode 100644 modules/customfeaturetab/views/js/admin.js create mode 100644 modules/customfeaturetab/views/js/index.php diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..83c00b77 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(head:*)", + "Bash(cp:*)", + "Bash(cd:*)", + "Bash(curl:*)" + ] + } +} diff --git a/.vscode/ftp-kr.json b/.vscode/ftp-kr.json index 90d36ac2..45cc6ca8 100644 --- a/.vscode/ftp-kr.json +++ b/.vscode/ftp-kr.json @@ -12,6 +12,7 @@ "ignoreRemoteModification": true, "ignore": [ ".git", - "/.vscode" + "/.vscode", + "/.claude" ] } \ No newline at end of file diff --git a/modules/customfeaturetab/classes/CustomFeatureTabRule.php b/modules/customfeaturetab/classes/CustomFeatureTabRule.php new file mode 100644 index 00000000..a118d4d9 --- /dev/null +++ b/modules/customfeaturetab/classes/CustomFeatureTabRule.php @@ -0,0 +1,99 @@ + + * @copyright Project-Pro + * @license Proprietary - paid license + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CustomFeatureTabRule extends ObjectModel +{ + public $id_feature; + public $id_feature_value; + public $position; + public $active; + public $title; + public $content; + public $date_add; + public $date_upd; + + public static $definition = array( + 'table' => 'custom_feature_tab', + 'primary' => 'id_custom_feature_tab', + 'multilang' => true, + 'fields' => array( + 'id_feature' => array( + 'type' => self::TYPE_INT, + 'validate' => 'isUnsignedId', + 'required' => true, + ), + 'id_feature_value' => array( + 'type' => self::TYPE_INT, + 'validate' => 'isUnsignedId', + 'required' => true, + ), + 'position' => array( + 'type' => self::TYPE_INT, + 'validate' => 'isUnsignedInt', + ), + 'active' => array( + 'type' => self::TYPE_BOOL, + 'validate' => 'isBool', + ), + 'date_add' => array( + 'type' => self::TYPE_DATE, + 'validate' => 'isDate', + ), + 'date_upd' => array( + 'type' => self::TYPE_DATE, + 'validate' => 'isDate', + ), + // Lang fields + 'title' => array( + 'type' => self::TYPE_STRING, + 'lang' => true, + 'validate' => 'isGenericName', + 'required' => true, + 'size' => 255, + ), + 'content' => array( + 'type' => self::TYPE_HTML, + 'lang' => true, + 'validate' => 'isCleanHtml', + ), + ), + ); + + /** + * Get active rules matching product features. + * + * @param array $productFeatures Array from Product::getFeaturesStatic + * @param int $idLang Language ID + * @return array + */ + public static function getMatchingRules(array $productFeatures, $idLang) + { + if (empty($productFeatures)) { + return array(); + } + + $conditions = array(); + foreach ($productFeatures as $feat) { + $conditions[] = '(' . (int) $feat['id_feature'] . ', ' . (int) $feat['id_feature_value'] . ')'; + } + + $sql = 'SELECT cft.*, cftl.`title`, cftl.`content` + FROM `' . _DB_PREFIX_ . 'custom_feature_tab` cft + LEFT JOIN `' . _DB_PREFIX_ . 'custom_feature_tab_lang` cftl + ON cft.`id_custom_feature_tab` = cftl.`id_custom_feature_tab` + AND cftl.`id_lang` = ' . (int) $idLang . ' + WHERE cft.`active` = 1 + AND (cft.`id_feature`, cft.`id_feature_value`) IN (' . implode(',', $conditions) . ') + ORDER BY cft.`position` ASC, cft.`id_custom_feature_tab` ASC'; + + return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); + } +} diff --git a/modules/customfeaturetab/classes/index.php b/modules/customfeaturetab/classes/index.php new file mode 100644 index 00000000..30c35169 --- /dev/null +++ b/modules/customfeaturetab/classes/index.php @@ -0,0 +1,8 @@ + + + customfeaturetab + + + + + front_office_features + 1 + 0 + + diff --git a/modules/customfeaturetab/controllers/admin/AdminCustomFeatureTabController.php b/modules/customfeaturetab/controllers/admin/AdminCustomFeatureTabController.php new file mode 100644 index 00000000..a6a21981 --- /dev/null +++ b/modules/customfeaturetab/controllers/admin/AdminCustomFeatureTabController.php @@ -0,0 +1,246 @@ + + * @copyright Project-Pro + * @license Proprietary - paid license + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +require_once _PS_MODULE_DIR_ . 'customfeaturetab/classes/CustomFeatureTabRule.php'; + +class AdminCustomFeatureTabController extends ModuleAdminController +{ + public function __construct() + { + $this->table = 'custom_feature_tab'; + $this->className = 'CustomFeatureTabRule'; + $this->identifier = 'id_custom_feature_tab'; + $this->lang = true; + $this->bootstrap = true; + $this->addRowAction('edit'); + $this->addRowAction('delete'); + $this->position_identifier = 'position'; + + parent::__construct(); + + $this->bulk_actions = array( + 'delete' => array( + 'text' => $this->l('Delete selected'), + 'confirm' => $this->l('Delete selected items?'), + 'icon' => 'icon-trash', + ), + ); + + $this->fields_list = array( + 'id_custom_feature_tab' => array( + 'title' => $this->l('ID'), + 'align' => 'center', + 'class' => 'fixed-width-xs', + ), + 'feature_name' => array( + 'title' => $this->l('Cecha'), + 'filter_key' => 'fl!name', + ), + 'feature_value_name' => array( + 'title' => $this->l('Wartość cechy'), + 'filter_key' => 'fvl!value', + ), + 'title' => array( + 'title' => $this->l('Tytuł zakładki'), + 'filter_key' => 'b!title', + ), + 'position' => array( + 'title' => $this->l('Pozycja'), + 'align' => 'center', + 'class' => 'fixed-width-xs', + 'position' => 'position', + ), + 'active' => array( + 'title' => $this->l('Aktywna'), + 'active' => 'status', + 'type' => 'bool', + 'align' => 'center', + 'class' => 'fixed-width-sm', + ), + ); + } + + /** + * Override getList to JOIN feature/feature_value names. + */ + public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false) + { + $this->_select = 'fl.`name` AS `feature_name`, fvl.`value` AS `feature_value_name`'; + $this->_join = ' + LEFT JOIN `' . _DB_PREFIX_ . 'feature_lang` fl + ON (a.`id_feature` = fl.`id_feature` AND fl.`id_lang` = ' . (int) $id_lang . ') + LEFT JOIN `' . _DB_PREFIX_ . 'feature_value_lang` fvl + ON (a.`id_feature_value` = fvl.`id_feature_value` AND fvl.`id_lang` = ' . (int) $id_lang . ')'; + + parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop); + } + + /** + * Build the add/edit form. + */ + public function renderForm() + { + $features = Feature::getFeatures($this->context->language->id); + $featureOptions = array(); + foreach ($features as $feature) { + $featureOptions[] = array( + 'id' => $feature['id_feature'], + 'name' => $feature['name'], + ); + } + + // For edit mode, get values of the currently selected feature + $featureValueOptions = array(); + $selectedFeatureValue = 0; + if ($this->object && $this->object->id) { + $selectedFeatureValue = (int) $this->object->id_feature_value; + $sql = 'SELECT fv.`id_feature_value`, fvl.`value` + FROM `' . _DB_PREFIX_ . 'feature_value` fv + LEFT JOIN `' . _DB_PREFIX_ . 'feature_value_lang` fvl + ON fv.`id_feature_value` = fvl.`id_feature_value` + AND fvl.`id_lang` = ' . (int) $this->context->language->id . ' + WHERE fv.`id_feature` = ' . (int) $this->object->id_feature . ' + ORDER BY fvl.`value` ASC'; + $values = Db::getInstance()->executeS($sql); + if ($values) { + foreach ($values as $val) { + $featureValueOptions[] = array( + 'id' => $val['id_feature_value'], + 'name' => $val['value'], + ); + } + } + } + + $this->fields_form = array( + 'legend' => array( + 'title' => $this->l('Reguła karty cechy'), + 'icon' => 'icon-cogs', + ), + 'input' => array( + array( + 'type' => 'select', + 'label' => $this->l('Cecha'), + 'name' => 'id_feature', + 'required' => true, + 'options' => array( + 'query' => $featureOptions, + 'id' => 'id', + 'name' => 'name', + ), + ), + array( + 'type' => 'select', + 'label' => $this->l('Wartość cechy'), + 'name' => 'id_feature_value', + 'required' => true, + 'options' => array( + 'query' => $featureValueOptions, + 'id' => 'id', + 'name' => 'name', + ), + 'desc' => $this->l('Wartości zostaną załadowane po wyborze cechy.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Tytuł zakładki'), + 'name' => 'title', + 'lang' => true, + 'required' => true, + 'size' => 255, + ), + array( + 'type' => 'textarea', + 'label' => $this->l('Treść'), + 'name' => 'content', + 'lang' => true, + 'autoload_rte' => true, + 'rows' => 10, + 'cols' => 100, + ), + array( + 'type' => 'text', + 'label' => $this->l('Pozycja'), + 'name' => 'position', + 'class' => 'fixed-width-xs', + ), + array( + 'type' => 'switch', + 'label' => $this->l('Aktywna'), + 'name' => 'active', + 'is_bool' => true, + 'values' => array( + array('id' => 'active_on', 'value' => 1, 'label' => $this->l('Tak')), + array('id' => 'active_off', 'value' => 0, 'label' => $this->l('Nie')), + ), + ), + ), + 'submit' => array( + 'title' => $this->l('Zapisz'), + ), + ); + + return parent::renderForm(); + } + + /** + * Add JS for AJAX dropdown and pass the selected value. + */ + public function setMedia($isNewTheme = false) + { + parent::setMedia($isNewTheme); + + if (in_array($this->display, array('add', 'edit'))) { + $this->addJS(_PS_MODULE_DIR_ . 'customfeaturetab/views/js/admin.js'); + + // Pass the preselected feature value to JS for edit mode + if ($this->object && $this->object->id) { + Media::addJsDef(array( + 'customfeaturetab_selected_value' => (int) $this->object->id_feature_value, + )); + } + } + } + + /** + * AJAX endpoint: get feature values for a given feature. + */ + public function ajaxProcessGetFeatureValues() + { + $idFeature = (int) Tools::getValue('id_feature'); + $idLang = (int) $this->context->language->id; + + // Include ALL values (predefined + custom) for the feature + $sql = 'SELECT fv.`id_feature_value`, fvl.`value` + FROM `' . _DB_PREFIX_ . 'feature_value` fv + LEFT JOIN `' . _DB_PREFIX_ . 'feature_value_lang` fvl + ON fv.`id_feature_value` = fvl.`id_feature_value` + AND fvl.`id_lang` = ' . (int) $idLang . ' + WHERE fv.`id_feature` = ' . (int) $idFeature . ' + ORDER BY fvl.`value` ASC'; + + $values = Db::getInstance()->executeS($sql); + $result = array(); + if ($values) { + foreach ($values as $val) { + $result[] = array( + 'id_feature_value' => $val['id_feature_value'], + 'value' => $val['value'], + ); + } + } + + header('Content-Type: application/json'); + die(json_encode($result)); + } +} diff --git a/modules/customfeaturetab/controllers/admin/index.php b/modules/customfeaturetab/controllers/admin/index.php new file mode 100644 index 00000000..30c35169 --- /dev/null +++ b/modules/customfeaturetab/controllers/admin/index.php @@ -0,0 +1,8 @@ + + * @copyright Project-Pro + * @license Proprietary - paid license + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +require_once dirname(__FILE__) . '/classes/CustomFeatureTabRule.php'; + +class CustomFeatureTab extends Module +{ + public function __construct() + { + $this->name = 'customfeaturetab'; + $this->tab = 'front_office_features'; + $this->version = '1.0.0'; + $this->author = 'Project-Pro'; + $this->need_instance = 0; + $this->bootstrap = true; + + parent::__construct(); + + $this->displayName = $this->l('Karty cech produktu'); + $this->description = $this->l('Dodaje dodatkowe zakładki na karcie produktu w zależności od przypisanych cech.'); + $this->ps_versions_compliancy = array('min' => '1.7.0.0', 'max' => _PS_VERSION_); + } + + public function install() + { + return parent::install() + && $this->installDb() + && $this->installTab() + && $this->registerHook('displayProductExtraContent'); + } + + public function uninstall() + { + return $this->uninstallDb() + && $this->uninstallTab() + && parent::uninstall(); + } + + private function installDb() + { + $sql = array(); + + $sql[] = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'custom_feature_tab` ( + `id_custom_feature_tab` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `id_feature` INT(11) UNSIGNED NOT NULL, + `id_feature_value` INT(11) UNSIGNED NOT NULL, + `position` INT(11) UNSIGNED NOT NULL DEFAULT 0, + `active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + PRIMARY KEY (`id_custom_feature_tab`), + INDEX `idx_feature_value` (`id_feature`, `id_feature_value`) + ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;'; + + $sql[] = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'custom_feature_tab_lang` ( + `id_custom_feature_tab` INT(11) UNSIGNED NOT NULL, + `id_lang` INT(11) UNSIGNED NOT NULL, + `title` VARCHAR(255) NOT NULL, + `content` TEXT, + PRIMARY KEY (`id_custom_feature_tab`, `id_lang`) + ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;'; + + foreach ($sql as $query) { + if (!Db::getInstance()->execute($query)) { + return false; + } + } + + return true; + } + + private function uninstallDb() + { + return Db::getInstance()->execute('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'custom_feature_tab_lang`') + && Db::getInstance()->execute('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'custom_feature_tab`'); + } + + private function installTab() + { + $tab = new Tab(); + $tab->class_name = 'AdminCustomFeatureTab'; + $tab->module = $this->name; + $tab->id_parent = (int) Tab::getIdFromClassName('AdminCatalog'); + $tab->icon = 'description'; + $languages = Language::getLanguages(false); + foreach ($languages as $lang) { + $tab->name[$lang['id_lang']] = 'Karty cech produktu'; + } + + return $tab->add(); + } + + private function uninstallTab() + { + $idTab = (int) Tab::getIdFromClassName('AdminCustomFeatureTab'); + if ($idTab) { + $tab = new Tab($idTab); + return $tab->delete(); + } + + return true; + } + + public function getContent() + { + Tools::redirectAdmin($this->context->link->getAdminLink('AdminCustomFeatureTab')); + } + + /** + * Hook: displayProductExtraContent + * Returns extra tabs for products that match feature rules. + */ + public function hookDisplayProductExtraContent($params) + { + $product = $params['product']; + $idLang = (int) $this->context->language->id; + + $productFeatures = Product::getFeaturesStatic((int) $product->id); + if (empty($productFeatures)) { + return array(); + } + + $rules = CustomFeatureTabRule::getMatchingRules($productFeatures, $idLang); + if (empty($rules)) { + return array(); + } + + $tabs = array(); + foreach ($rules as $rule) { + $extraContent = new PrestaShop\PrestaShop\Core\Product\ProductExtraContent(); + $extraContent->setTitle($rule['title']); + $extraContent->setContent($rule['content']); + $tabs[] = $extraContent; + } + + return $tabs; + } +} diff --git a/modules/customfeaturetab/dbquery_temp.php b/modules/customfeaturetab/dbquery_temp.php new file mode 100644 index 00000000..391af72f --- /dev/null +++ b/modules/customfeaturetab/dbquery_temp.php @@ -0,0 +1,23 @@ +executeS($sql); +if ($results) { + foreach ($results as $row) { + echo $row['id_product'] . ' | ' . $row['product_name'] . ' | ' . $row['feature_name'] . ' -> ' . $row['feature_value'] . "\n"; + } +} else { + echo "Brak wynikow\n"; +} diff --git a/modules/customfeaturetab/index.php b/modules/customfeaturetab/index.php new file mode 100644 index 00000000..1e7bc3a7 --- /dev/null +++ b/modules/customfeaturetab/index.php @@ -0,0 +1,14 @@ +KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000+`NklaG7puERJK8fW9}7ig6j6L2;h`WQknl_bA>>t+q>`%C<37&$ z?LB{-$Grh{W+`oD{Z`#`?yYlwd++c5?$Y{HP8CbrufTg2Sv;L=CU+|w91Gt?C4 zKwXS8Aq2yFKmtJpaUd8;HxNvJqZkp--_NNc{1NaaD&~?JB8SPq?bioMK7rcPuUw9U)U-&{+tz1c?T48o}7FAs@xX){loBl9D23d&6 zXB6Hl7*Qo+$IhL6`%7PD^8WjockmF4y=OtCMk9`hoI`L3hL~Vt7}@g#Kr`R#Bq@iJ z4r57|ef1i8$|W?f9dj1Y|dtvNQe=isa}x!|3D z#!JtCDITTO?u?1p2UO*T|6d{fRqy}MZxIrlH%tKRgQBbxkwp#x3@4DLffHA(AX&7K z$L_kD6B~_;XuQWsMmpjIyhB95Wh5jb`L)lKA-OU{5a$?-BUaR#4DZ>?hFgEg`pplq zXyrY~;RXx%Tr5ClOJ-#`T~2B-NXLz_~{qMWXpebA=$5T^N}kQoIhJ z`8`!k4Yhy-#mq2MNEN4n4U>~h|NGbS-j9EbC=GN&xXo~H5S7-iwd4ms^dX4kIm^6# z5V5PWjph5xI5Lx|u&fk>k`WQAtUd2V+_`HfbKADEv^j_u!2@E3sUilEIAZUJoud!0 z<&ymSN+srKRD>nf8v8bFX8rEnoO993iD0={81Yf^1Mva(tR>@x-fC<_-ZpjN9B#~xFo z)9o@dGt2(*aqhe0cJ^=B$l?PBIl+Ya^%~uvghI#=GPs69*nnvuN{!oRCRusyHC+0p zH!(MLc(Us`&zwEcFQ*(FKT=2)`6?p+(fVRtKM^6|EPG)TcFy_MfokrjD9(AR)hZzb z(hxuks?P(tIN~V6M=^({r+E0`&D{6h@1YwWV68+9R;zSFilqTHh5Q*B0u~g}Ku8j9 zvB0HY{3p&@vxd2LYdxeFVT5wq2PXFQ$Oc|6>Tb_pXF@uc2_XfOTFJ6j_zzmxw-n;p zmd*Y~OHG*=W`&o7IH6Ll5d*CqS)gK2F^W^5~7)fO$xCT%;mh0(~=l+L?p^S zn<*uKnibhlb{W)Yw_2ogZO%Sz4e$AfPc#0ii@0ZclBkHU;_|SRg(GtH4mBDqcxW>} zz4eFGn@v>QU#q!T&7tOS7Ba1tSY@{NrsnprH?knux ze*hmvYf{{syXxtx$5{*(gOBa%Y9*GPJ9qM)%P!-dU);^W(7X&0YMz|NEQ{tcH& zcMiYoe$LF2M|H`d?~pwB_bgo~5rH(Mv}UKd{B3V%`0_vDXA=jAyw3#nLMCe-ew?0NxpaEH>uSdh|~Y(<8JKYF3u@<<7AYJeYMK}x$#C`ICg*^f8y`>`CWI@92h8f zpOLHNmst$6XPZUJ_?~A(`THj1IL~2g{hr@f6G-L|BACML%RLK7#Pd~c}YC|9$3J#N5CZpkzRZ+EoLPq%p`wZd?<%PjuIz%?b+_|r|g7oU3`>3Qd{V{Qf~1<_^7Dy|n)svO?B0bF58SCW^wBV^CvTZ9s!m;U!+ zZO^v_34zzY{T*zJT$xtpFG@m);+V?bG4|};O&mo|Lv*Gi$QmDcCJr1R-LscwrBViV z#1Sy&H=4Y>)#0nxeuC*%3-9CzHnqGOG3ll6W|<|NSB1+0teuIM7=RUeV|FD zS|y63%+@aZj!rk>#8oHaPF}@Sr$Zs0lfDE)U~qPh-P@kRS7IW^8IeO)dG8q;8>2ci zO{LcC!7$%cFyq)plgFl}Xmt}Bl`1A7^PAiPCS*QaCJa)fi6tRfKeR>gQA8BQI488a z9VQMPV#9_FY}@=WQ{&@Y_O5raa@h&ATC=4#8`UbupK~sIHg4vmxI)OSo%y$*BFqbc zecPYTC2hTjdh5g&NlZ=nU{t1qWVvZJAcg;2Yr@#GMmMvLI zt2LJi(M>ph)hd1w`Ce&OU?UG*O&_uKu}4YL6p+zK(WMAX9hjh50!jJ=&Ds=GY0A*? z#}UV#(8=DjzJ?bjWqu1B5n>#UkOm%kY!f@SZDZ`orz~& zZK~dlM2ajxgwEU?ap`CM8_japmKyUGE$Ttk`a(nz8p_{!s-6eWdb7d*Ubl`XZvGxC zhlhDVTw%Ccr9QGC3m>6ZK3T|$>eQ6cfdS4rc#toC>{|Znlb>X`UMERY1{zIdaFAsG zK`K^;WQvU011{;(O}bQlwc$KeK?q5T6YFJdy`l(*(6T^ta1hLT#g!E>@qP3`Nj#Fn zbY^ajJ)1Uj$??lryYMKM4h}L9#aXvNEpLPLm3#ShqRP^tA=d2J#pkd7ActmWiQ|ZB ztqRpD-JpGquiQX_{W(|x_He?K{&WpbSr)zV|!vJ>Y>)p+W`hd8NL&nkK>>kyRR zm38Js6%9&eoZPa7#?(lIa{AB+5B~HHuA7=7o}Q-aBGA6)Y354pG-2SVMbzsxtlgP( z&dV-MjiLDq>Bdoyhb0cOplNEY2B5PPoxJg)ABCPIVKJrV*2SF%&*mYjj0lz zMSIf9_N3;c0{X@V#lO@7XO4_8dhh+jI(G^NwR)9%?z)r8!~`|(^RAoq zb>TvJZMiivR?e;qD;4-PGdihq-p~jaH0NcXD~*viEX|k}&!T0^5Hkeb>O^I;hLnZ# zkAl%rT1onx2dgRdI6`;qWPJYtyo*aKYQK*feLbA4N4n*KjVvD+K+`nq&*V)g`UE9! zC=)b?9;Mq@;bXx{MOaY08Loh|k+mxoP-VJV<(Lze5t2X@Y?DL6BWkn;2AYhVw35kg zrw>B;#x;wL3#Mnd`)5C+TC0_8CZ!<@qRJ;=&M|T5Ajz(s99?f_!kOk)*3x=qU$ua+ zTF$@<7W&@n!8ju)J;E50FV&Rx=qSf5T}n6Yf|mzeFnz}0Au;8|bI)N9nbt5x@+L2) zq#>}p-r(`?-NJ#xhY;s-YxU~fvR=Z7;|klhZliYK5H%;+#`6xFG_uR3bMzc}IJ1aY{l-88Uv+bcx93>!W(MrsPvyiQjb;ZdypRsG` z`D#HxsyPSs1TCzXxl?9oufkptPI&Q)v*OF?T@g0t)FRaUw?mrPaYr9b{hYNtJu`zV zAyH`ENJ3zBtZinp~H*##HhK4kQt16b}tFUGW}( zf(6U2t>?R9+Ecn(AM~wES~N|VIBqFto_jXkb_XYNqjQE+ubAfn6L%LO;gxTB3tMV& zwvn9qwD8NIRJ~{Uo-w}hFYAcw4J7Zhixjaf6FGK{WoN&TO%n&GWJAnc&=k{vX!T zJ?(TByy7BuO;2YXZ!@HT5lUABCl1bI?&e$g`q#cjV{j1br=T2R-By=NFMli1#jj@l z-aW*C^jj@0tQdL&o&wT2>uTY*p`Vnr^i_o(DJ2QJmoDLw%PynSYGc7)ry-ux5aUt_ zq!I|Cw<&T@=yrM4+up__12qU~_Ao74^VvqykaFSBFi-yT7y0hZH#0aiua9$b=%8ql z@b34$m+1AEaL?FYV#{Et1x(Y>=ZD#Mkmgp+hdbmxTl~!t6R-R;u^L<2EmpqcZ7e@- zDM`2clxX^|&Jvs@I59`ILDDV${X>4_^H~M6_62z@4k9tysS1RBpcKZbl~# zQIBFwdWm;Yg*y?JM-?{Q{&OB2A7|y-GZ>yXk0edX7Nx2Z0c&4+KDY1KLFLq~$RbuZs=fVs5{^m`%C%3b(S|jaaPVE;U&uXkxgyrM*>~mNK7@YuoU9*RInUHGgD zSJnob+_3J;?Eb<(a#3S|)O+%2OF2Tz8pqkx$vMZtcAM?(HWNdG#LHg5{1cWjvUo9- zfdK{v2iZS8!}mV-Io>KUBazR8c~Qj_rBj|Dl&N1p)Ci_jReAF8VYZ$BBCh<*r)l_@ zB!ruyNH0a6d)7s!b|3z=r$5w%YT}dccrO<>2IukRfBq8tuD_o18}lIY+3cVESV}P( zC=VcIQ{+^Qsiezfnlfd^flix_DNY=V1_oJGsWMn(G*G5&mIda%{5JDX37LRT&rI|9 z`RDN$pZ*jJYYjR{>p>%W6&4-f*PQ2(J!AWS;~5^H9y|G#6JA?wHu>h)zs}~*ex3`| z(X3TSvm>2(C)<@XMx^i5Mt?WWJH$Y-UVOvy8B4FkD(;slU+$YeC=bm|Gxz$}am7bJ z%s{0|r_;W_QsEL2_8md({i5s|-}hTiiaIKehFJKvh<|lsaDaO^Zs1!V{_mWxtyE$ZSj?nGpFIyIx6UeSID)D;2e@WfBFW({8gq7M5N49^QQU z<+z|ENoOP2Ybq7lC+@eLx%z(z89VTjHiAXhd9!!dn+={B+sikc3!X>mB+vLo5&(q}_xkx^2czUCo8>yONij_afS}GicCn z;^aM;?yFRU=R-);Vw{94)VQu1SLREl!hQGuiuLQhg5C2=PWLe*wJJeeHX0~;LA^0; zZysAVskC>atT+s3il!-#&&{&$#1)+Kj<<2a#TPSNtI=xBbxrgdg8Y3psaBppAvMB# zC756B)V=_AQMJ*aV}YOi_{VJi&dt;}ZDw?~#b^{$i({PgST-l@HTk61UMo(1I8#WJ zL{koS6UHLP)QaVdyy`+;cj=`pUa*LEdzKLN0nooPu{%kNvv~g&LaHZnDk1Q8!Tu}G zuWdFO%%%xXY3d|LNraBcijKLLZXonoK{>~dAT!vqlsN0BCEW262+CQZ77~3 z4J2tossZmDvG>GLob_db5<*Hy6N2)TVD|{zq>=xLXgY(XScv#tg?zsL%S0FBHsCfK zm0+BrAw44v;dEf7U`LzDhzN~}xQH}_G)-G+yFCf)1KH}GZBk<+xXqZ&{f3bDJN^9M Z0RTWHo1NjK*;xPp002ovPDHLkV1hJN^3nhR literal 0 HcmV?d00001 diff --git a/modules/customfeaturetab/views/index.php b/modules/customfeaturetab/views/index.php new file mode 100644 index 00000000..30c35169 --- /dev/null +++ b/modules/customfeaturetab/views/index.php @@ -0,0 +1,8 @@ +--'); + return; + } + + // Use the current page URL, append ajax params + var ajaxUrl = baseUrl + '&ajax=1&action=getFeatureValues&id_feature=' + idFeature; + + $.ajax({ + url: ajaxUrl, + type: 'GET', + dataType: 'json', + success: function (data) { + $valueSelect.empty(); + if (data && data.length) { + $.each(data, function (i, item) { + var selected = (item.id_feature_value == preselectedValue) ? ' selected' : ''; + $valueSelect.append( + '' + ); + }); + preselectedValue = null; + } else { + $valueSelect.append(''); + } + }, + error: function (xhr, status, error) { + console.error('[customfeaturetab] AJAX error:', status, error); + } + }); + }); + + if ($featureSelect.val()) { + $featureSelect.trigger('change'); + } +}); diff --git a/modules/customfeaturetab/views/js/index.php b/modules/customfeaturetab/views/js/index.php new file mode 100644 index 00000000..30c35169 --- /dev/null +++ b/modules/customfeaturetab/views/js/index.php @@ -0,0 +1,8 @@ +{l s='Product Details' d='Shop.Theme.Catalog'} - {foreach from=$product.extraContent item=extra key=extraKey} {/foreach} +