s); $ret = $this->db->executeS($query); break; case 'prepareQuery': $query = new DbQuery(); $query->select('i.id_product AS id, c, a, f, g')->from('af_index', 'i'); switch ($params['order']['by']) { case 'n': if ($this->settings['indexation']['n']) { $query->select('n_'.(int)$params['id_lang'].' AS n'); } else { $on = 'pl.id_product = i.id_product AND pl.id_shop = i.id_shop AND pl.id_lang = '.(int)$params['id_lang']; $query->select('pl.name AS n')->leftJoin('product_lang', 'pl', $on); } break; case 'd': case 'r': $query->select($params['order']['by']); // ordering in PHP is faster on large catalogues if($params['order']['way'] == 'asc' && $params['order']['by'] == 'r'){ if(isset($_REQUEST['s']) && !empty($_REQUEST['s']) && $_REQUEST['s2'] != 3){ $search_term = $_REQUEST['s']; $query->leftJoin('product_lang','pl','i.`id_product` = pl.`id_product`'); $query->where('(i.`r` LIKE "%'.$search_term.'%" OR pl.name LIKE "%'.$search_term.'%" )'); $query->orderBy('(CASE WHEN i.`r` LIKE "'.$search_term.'%" THEN 1 ELSE 2 END),(CASE WHEN pl.name LIKE "'.$search_term.'%" THEN 1 ELSE 2 END)'); }else{ $query->leftJoin('acdc_special_lookup','asl','i.`r` = asl.`reference`'); $query->leftJoin('product_sale','ps','i.`id_product` = ps.`id_product`'); $query->orderBy('(CASE WHEN asl.`reference` IS NOT NULL THEN 1 ELSE 2 END)'); $query->orderBy('ps.`quantity` DESC'); } } } foreach (array('s', 'q', 'm') as $c_name) { if (isset($params['available_options'][$c_name]) || ($c_name == 'm' && $params['order']['by'] == 'manufacturer_name')) { $query->select($c_name); } } if (isset($params['available_options']['t']) && $this->settings['indexation']['t']) { $query->select('t_'.(int)$params['id_lang'].' AS t'); } if (isset($params['available_options']['w']) || isset($params['sliders']['w'])) { $query->select('w'); } if (!empty($params['p_identifier'])) { $query->select($params['p_identifier'].' AS p'); } foreach ($this->indexationData('queryRestrictions', $params) as $restriction) { $query->where($restriction); } $ret = $query; break; case 'queryRestrictions': $ret = array( 'id_shop' => 'i.id_shop = '.(int)$params['id_shop'], 'visibility' => 'v <> '.($params['current_controller'] == 'search' ? 1 : 2), // visibility 'none' is excluded during indexation ); if ($params['current_controller'] == 'category') { $ret['controller'] = 'FIND_IN_SET('.(int)$params['id_parent_cat'].', i.c) > 0'; } elseif ($params['current_controller'] == 'manufacturer') { $ret['controller'] = 'i.m = '.(int)$params['id_manufacturer']; } elseif ($params['current_controller'] == 'supplier') { $ret['controller'] = 'FIND_IN_SET('.(int)$params['id_supplier'].', i.s) > 0'; } elseif ($params['current_controller'] != 'index' && $params['current_controller'] != 'seopage') { // newproducts, pricesdrop, bestsales, search $imploded_cpids = $this->formatIDs($params['controller_product_ids'], true) ?: 0; $ret['controller'] = 'i.id_product IN ('.pSQL($imploded_cpids).')'; } break; case 'erase': $sql = 'DELETE FROM '.pSQL($this->i['table']).' WHERE 1'; foreach (array('id_product', 'id_shop') as $c_name) { if (isset($params[$c_name]) && $imploded_ids = $this->formatIDs($params[$c_name], true)) { $sql .= ' AND '.$c_name.' IN ('.($imploded_ids).')'; } } $ret &= $this->db->execute($sql); break; case 'get_ids': $ret = array_column($this->db->executeS(' SELECT id_product AS id FROM '.pSQL($this->i['table']).' WHERE id_shop = '.(int)$params['id_shop'].' '), 'id', 'id'); break; } return $ret; } public function indexationInfo($type, $shop_ids = array(), $remove_unused = false) { $ret = array(); $shop_ids = $shop_ids ?: $this->shop_ids; switch ($type) { case 'ids': foreach ($shop_ids as $id_shop) { $indexed = $this->indexationData('get_ids', array('id_shop' => $id_shop)); $required = $this->getProductIDsForIndexation($id_shop); $ret[$id_shop]['indexed'] = array_intersect($required, $indexed); $ret[$id_shop]['missing'] = array_diff($required, $indexed); if ($remove_unused && $unused_ids = array_diff($indexed, $required)) { $this->unindexProducts($unused_ids, array($id_shop)); } } break; case 'count': $ret = $this->indexationInfo('ids', $shop_ids, $remove_unused); foreach ($ret as $id_shop => $data) { foreach ($data as $key => $ids) { $ret[$id_shop][$key] = count($ids); } } break; } return $ret; } public function hookDisplayBackOfficeHeader() { if (Tools::getValue('controller') == 'AdminProducts') { // reindexProduct after mass combinations generation if ($this->is_17) { $this->addJqueryBO(); $js_path = $this->_path.'views/js/attribute-indexer.js?v='.$this->version; $this->context->controller->js_files[] = $js_path; $ajax_path = 'index.php?controller=AdminModules&configure='.$this->name. '&token='.Tools::getAdminTokenLite('AdminModules').'&ajax=1'; return $this->bo()->assignJsVars(array('af_ajax_action_path' => $ajax_path)); } elseif (!empty($this->context->cookie->af_index_product)) { $this->indexProduct($this->context->cookie->af_index_product); $this->context->cookie->__unset('af_index_product'); } return; } elseif (Tools::getValue('configure') != $this->name) { return; } $this->addJqueryBO(); $this->context->controller->addJqueryUI('ui.sortable'); $this->context->controller->css_files[$this->_path.'views/css/back.css?v='.$this->version] = 'all'; if ($this->is_17) { $this->context->controller->css_files[$this->_path.'views/css/back-17.css?'.$this->version] = 'all'; } $this->context->controller->js_files[] = $this->_path.'views/js/back.js?v='.$this->version; if (!empty($this->sp)) { $sp_path = _MODULE_DIR_.$this->sp->name.'/'; $this->context->controller->js_files[] = $sp_path.'views/js/back.js?v='.$this->sp->version; $this->context->controller->css_files[$sp_path.'views/css/back.css?v='.$this->sp->version] = 'all'; } // mce $this->context->controller->addJS(__PS_BASE_URI__.'js/tiny_mce/tiny_mce.js'); $this->context->controller->addJS(__PS_BASE_URI__.'js/admin/tinymce.inc.js'); $js_vars = array( 'savedTxt' => $this->saved_txt, 'errorTxt' => $this->error_txt, 'deletedTxt' => $this->l('Deleted'), 'areYouSureTxt' => $this->l('Are you sure?'), ); return $this->bo()->assignJsVars($js_vars); } public function addJqueryBO() { $this->defineSettings(); if (empty($this->context->jqueryAdded)) { version_compare(_PS_VERSION_, '1.7.6.0', '>=') ? $this->context->controller->setMedia() : $this->context->controller->addJquery(); $this->context->jqueryAdded = 1; } } public function ajaxAction($action) { $ret = array(); switch ($action) { case 'CallTemplateForm': $id_template = Tools::getValue('id_template'); $ret = $this->callTemplateForm($id_template); break; case 'RunProductIndexer': $this->ajaxRunProductIndexer(Tools::getValue('all_identifier')); break; case 'SaveMultipleSettings': $ret['saved'] = true; foreach (Tools::getValue('submitted_forms') as $type => $data) { $submitted_settings = $this->parseStr($data); $ret['saved'] &= $this->saveSettings($type, $submitted_settings, null, true); } break; case 'SaveTemplate': case 'DuplicateTemplate': case 'DeleteTemplate': case 'EraseIndex': case 'UpdateHook': $method = 'ajax'.$action; $this->$method(); break; case 'ToggleActiveStatus': $id_template = Tools::getValue('id_template'); $active = Tools::getValue('active'); $ret = array('success' => $this->toggleActiveStatus($id_template, $active)); break; case 'ShowAvailableFilters': $available_filters = $this->getAvailableFiltersSorted(); $this->context->smarty->assign(array('available_filters' => $available_filters)); $html = $this->display(__FILE__, 'views/templates/admin/available-filters.tpl'); $ret['content'] = utf8_encode($html); $ret['title'] = utf8_encode($this->l('Available filtering criteria')); break; case 'RenderFilterElements': $keys = explode(',', Tools::getValue('keys')); $html = ''; $this->assignLanguageVariables(); foreach ($keys as $key) { $this->context->smarty->assign(array('filter' => $this->getFilterData($key))); $html .= $this->display(__FILE__, 'views/templates/admin/filter-form.tpl'); } $ret['html'] = utf8_encode($html); break; case 'SaveAvailableCustomerFilters': $filters = Tools::getValue('customer_filters'); $filters = $filters ? Tools::jsonEncode($filters) : ''; $ret = array('success' => Configuration::updateValue('AF_SAVED_CUSTOMER_FILTERS', $filters)); break; case 'UpdateModulePosition': case 'DisableModule': case 'UnhookModule': case 'UninstallModule': case 'EnableModule': $id_module = Tools::getValue('id_module'); $hook_name = Tools::getValue('hook_name'); $id_hook = Hook::getIdByName($hook_name); $module = Module::getInstanceById($id_module); if (Validate::isLoadedObject($module)) { if ($action == 'UpdateModulePosition') { $new_position = Tools::getValue('new_position'); $way = Tools::getValue('way'); $ret['saved'] = $module->updatePosition($id_hook, $way, $new_position); } elseif ($action == 'DisableModule') { $module->disable(); $ret['saved'] = !$module->isEnabledForShopContext(); } elseif ($action == 'UnhookModule') { $ret['saved'] = $module->unregisterHook($id_hook, $this->shop_ids); } elseif ($action == 'UninstallModule') { if ($id_module != $this->id) { $ret['saved'] = $module->uninstall(); } } elseif ($action == 'EnableModule') { $ret['saved'] = $module->enable(); } } break; case 'IndexProduct': $ret['indexed'] = $this->indexProduct(Tools::getValue('id_product')); break; case 'addOverride': case 'removeOverride': $override = Tools::getValue('override'); $ret['processed'] = $this->processOverride($action, $override); break; case 'clearCache': $this->cache('clear', ''); $ret['notice'] = utf8_encode($this->l('Cleared')); break; case 'getCachingInfo': $ret['info'] = array(); foreach (array_keys($this->getSettingsFields('caching', false)) as $name) { $ret['info'][$name] = utf8_encode($this->cache('info', $name)); } break; } exit(Tools::jsonEncode($ret)); } public function getAvailableFiltersSorted() { $filters = $this->getAvailableFilters(); $sorted = array(); foreach ($filters as $key => $f) { if ($key == 'c') { $f['name'] = $this->l('Subcategories of current page'); } $sorted[$f['prefix']][$key] = $f; } return $sorted; } public function processOverride($action, $override, $throw_error = true) { $processed = false; switch ($action) { case 'addOverride': case 'removeOverride': $file_path = $this->custom_overrides_dir.$override; $tmp_path = $this->local_path.'override/'.$override; if (file_exists($file_path)) { if (is_writable(dirname($tmp_path))) { try { // temporarily copy file to /override/ folder for processing it natively Tools::copy($file_path, $tmp_path); $class_name = basename($override, '.php'); $processed = $this->$action($class_name); unlink($tmp_path); } catch (Exception $e) { unlink($tmp_path); if ($throw_error) { $this->throwError($e->getMessage()); } } } elseif ($throw_error) { $dir_name = str_replace(_PS_ROOT_DIR_, '', dirname($tmp_path)).'/'; $txt = $this->l('Make sure the following directory is writable: %s'); $this->throwError(sprintf($txt, $dir_name)); } } break; } return $processed; } public function getImplodedContextShopIds() { return implode(', ', $this->shop_ids); } public function getContent() { if (Tools::isSubmit('ajax') && $action = Tools::getValue('action')) { $this->defineSettings(); if (!empty($this->sp) && Tools::getValue('sp')) { $this->sp->ajaxAction($action); } elseif (Tools::getValue('mergedValues')) { $this->mergedValues()->ajaxAction($action); } else { $this->ajaxAction($action); } } $this->bo()->setWarningsIfRequired(); $this->indexationColumns('adjust', 0, 86400); // just to be sure $available_customer_filters = $this->getAvailableFilters(false); $to_unset = array_merge(array('p', 'w'), array_keys($this->getSpecialFilters())); foreach ($to_unset as $k) { unset($available_customer_filters[$k]); } $settings = array(); foreach ($this->getSettingsKeys() as $type) { $settings[$type] = $this->getSettingsFields($type); } $indexation_required = false; $indexation_info = $this->indexationInfo('count', $this->shop_ids, true); foreach ($indexation_info as $id_shop => $data) { $indexation_info[$id_shop]['shop_name'] = $this->db->getValue(' SELECT name FROM '._DB_PREFIX_.'shop WHERE id_shop = '.(int)$id_shop.' '); if ($data['missing']) { $indexation_required = true; } } $smarty_variables = array( 'indexation_data' => $indexation_info, 'indexation_required' => $indexation_required, 'grouped_templates' => $this->getGroupedTemplates(), 'available_hooks' => $this->getAvailableHooks(), 'settings' => $settings, 'available_customer_filters' => $available_customer_filters, 'saved_customer_filters' => $this->getAdjustableCustomerFilters(), 'overrides_data' => $this->getOverridesData(), 'this' => $this, 'changelog_link' => $this->_path.'Readme.md?v='.$this->version, 'documentation_link' => $this->_path.'readme_en.pdf?v='.$this->version, 'contact_us_link' => 'https://addons.prestashop.com/en/write-to-developper?id_product=18575', 'other_modules_link' => 'https://addons.prestashop.com/en/2_community-developer?contributor=64815', 'files_update_warnings' => $this->bo()->getFilesUpdadeWarnings(), ); if (!empty($this->sp)) { $smarty_variables['sp'] = $this->sp->getConfigSmartyVariables(); } $this->context->smarty->assign($smarty_variables); $this->mergedValues()->assignConfigVariables(); $html = $this->display(__FILE__, 'views/templates/admin/configure.tpl'); return $html; } public function getGroupedTemplates($available_controllers = array()) { $grouped_templates = array(); $templates_multishop = $this->db->executeS(' SELECT * FROM '._DB_PREFIX_.'af_templates WHERE id_shop IN ('.pSQL($this->getImplodedContextShopIds()).') GROUP BY id_template ORDER BY id_template DESC, id_shop = '.(int)$this->id_shop.' DESC '); $available_controllers = $available_controllers?: $this->getAvailableControllers(true); $multiple_id_controllers = $this->getControllersWithMultipleIDs(false); foreach ($available_controllers as $c => $title) { if (!isset($multiple_id_controllers[$c])) { $c = 'other'; $title = $this->l('other pages'); } $grouped_templates[$c] = array( 'title' => sprintf($this->l('Templates for %s'), Tools::strtolower($title)), 'first' => !count($grouped_templates), 'additional_actions' => $c != 'other', 'templates' => array(), ); } foreach ($templates_multishop as $t) { $c = $t['template_controller']; if (isset($available_controllers[$c])) { $group = isset($multiple_id_controllers[$c]) ? $c : 'other'; if (isset($grouped_templates[$group])) { $grouped_templates[$group]['templates'][$t['id_template']] = $t; } } } foreach ($grouped_templates as $g => $t) { if ($t['templates']) { $min_id = min(array_keys($t['templates'])); $grouped_templates[$g]['templates'][$min_id]['first_in_group'] = 1; } } return $grouped_templates; } public function getGroupOptions($type, $id_lang) { $group_options = array(); switch ($type) { case 'attribute': foreach (AttributeGroup::getAttributesGroups($id_lang) as $g) { $name = $g['public_name'].($g['name'] != $g['public_name'] ? ' ('.$g['name'].')' : ''); $group_options[$g['id_attribute_group']] = $name; } break; case 'feature': foreach (Feature::getFeatures($id_lang) as $f) { $group_options[$f['id_feature']] = $f['name']; } break; } return $group_options; } public function getOverridesData() { $data_fetching_txt = $this->l('Required to avoid double data fetching on %s'); $notes = array( 'Product' => sprintf($data_fetching_txt, $this->l('prices drop and new products pages')), 'ProductSale' => sprintf($data_fetching_txt, $this->l('bestsellers page')), 'Search' => sprintf($data_fetching_txt, $this->l('search results page')), 'Manufacturer' => sprintf($data_fetching_txt, $this->l('manufacturer pages')), 'Supplier' => sprintf($data_fetching_txt, $this->l('supplier pages')), 'AdminProductsController' => $this->l('Required for proper indexation on saving the product'), 'FrontController' => $this->l('Required for applying custom sorting and number of products per page'), ); if ($this->is_17) { $notes['Product'] = $this->l('Required for improved performance on Search results page'); } $autoload = PrestaShopAutoload::getInstance(); $overrides = array(); foreach (Tools::scandir($this->custom_overrides_dir, 'php', '', true) as $file) { $class_name = basename($file, '.php'); if ($class_name != 'index' && (!$this->is_17 || $class_name == 'Product')) { $path = $autoload->getClassPath($class_name.'Core'); $overrides[$class_name] = array( 'note' => isset($notes[$class_name]) ? $notes[$class_name] : '', 'path' => $path, 'installed' => $this->isOverrideInstalled($path), ); } } return $overrides; } public function isOverrideInstalled($path) { $shop_override_path = _PS_OVERRIDE_DIR_.$path; $module_override_path = $this->custom_overrides_dir.$path; $methods_to_override = $already_overriden = array(); if (file_exists($module_override_path)) { $lines = file($module_override_path); foreach ($lines as $line) { // note: this check is available only for public functions if (Tools::substr(trim($line), 0, 6) == 'public') { $key = trim(current(explode('(', $line))); $methods_to_override[$key] = 0; } } } $name_length = Tools::strlen($this->name); if (file_exists($shop_override_path)) { $lines = file($shop_override_path); foreach ($lines as $i => $line) { if (Tools::substr(trim($line), 0, 6) == 'public') { $key = trim(current(explode('(', $line))); if (isset($methods_to_override[$key])) { unset($methods_to_override[$key]); // if there is no comment about installed override if (!isset($lines[$i - 4]) || Tools::substr(trim($lines[$i - 4]), - $name_length) !== $this->name) { $key = explode('function ', $key); if (isset($key[1])) { $already_overriden[] = $key[1].'()'; } } } } } } $installed = (bool)!$methods_to_override; if ($already_overriden) { $installed = implode(', ', $already_overriden); } return $installed; } public function getSettingsFields($type, $fill_values = true, $id_shop = false) { $fields = array(); switch ($type) { case 'general': $fields = $this->getGeneralSettingsFields(); break; case 'caching': $fields = $this->getCachingSettingsFields(); break; case 'indexation': $fields = $this->getIndexationSettingsFields(); if ($fill_values) { $this->markBlockedIndexationFields($fields); } break; case 'seopage': if (!empty($this->sp)) { $fields = $this->sp->getSettingsFields(); } break; default: $fields = $this->getSelectorSettingsFields($type); break; } if ($fill_values) { if (!$id_shop && isset($this->settings[$type])) { $saved_settings = $this->settings[$type]; } else { $saved_settings = $this->db->getValue(' SELECT value FROM '._DB_PREFIX_.'af_settings WHERE type = \''.pSQL($type).'\' AND id_shop = '.(int)$id_shop.' '); $saved_settings = $saved_settings ? Tools::jsonDecode($saved_settings, true) : array(); } foreach ($fields as $name => &$f) { if (isset($saved_settings[$name]) && empty($f['blocked'])) { $f['value'] = $saved_settings[$name]; } } } return $fields; } public function markBlockedIndexationFields(&$fields) { $suffixes_count = $this->indexationColumns('getAvailableSuffixesCount', 0, 3600); $check_values = array('p_c' => 'currency', 'p_g' => 'group', 'n' => 'lang', 't' => 'lang'); foreach ($check_values as $key => $type) { if (isset($suffixes_count[$type]) && $suffixes_count[$type] > $this->i['max_column_suffixes']) { $fields[$key]['blocked'] = 1; $fields[$key]['value'] = 0; } } } public function getGeneralSettingsFields() { $get_settings = $this->db->getValue('SELECT value FROM ' . _DB_PREFIX_ . 'af_settings where type= "general"'); $get_settings = $get_settings ? Tools::jsonDecode($get_settings, true) : array(); $fields = array( 'layout' => array( 'display_name' => $this->l('Display type'), 'type' => 'select', 'value' => 'vertical', 'options' => $this->getOptions('layout'), ), 'count_data' => array( 'display_name' => $this->l('Show numbers of matches'), 'value' => 1, 'type' => 'switcher', ), 'hide_zero_matches' => array( 'display_name' => $this->l('Hide options with zero matches'), 'value' => 1, 'type' => 'switcher', ), 'dim_zero_matches' => array( 'display_name' => $this->l('Dim options with zero matches'), 'value' => 1, 'type' => 'switcher', ), 'sf_position' => array( 'display_name' => $this->l('Display selected filters'), 'value' => 0, 'type' => 'select', 'options' => array( 0 => $this->l('Above filter block'), 1 => $this->l('Above product list'), ), ), 'include_group' => array( 'display_name' => $this->l('Show group name in selected filters'), 'value' => 0, 'type' => 'switcher', ), 'compact' => array( 'display_name' => $this->l('Screen width for compact layout'), 'tooltip' => $this->l('Use compact layout if browser width is equal to this value or less'), 'type' => 'text', 'input_suffix' => 'px', 'value' => 767, 'validate' => 'isInt', 'related_options' => '.compact-option', 'subtitle' => $this->l('Responsive compact view'), ), 'compact_offset' => array( 'display_name' => $this->l('Compact panel offset direction'), 'type' => 'select', 'value' => 2, 'options' => array(1 => $this->l('Left'), 2 => $this->l('Right')), 'validate' => 'isInt', 'class' => 'compact-option hidden-on-0', ), 'compact_btn' => array( 'display_name' => $this->l('Compact button'), 'type' => 'select', 'value' => 1, 'options' => array( 1 => $this->l('Text + Filter icon'), 2 => $this->l('Only text'), 3 => $this->l('Only filter icon'), ), 'validate' => 'isInt', 'class' => 'compact-option hidden-on-0', ), 'npp' => array( 'display_name' => $this->l('Number of products per page'), 'value' => Configuration::get('PS_PRODUCTS_PER_PAGE'), 'type' => 'text', 'validate' => 'isInt', 'subtitle' => $this->l('Product list'), ), 'default_order_by' => array( 'display_name' => $this->l('Default order by'), 'value' => Tools::getProductsOrder('by'), 'type' => 'select', 'options' => $this->getOptions('orderby'), 'related_options' => '.order-by-option', ), 'default_order_way' => array( 'display_name' => $this->l('Default order way'), 'value' => Tools::getProductsOrder('way'), 'type' => 'select', 'options' => $this->getOptions('orderway'), 'class' => 'order-by-option hidden-on-random', ), 'random_upd' => array( 'display_name' => $this->l('Update random order'), 'value' => 1, 'type' => 'select', 'options' => array( 0 => $this->l('On every page load'), 1 => $this->l('Every hour'), 2 => $this->l('Every day'), 3 => $this->l('Every week'), ), 'class' => 'order-by-option visible-on-random', ), 'reload_action' => array( 'display_name' => $this->l('Update product list'), 'value' => 1, 'type' => 'select', 'options' => array( 1 => $this->l('Instantly'), 2 => $this->l('On button click'), ), ), 'p_type' => array( 'display_name' => $this->l('Pagination type'), 'value' => 1, 'type' => 'select', 'options' => array( 1 => $this->l('Regular'), 2 => $this->l('Load more button'), 3 => $this->l('Infinite scroll'), ), ), 'autoscroll' => array( 'display_name' => $this->l('Autoscroll to top after filtration'), 'tooltip' => $this->l('After applying filters, switching pages, changing sorting, etc...'), 'value' => $get_settings['autoscroll']!= 0?$get_settings['autoscroll']:0, 'type' => 'switcher', ), 'oos_behaviour' => array( 'display_name' => $this->l('Out of stock behaviour'), 'value' => $get_settings['oos_behaviour']!= 0?$get_settings['oos_behaviour']:0, 'type' => 'select', 'options' => array( 0 => $this->l('Do nothing'), 1 => $this->l('Move out of stock products to the end of the list'), 2 => $this->l('Exclude products that are out of stock except those allowed for ordering'), 3 => $this->l('Exclude all products that are out of stock'), ), 'subtitle' => $this->l('Stock and combinations'), ), 'combinations_stock' => array( 'display_name' => $this->l('Count stock for combinations'), 'tooltip' => $this->l('Count stock basing on selected attributes'), 'value' => 0, 'type' => 'switcher', ), 'combinations_existence' => array( 'display_name' => $this->l('Check combinations existence'), 'tooltip' => $this->l('Exclude products that do not have combinations with selected attributes'), 'value' => 0, 'type' => 'switcher', ), 'combination_results' => array( 'display_name' => $this->l('Display combination prices/images'), 'tooltip' => $this->l('Display prices/images basing on selected attributes'), 'value' => 0, 'type' => 'switcher', ), 'url_filters' => array( 'display_name' => $this->l('Include filter parameters in URL'), 'value' => 1, 'type' => 'switcher', 'subtitle' => $this->l('Dynamic URL params'), ), 'url_sorting' => array( 'display_name' => $this->l('Include sorting parameter in URL'), 'value' => 1, 'type' => 'switcher', ), 'url_page' => array( 'display_name' => $this->l('Include page number in URL'), 'value' => 1, 'type' => 'switcher', ), 'dec_sep' => array( 'display_name' => $this->l('Decimal separator'), 'type' => 'text', 'value' => '.', 'subtitle' => $this->l('Number format (Used in numeric sliders and sorting by numbers)'), ), 'tho_sep' => array( 'display_name' => $this->l('Thousand separator'), 'type' => 'text', 'value' => '', ), ) + $this->mergedValues()->getGeneralSettingsFields(); foreach (array('combinations_stock', 'combinations_existence', 'combination_results') as $name) { $fields[$name]['warning'] = $this->l('May increase filtering time'); } if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { $warning_txt = $this->l('Not compatible with advanced stock management'); foreach (array('oos_behaviour', 'combinations_stock') as $name) { $fields[$name]['warning'] = $warning_txt; } } return $fields; } public function getCachingSettingsFields() { $fields = array( 'c_list' => array('display_name' => $this->l('Category options'), 'value' => 0), 'a_list' => array('display_name' => $this->l('Attribute options'), 'value' => 1), 'f_list' => array('display_name' => $this->l('Feature options'), 'value' => 1), 'comb_data' => array('display_name' => $this->l('Combinations availability'), 'value' => 1), ); foreach ($fields as $name => &$f) { $f += array('type' => 'switcher', 'class' => $name); } return $fields; } public function getSelectorSettingsFields($type) { $fields = array(); if ($selectors = $this->getSelectors($type)) { $input_prefix = '.'; $validate = 'isImageTypeName'; // a-zA-Z0-9_ - if ($type == 'themeid') { $input_prefix = '#'; $validate = 'isHookName'; // a-zA-Z0-9_- no spaces } elseif ($type == 'iconclass') { $fields['load_font'] = array( 'display_name' => $this->l('Load icon font'), 'tooltip' => $this->l('Use this option if your theme does not support icon-xx classes'), 'value' => $this->is_17 ? 1 : 0, 'type' => 'switcher', ); } foreach ($selectors as $name => $display_name) { $fields[$name] = array( 'display_name' => $display_name, 'value' => $name, 'type' => 'text', 'input_prefix' => $input_prefix, 'validate' => $validate, 'required' => 1, ); } } return $fields; } public function getIndexationSettingsFields() { $fields = array( 'auto' => array( 'display_name' => $this->l('Re-index products on saving programmatically'), 'info' => $this->l('After calling hook ActionProductUpdate or ActionProductAdd during bulk import'), 'type' => 'switcher', 'value' => 1, ), 'subcat_products' => array( 'display_name' => $this->l('Index associations for all products from subcategories'), 'info' => $this->l('Even if they are not directly associated to current category'), 'value' => 1, 'type' => 'switcher', ), 'p' => array( 'display_name' => $this->l('Include price data in indexation'), 'info' => $this->l('Required if you want to filter/sort products by price'), 'type' => 'switcher', 'value' => 1, 'related_options' => '.indexation-price-option', ), 'p_c' => array( 'display_name' => $this->l('Index prices for different currencies'), 'info' => $this->l('Required if you have specific price rules only for selected currencies'), 'type' => 'switcher', 'value' => 0, 'class' => 'indexation-price-option hidden-on-0', ), 'p_g' => array( 'display_name' => $this->l('Index prices for different customer groups'), 'info' => $this->l('Required if you have specific price rules only for selected customer groups'), 'type' => 'switcher', 'value' => 0, 'class' => 'indexation-price-option hidden-on-0', ), 't' => array( 'display_name' => $this->l('Include tags data in indexation'), 'info' => $this->l('Required if you want to filter products by tags'), 'type' => 'switcher', 'value' => 0, ), 'n' => array( 'display_name' => $this->l('Include product name in indexation'), 'info' => $this->l('Can make sorting by name faster on very large catalogues (30 000+ products)'), 'type' => 'switcher', 'value' => 0, ), ); return $fields; } public function getSelectors($type) { $selectors = array(); switch ($type) { case 'iconclass': $selectors = array( 'icon-filter' => $this->l('Filter icon'), 'u-times' => $this->l('Remove one filter icon'), 'icon-eraser' => $this->l('Remove all filters icon'), 'icon-lock' => $this->l('Locked filters icon'), 'icon-unlock-alt' => $this->l('Unlocked filters icon'), // 'icon-refresh icon-spin' => $this->l('Loading indicator icon'), // not used 'icon-minus' => $this->l('Minus icon'), 'icon-plus' => $this->l('Plus icon'), 'icon-check' => $this->l('Checked icon'), 'icon-save' => $this->l('Save icon'), ); break; case 'themeclass': $selectors = array( 'js-product-miniature' => $this->l('Product list item'), 'pagination' => $this->l('Pagination container'), ); if (!$this->is_17) { $selectors = array( 'ajax_block_product' => $selectors['js-product-miniature'], 'pagination' => $selectors['pagination'], 'product-count' => $this->l('Product count countainer'), 'heading-counter' => $this->l('Total matches container'), ); } break; case 'themeid': $selectors = array('main' => $this->l('Main column container')); if (!$this->is_17) { $selectors = array( 'center_column' => $selectors['main'], 'pagination' => $this->l('Top pagination wrapper'), 'pagination_bottom' => $this->l('Bottom pagination wrapper'), ); } break; } return $selectors; } public function saveSettings($type, $values = array(), $shop_ids = null, $throw_error = false, $fields = null) { if ($fields = $fields ?: $this->getSettingsFields($type, false)) { $settings_to_save = $settings_rows = array(); $this->addRecommendedValuesIfRequired($type, $values); $errors = $this->validateSettings($values, $fields); // values that didn't pass validation are updated if ($errors && $throw_error) { $this->throwError($errors); } foreach ($fields as $name => $field) { $settings_to_save[$name] = isset($values[$name]) ? $values[$name] : $field['value']; } $encoded_settings = Tools::jsonEncode($settings_to_save); $shop_ids = $shop_ids ?: $this->shop_ids; if ($type == 'indexation') { $shop_ids = $this->all_shop_ids; } foreach ($shop_ids as $id_shop) { $settings_rows[] = '('.(int)$id_shop.', \''.pSQL($type).'\', \''.pSQL($encoded_settings).'\')'; } if ($settings_rows && $settings_to_save && $saved = $this->db->execute(' REPLACE INTO '._DB_PREFIX_.'af_settings VALUES '.implode(', ', $settings_rows).' ')) { $this->settings[$type] = $settings_to_save; if ($type == 'indexation' && empty($this->installation_process)) { $this->cache('clear', 'indexationColumns'); $this->indexationColumns('adjust'); } return $saved; } } } public function addRecommendedValuesIfRequired($type, &$values) { switch ($type) { case 'indexation': $check_values = array( 'p_c' => array(array('id_currency', 'specific_price')), 'p_g' => array(array('id_group', 'specific_price'), array('reduction', 'group')), 't' => array(array('id_tag', 'product_tag')), ); foreach ($check_values as $key => $data) { if (!isset($values[$key])) { $value = true; foreach ($data as $d) { $value &= $this->db->getValue('SELECT '.pSQL($d[0]).' FROM '._DB_PREFIX_.pSQL($d[1])); } $values[$key] = (int)$value; } } break; } } public function defineSettings() { if (!isset($this->settings)) { $this->settings = $this->getSavedSettings(); require_once($this->local_path.'classes/ExtendedTools.php'); if (Module::isEnabled('af_seopages')) { $this->sp = Module::getInstanceByName('af_seopages'); } } } public function getSavedSettings($id_shop = false) { $settings = array(); $id_shop = $id_shop ?: $this->context->shop->id; $data = $this->db->executeS('SELECT * FROM '._DB_PREFIX_.'af_settings WHERE id_shop = '.(int)$id_shop); foreach ($data as $row) { $settings[$row['type']] = Tools::jsonDecode($row['value'], true); } return $settings; } public function getSettingsKeys() { return array('general', 'iconclass', 'themeclass', 'themeid', 'caching', 'indexation', 'seopage'); } public function getLayoutClasses() { return $this->settings['iconclass'] + $this->settings['themeclass']; } public function getProductIDsForIndexation($id_shop) { return array_column($this->db->executeS(' SELECT p.id_product AS id FROM '._DB_PREFIX_.'product p INNER JOIN '._DB_PREFIX_.'product_shop ps ON ps.id_product = p.id_product AND ps.id_shop = '.(int)$id_shop.' AND ps.id_product > 0 AND ps.active = 1 AND ps.visibility <> "none" '), 'id', 'id'); } public function getCurrentHook() { $hooks = array_flip($this->getAvailableHooks()); return isset($hooks[1]) ? $hooks[1] : ''; } public function getAvailableHooks() { $methods = get_class_methods(__CLASS__); $methods_to_exclude = array( 'hookDisplayBackOfficeHeader', 'hookDisplayHeader', 'hookDisplayCustomerAccount', 'hookDisplayHome' ); $available_hooks = array(); $hook_found = false; foreach ($methods as $m) { if (Tools::substr($m, 0, 11) === 'hookDisplay' && !in_array($m, $methods_to_exclude)) { $hook_name = str_replace('hookDisplay', 'display', $m); $selected = 0; if (!$hook_found && $this->isRegisteredInHook($hook_name)) { $hook_found = $selected = 1; } $available_hooks[$hook_name] = $selected; } } ksort($available_hooks); return $available_hooks; } /* * this method is overriden in order to take current shop context in consideration */ public function isRegisteredInHook($hook_name) { return $this->db->getValue(' SELECT COUNT(*) FROM '._DB_PREFIX_.'hook_module hm LEFT JOIN '._DB_PREFIX_.'hook h ON (h.id_hook = hm.id_hook) WHERE h.name = \''.pSQL($hook_name).'\' AND hm.id_module = '.(int)$this->id.' AND id_shop IN ('.pSQL($this->getImplodedContextShopIds()).') '); } public function verifyMethod($method_name) { if (!method_exists($this, $method_name)) { $this->throwError($this->l('Unknown method:').' '.$method_name); } } public function callTemplateForm($id_template, $full = true) { $available_controllers = $this->getAvailableControllers(true); if (!$id_template) { $controller = Tools::getValue('template_controller'); $name = isset($available_controllers[$controller]) ? $available_controllers[$controller] : $controller; $template_name = sprintf($this->l('Template for %s'), $name).' - '.date('Y-m-d H:i:s'); $id_template = $this->saveTemplate($id_template, $controller, $template_name); } $template_data = $this->db->getRow(' SELECT * FROM '._DB_PREFIX_.'af_templates WHERE id_template = '.(int)$id_template.' ORDER BY id_shop = '.(int)$this->id_shop.' DESC '); $template_data['first_in_group'] = $id_template == $this->db->getValue(' SELECT id_template FROM '._DB_PREFIX_.'af_templates WHERE template_controller = \''.pSQL($template_data['template_controller']).'\' ORDER BY id_template ASC '); $this->context->smarty->assign(array( 'controller_options' => $available_controllers, 't' => $template_data, 'is_17' => $this->is_17, )); if ($full && $template_data) { $template_filters = Tools::jsonDecode($template_data['template_filters'], true); $template_filters_lang = $this->db->executeS(' SELECT id_lang, data FROM '._DB_PREFIX_.'af_templates_lang WHERE id_template = '.(int)$template_data['id_template'].' AND id_shop = '.(int)$template_data['id_shop'].' '); foreach ($template_filters_lang as $multilang_data) { $id_lang = $multilang_data['id_lang']; $data = Tools::jsonDecode($multilang_data['data'], true); foreach ($data as $filter_key => $values) { if (isset($template_filters[$filter_key])) { foreach ($values as $name => $value) { $template_filters[$filter_key][$name][$id_lang] = $value; } } } } foreach ($template_filters as $key => $saved_values) { $template_filters[$key] = $this->getFilterData($key, $saved_values); } $controller = $template_data['template_controller']; $controller_ids = $this->getTemplateControllerIds($id_template, $controller, $template_data['id_shop']); $general_settings_fields = $this->getSettingsFields('general', true); if ($controller == 'search') { $general_settings_fields['default_order_by']['options']['position'] = $this->l('Relevance'); } $this->context->smarty->assign(array( 'template_controller_settings' => $this->getControllerSettingsFields($controller, $controller_ids), 'template_filters' => $template_filters, 'additional_settings' => $template_data['additional_settings'] ? Tools::jsonDecode($template_data['additional_settings'], true) : array(), 'general_settings_fields' => $general_settings_fields, 'additional_actions' => in_array($controller, $this->getControllersWithMultipleIDs(true)), )); } $this->assignLanguageVariables(); $ret = array( 'form_html' => utf8_encode($this->display(__FILE__, 'views/templates/admin/template-form.tpl')), 'id_template' => $id_template, ); return $ret; } public function getTemplateControllerIds($id_template, $controller, $id_shop) { $ids = array(); if (in_array($controller, $this->getControllersWithMultipleIDs())) { $ids = array_column($this->db->executeS(' SELECT DISTINCT id_'.pSQL($controller).' AS id FROM '._DB_PREFIX_.'af_'.pSQL($controller).'_templates WHERE id_template = '.(int)$id_template.' AND id_shop = '.(int)$id_shop.' '), 'id', 'id'); } return $ids; } public function getDefaultAdditionalSettings($controller) { $additional_settings = array(); if ($specific_sorting = $this->getSpecificSorting($controller)) { foreach ($specific_sorting as $name => $value) { $additional_settings['default_order_'.$name] = $value; } } return $additional_settings; } public function assignLanguageVariables() { $this->context->smarty->assign(array( 'available_languages' => $this->getAvailableLanguages(), 'id_lang_current' => $this->context->language->id, )); } public function getAvailableLanguages($only_ids = false, $only_active = false) { $available_languages = array(); foreach (Language::getLanguages($only_active) as $lang) { $available_languages[$lang['id_lang']] = $lang['iso_code']; } return $only_ids ? array_keys($available_languages) : $available_languages; } public function getControllerSettingsFields($controller, $controller_ids) { $fields = array(); $multiple_id_controllers = $this->getControllersWithMultipleIDs(false); if (isset($multiple_id_controllers[$controller])) { $field = array( 'display_name' => $multiple_id_controllers[$controller], 'value' => $controller_ids, 'type' => 'multiple_options', 'options' => $this->getOptions($controller), ); if ($controller == 'category') { $field['id_parent'] = Configuration::get('PS_ROOT_CATEGORY'); } $fields['controller_ids'] = $field; } return $fields; } public function getOptions($type) { $options = array(); switch ($type) { case 'manufacturer': case 'supplier': $items = $this->db->executeS('SELECT * FROM '._DB_PREFIX_.pSQL($type)); foreach ($items as $row) { $options[$row['id_'.$type]] = $row['name']; } break; case 'category': $categories = $this->db->executeS(' SELECT * FROM '._DB_PREFIX_.'category c '.Shop::addSqlAssociation('category', 'c').' LEFT JOIN '._DB_PREFIX_.'category_lang cl ON c.id_category = cl.id_category WHERE id_lang = '.(int)$this->id_lang.' '); foreach ($categories as $cat) { $options[$cat['id_parent']][$cat['id_category']] = $cat['name']; } break; case 'seopage': if (!empty($this->sp)) { $options = $this->sp->getPageOptions(); } break; case 'layout': $options = array( 'vertical' => $this->l('Vertical'), 'horizontal' => $this->l('Horizontal'), ); break; case 'orderby': $options = array( 'position' => $this->l('Position'), 'date_add' => $this->l('Date added'), 'name' => $this->l('Name'), 'reference' => $this->l('Reference'), 'manufacturer_name' => $this->is_17 ? $this->l('Brand name') : $this->l('Manufacturer name'), 'price' => $this->l('Price'), 'quantity' => $this->l('Quantity'), 'sales' => $this->l('Sales'), 'random' => $this->l('Random'), ); break; case 'orderway': $options = array( 'asc' => $this->l('Ascending'), 'desc' => $this->l('Descending') ); break; } return $options; } public function getFilterData($key, $saved_values = array()) { if (!isset($this->available_filters)) { $this->available_filters = $this->getAvailableFilters(); } if (isset($this->available_filters[$key])) { $filter_data = $this->available_filters[$key]; $filter_data['key'] = $key; if ($key == 'c') { $filter_data['prefix'] = $this->l('Subcategories of current page'); } $filter_data['name_original'] = $filter_data['name']; $filter_data['settings'] = $this->getFilterFields($filter_data, $saved_values); $custom_name = $filter_data['settings']['custom_name']['value']; if (is_array($custom_name) && !empty($custom_name[$this->context->language->id])) { $filter_data['name'] = $custom_name[$this->context->language->id]; } } else { $filter_data = array(); } return $filter_data; } public function getFilterFields($filter_data, $saved_values = array()) { $fields = array( 'custom_name' => array( 'display_name' => $this->l('Custom name'), 'value' => '', 'type' => 'text', 'multilang' => 1, 'class' => 'custom-name', ), 'quick_search' => array( 'display_name' => $this->l('Quick search for options'), 'tooltip' => $this->l('If there are more than 10 options'), 'value' => 0, 'type' => 'switcher', 'class' => 'type-exc not-for-3 not-for-4', ), 'slider_prefix' => array( 'display_name' => $this->l('Slider prefix'), 'value' => '', 'type' => 'text', 'multilang' => 1, 'class' => 'type-exc not-for-1 not-for-2 not-for-3 not-for-5', ), 'slider_suffix' => array( 'display_name' => $this->l('Slider suffix'), 'value' => '', 'type' => 'text', 'multilang' => 1, 'class' => 'type-exc not-for-1 not-for-2 not-for-3 not-for-5', ), 'slider_step' => array( 'display_name' => $this->l('Slider step'), 'value' => 1, 'type' => 'text', 'class' => 'type-exc not-for-1 not-for-2 not-for-3 not-for-5', // 'quick' => 1, ), 'range_step' => array( 'display_name' => $this->l('Range step'), 'value' => 100, 'type' => 'text', 'class' => 'type-exc not-for-4', 'quick' => 1, ), 'foldered' => array( 'display_name' => $this->l('Foldered structure'), 'value' => 1, 'type' => 'switcher', 'class' => 'type-exc not-for-3', ), 'nesting_lvl' => array( 'display_name' => $this->l('Nesting level'), 'value' => 0, 'type' => 'select', 'options' => array(0 => $this->l('All'), 1 => 1, 2 => 2), 'input_class' => 'nesting-lvl', ), 'color_display' => array( 'display_name' => $this->l('Color display'), 'value' => 1, 'type' => 'select', 'options' => array( 0 => $this->l('None'), 1 => $this->l('Inline color boxes'), 2 => $this->l('Color boxes with names'), ), 'class' => 'type-exc not-for-4 not-for-3 not-for-5', ), 'visible_items' => array( 'display_name' => $this->l('Max. visible items'), 'value' => 15, 'type' => 'text', 'class' => 'type-exc not-for-4 not-for-3', ), 'sort_by' => array( 'display_name' => $this->l('Sort by'), 'value' => 0, 'type' => 'select', 'options' => array( '0' => $this->l('Name'), 'first_num' => $this->l('First number in name'), 'numbers_in_name' => $this->l('All numbers in name'), 'id' => $this->l('ID'), 'position' => $this->l('Position'), ), 'class' => 'type-exc not-for-4', 'input_class' => 'sort-by', 'quick' => 1, ), 'type' => array( 'display_name' => $this->l('Type'), 'value' => 1, 'type' => 'select', 'options' => array( 1 => $this->l('Checkbox'), 2 => $this->l('Radio button'), 3 => $this->l('Select'), 4 => $this->l('Slider'), 5 => $this->l('Text box'), ), 'quick' => 1, 'input_class' => 'f-type', ), 'minimized' => array( 'display_name' => $this->l('Minimized'), 'value' => 0, 'type' => 'checkbox', 'quick' => 1, ), ); $filter_data['first_char'] = Tools::substr($filter_data['key'], 0, 1); if (!isset($saved_values['slider_prefix']) && !isset($saved_values['slider_suffix'])) { if ($slider_extensions = $this->detectSliderExtensions($filter_data['key'])) { $fields['slider_prefix']['value'] = $slider_extensions['prefix']; $fields['slider_suffix']['value'] = $slider_extensions['suffix']; } } if (!isset($saved_values['visible_items']) && !in_array($filter_data['first_char'], array('a', 'f', 'm', 's', 't'))) { $fields['visible_items']['value'] = ''; } if ($this->settings['general']['layout'] == 'horizontal') { $fields['visible_items']['class'] = 'hidden'; } $this->removeSpecificOptions($filter_data, $fields); foreach ($fields as $name => &$f) { $f['input_name'] = 'filters['.$filter_data['key'].']['.$name.']'; $f['value'] = isset($saved_values[$name]) ? $saved_values[$name] : $f['value']; if (!empty($f['multilang'])) { $f['input_name'] = str_replace('filters', 'filters[multilang]', $f['input_name']); } } return $fields; } public function detectSliderExtensions($key) { $extensions = array(); $first_char = Tools::substr($key, 0, 1); switch ($first_char) { case 'a': // possible numeric sliders case 'f': $id_group = Tools::substr($key, 1); $method = $first_char == 'a' ? 'getAttributes' : 'getFeatures'; foreach ($this->getAvailableLanguages(true) as $id_lang) { $values = $this->$method($id_lang, $id_group); foreach ($values as $i => $val) { if ($i > 3 || isset($extensions['prefix'][$id_lang])) { break; // don't spend many resourses on detecting extensions } $name = $val['name']; if ($number = $this->extractNumberFromString($name)) { $name = explode($number, $name); $possible_prefix = trim(strip_tags($name[0])); $possible_suffix = isset($name[1]) ? trim(strip_tags($name[1])) : ''; if (Tools::strlen($possible_prefix) < 4 && Tools::strlen($possible_suffix) < 4) { $extensions['prefix'][$id_lang] = $possible_prefix; $extensions['suffix'][$id_lang] = $possible_suffix; } } } } break; case 'w': // weight foreach ($this->getAvailableLanguages(true) as $id_lang) { $extensions['prefix'][$id_lang] = ''; $extensions['suffix'][$id_lang] = Configuration::get('PS_WEIGHT_UNIT'); } break; } return $extensions; } public function removeSpecificOptions($filter_data, &$fields) { $special_filters = array_keys($this->getSpecialFilters()); $slider_filters = array('p', 'w'); $numeric_slider_filters = array('a', 'f'); if ($filter_data['first_char'] != 'c') { unset($fields['foldered']); unset($fields['nesting_lvl']); } if ($filter_data['first_char'] != 'c' && $filter_data['first_char'] != 'a') { unset($fields['sort_by']['options']['position']); } if (!in_array($filter_data['key'], $slider_filters)) { unset($fields['range_step']); if (!in_array($filter_data['first_char'], $numeric_slider_filters)) { unset($fields['slider_step']); unset($fields['slider_prefix']); unset($fields['slider_suffix']); unset($fields['type']['options'][4]); } } else { if ($filter_data['key'] == 'p') { // prefix-suffux for price is based on selected currency unset($fields['slider_prefix']); unset($fields['slider_suffix']); } $fields['type']['value'] = 4; } if (in_array($filter_data['key'], $special_filters) || in_array($filter_data['key'], $slider_filters)) { unset($fields['sort_by']); } if (in_array($filter_data['key'], $special_filters)) { if($filter_data['key'] != 'search_filter'){ unset($fields['type']['options'][2]); } unset($fields['type']['options'][3]); unset($fields['type']['options'][4]); unset($fields['visible_items']); $fields['quick_search']['class'] .= ' force-hidden'; } if (empty($filter_data['is_color_group'])) { unset($fields['color_display']); } } public function getParentCategories($id_lang, $id_shop) { $parents_data = $this->db->executeS(' SELECT DISTINCT(cl.id_category) AS id, cl.name AS name, c.position FROM '._DB_PREFIX_.'category c INNER JOIN '._DB_PREFIX_.'category_lang cl ON cl.id_category = c.id_parent AND cl.id_lang = '.(int)$id_lang.' AND cl.id_shop = '.(int)$id_shop.' WHERE c.level_depth > 2 ORDER BY cl.name ASC '); $parent_categories = array(); foreach ($parents_data as $data) { $parent_categories['c'.$data['id']] = $data; } return $parent_categories; } public function getSpecialFilters() { return array( 'newproducts' => $this->l('New products'), 'bestsales' => $this->l('Best sales'), 'pricesdrop' => $this->l('Prices drop'), 'in_stock' => $this->l('In stock'), 'search_filter' => $this->l('SEARCH FILTER'), 'module-iqitwishlist-view' => $this->l('Wish List'), /*'page_number_only' => $this->l('PAGE NUMBER ONLY'), 'item_number_only' => $this->l('ITEM NUMBER ONLY'), 'description_only' => $this->l('DESCRIPTION ONLY'), 'item_number_and_description' => $this->l('ITEM NUMBER AND ITEM DESCRIPTION'), 'that_contain' => $this->l('THAT CONTAINS'),*/ // 'that_contain' => $this->l('THAT START WITH'), ); } public function getStandardFilters() { return array( 'p' => $this->l('Price'), 'w' => $this->l('Weight'), 'm' => $this->l('Manufacturers'), 's' => $this->l('Suppliers'), 't' => $this->l('Tags'), 'q' => $this->l('Condition'), ); } public function getAvailableFilters($include_parents = true) { $id_lang = $this->context->language->id; $id_shop = $this->context->shop->id; $available_filters = array(); // cats $categories = array( 'c' => array( 'id' => 0, 'name' => $this->l('Categories'), 'position' => -1, ), ); if ($include_parents) { $categories += $this->getParentCategories($id_lang, $id_shop); } foreach ($categories as $key => $c) { $c['prefix'] = $this->l('Subcategories'); $available_filters[$key] = $c; } // atts $attribute_groups = AttributeGroup::getAttributesGroups($id_lang); $attribute_groups = $this->sortByKey($attribute_groups, 'position'); foreach ($attribute_groups as $group) { $name = $group['public_name'].($group['name'] != $group['public_name'] ? ' ('.$group['name'].')' : ''); $available_filters['a'.$group['id_attribute_group']] = array( 'id' => $group['id_attribute_group'], 'name' => $name, 'position' => $group['position'], 'prefix' => $this->l('Attribute'), 'is_color_group' => !empty($group['is_color_group']), ); } // feats $features = Feature::getFeatures($id_lang); // sorted by position initially foreach ($features as $f) { $available_filters['f'.$f['id_feature']] = array( 'id' => $f['id_feature'], 'name' => $f['name'], 'position' => $f['position'], 'prefix' => $this->l('Feature'), ); } foreach ($this->getStandardFilters() as $key => $name) { $available_filters[$key] = array( 'id' => 0, 'position' => 0, 'name' => $name, 'prefix' => $this->l('Standard parameter'), ); } foreach ($this->getSpecialFilters() as $key => $name) { $available_filters[$key] = array( 'id' => 0, 'position' => 0, 'name' => $name, 'prefix' => $this->l('Special filter'), ); } return $available_filters; } public function toggleActiveStatus($id_template, $active) { $imploded_shop_ids = $this->getImplodedContextShopIds(); if ($active) { $current_hook = $this->getCurrentHook(); $controller_name = $this->getTemplateControllerById($id_template); if (!$this->isHookAvailableOnControllerPage($current_hook, $controller_name)) { // only left/right column hooks are checked $col_txt = ($current_hook == 'displayLeftColumn') ? $this->l('Left') : $this->l('Right'); $error_txt = sprintf($this->l('%s column is not activated on selected page'), $col_txt); $error_txt .= '. '.$this->howToActivateColumnTxt(); $this->throwError($error_txt); } } $update_query = ' UPDATE '._DB_PREFIX_.'af_templates SET active = '.(int)$active.' WHERE id_template = '.(int)$id_template.' AND id_shop IN ('.pSQL($imploded_shop_ids).') '; return $this->db->execute($update_query) && $this->cache('clear', 'tpl-avl-'); } public function getTemplateControllerById($id_template) { $controller = $this->db->getValue(' SELECT template_controller FROM '._DB_PREFIX_.'af_templates WHERE id_template = '.(int)$id_template.' '); return $controller; } /* * Check if column hook is available on selected page */ public function isHookAvailableOnControllerPage($hook_name, $controller_name) { if ($controller_name == 'seopage') { return true; } $available = true; $columns = array('left', 'right'); foreach ($columns as $col) { if (Tools::strtolower($hook_name) == 'display'.$col.'column') { $page_name = $this->getPageName($controller_name); if ($this->is_17) { $layout = $this->context->shop->theme->getLayoutNameForPage($page_name); $available = $layout == 'layout-both-columns' || $layout == 'layout-'.$col.'-column' || $layout == 'layout-'.$col.'-side-column'; } else { $method_name = 'has'.Tools::ucfirst($col).'Column'; $available = $this->context->theme->$method_name($page_name); } } } return $available; } public function ajaxDuplicateTemplate() { $original_id = Tools::getValue('id_template'); if ($new_id = $this->duplciateTemplate($original_id)) { $ret = $this->callTemplateForm($new_id, false); exit(Tools::jsonEncode($ret)); } else { $this->throwError('Error'); } } public function duplciateTemplate($id_template_original) { $id_template_new = $this->getNewTemplateId(); $sql = array(); foreach ($this->getTemplateAssociatedTables() as $table_name) { $data = $this->db->executeS(' SELECT * FROM '._DB_PREFIX_.pSQL($table_name).' WHERE id_template = '.(int)$id_template_original.' '); $new_rows = array(); foreach ($data as $row) { $row['id_template'] = $id_template_new; if (isset($row['template_name'])) { $row['template_name'] .= ' '.$this->l('copy'); } $row = array_map('pSQL', $row); // note: all possible HTML is stripped here!!! $new_rows[] = '(\''.implode('\', \'', $row).'\')'; } if ($new_rows) { $sql[$table_name] = 'REPLACE INTO '._DB_PREFIX_.pSQL($table_name).' VALUES '.implode(', ', $new_rows); } } return $this->runSql($sql) ? $id_template_new : false; } public function makeSureTemplateCanBeDeleted($id_template) { $controller = $this->db->getValue(' SELECT template_controller FROM '._DB_PREFIX_.'af_templates WHERE id_template = '.(int)$id_template.' '); $other_existing_template = $this->db->getValue(' SELECT id_template FROM '._DB_PREFIX_.'af_templates WHERE template_controller = \''.pSQL($controller).'\' AND id_template <> '.(int)$id_template.' '); if (!$other_existing_template) { $this->throwError($this->l('You can not delete this template, but you can turn it off')); } } public function ajaxDeleteTemplate() { $id_template = Tools::getValue('id_template'); $this->makeSureTemplateCanBeDeleted($id_template); $result = array ( 'success' => $this->deleteTemplate($id_template), ); exit(Tools::jsonEncode($result)); } public function deleteTemplate($id_template) { $sql = array(); foreach ($this->getTemplateAssociatedTables() as $table_name) { $sql[] = 'DELETE FROM '._DB_PREFIX_.pSQL($table_name).' WHERE id_template = '.(int)$id_template.' AND id_shop IN ('.$this->getImplodedContextShopIds().')'; } return $this->runSql($sql); } public function getTemplateAssociatedTables() { $tables = array('af_templates', 'af_templates_lang'); foreach ($this->getControllersWithMultipleIDs() as $controller) { $tables[] = 'af_'.$controller.'_templates'; } return $tables; } public function ajaxSaveTemplate() { $id_template = Tools::getValue('id_template'); $template_controller = Tools::getValue('template_controller'); $template_name = Tools::getValue('template_name'); $filters_data = Tools::getValue('filters'); $controller_ids = Tools::getValue('controller_ids'); // additional settings $available_additional_settings = Tools::getValue('additional_settings'); $unlocked_additional_settings = Tools::getValue('unlocked_additional_settings', array()); $additional_settings = array(); foreach (array_keys($unlocked_additional_settings) as $name) { if (isset($available_additional_settings[$name])) { $additional_settings[$name] = $available_additional_settings[$name]; } } // validation $errors = $this->validateSettings($additional_settings, $this->getSettingsFields('general')); if (!$filters_data) { $errors['no_filters'] = $this->l('Please select at least one filter.'); } if ($template_name == '') { $errors['no_name'] = $this->l('Please add a template name'); } if ($errors) { $this->throwError($errors); } if (!$this->saveTemplate( $id_template, $template_controller, $template_name, $filters_data, $controller_ids, $additional_settings )) { $this->throwError($this->l('Template not saved')); } $ret = array ( 'hasError' => false, 'responseText' => $this->saved_txt, ); die(Tools::jsonEncode($ret)); } public function ajaxUpdateHook() { $new_hook = Tools::getValue('hook_name'); $previous_hook = $this->getCurrentHook(); $available_hooks = array_keys($this->getAvailableHooks()); $default_hook_layouts = $pages_without_this_hook = $warning = array(); foreach ($available_hooks as $hook) { $this->unregisterHook($hook, $this->shop_ids); $default_hook_layouts[$hook] = $hook != 'displayTopColumn' ? 'vertical' : 'horizontal'; } $this->registerHook($new_hook, $this->shop_ids); $this->updatePosition(Hook::getIdByName($new_hook), 0, 1); $ret = array ( 'hasError' => false, 'positions_form_html' => utf8_encode($this->renderHookPositionsForm($new_hook)), 'responseText' => $this->saved_txt, ); if ($default_hook_layouts[$new_hook] != $default_hook_layouts[$previous_hook]) { $layout = $default_hook_layouts[$new_hook]; foreach ($this->shop_ids as $id_shop) { $settings = $this->db->getValue(' SELECT value FROM '._DB_PREFIX_.'af_settings WHERE type = \'general\' AND id_shop = '.(int)$id_shop.' '); $settings = $settings ? Tools::jsonDecode($settings, true) : array(); $settings['layout'] = $layout; $this->saveSettings('general', $settings, array($id_shop)); } $warning[] = utf8_encode(sprintf($this->l('Layout type was updated to "%s"'), $layout)); } $active_templates = $this->db->executeS(' SELECT * FROM '._DB_PREFIX_.'af_templates WHERE active = 1 '); foreach ($active_templates as $t) { if (!$this->isHookAvailableOnControllerPage($new_hook, $t['template_controller'])) { $pages_without_this_hook[$t['template_controller']] = $t['template_controller']; } } if ($pages_without_this_hook) { // warning if some pages do not have selected hook $txt = sprintf($this->l('Module was succesfully hooked to %s'), $new_hook).', '; $txt .= $this->l('but this column is not activated for the following pages').':
'; ksort($pages_without_this_hook); foreach ($pages_without_this_hook as $controller_name) { $txt .= '- '.$controller_name.'
'; } $txt .= $this->howToActivateColumnTxt(); $warning[] = utf8_encode($txt); } if ($warning) { $ret['warning'] = implode('
-----
', $warning); } exit(Tools::jsonEncode($ret)); } public function howToActivateColumnTxt() { $txt = $this->l('You can activate it in %s'); if ($this->is_17) { $sprintf = $this->l('Design > Theme & Logo > Choose layouts'); } else { $sprintf = $this->l('Preferences > Themes > Advanced settings'); } return sprintf($txt, $sprintf); } public function renderHookPositionsForm($hook_name) { $this->context->smarty->assign(array( 'hook_modules' => $this->getHookModulesInfos($hook_name), 'hook_name' => $hook_name, )); return $this->display($this->local_path, 'views/templates/admin/hook-positions-form.tpl'); } public function getHookModulesInfos($hook_name) { $hook_modules = Hook::getModulesFromHook(Hook::getIdByName($hook_name)); $sorted = array(); foreach ($hook_modules as $m) { if ($instance = Module::getInstanceByName($m['name'])) { $logo_src = false; if (file_exists(_PS_MODULE_DIR_.$instance->name.'/logo.png')) { $logo_src = _MODULE_DIR_.$instance->name.'/logo.png'; } $sorted[$m['id_module']] = array( 'name' => $instance->name, 'position' => $m['m.position'], 'enabled' => $instance->isEnabledForShopContext(), 'display_name' => $instance->displayName, 'description' => $instance->description, 'logo_src' => $logo_src, ); if ($m['id_module'] == $this->id) { $sorted[$m['id_module']]['current'] = 1; } } } return $sorted; } public function getDefaultFiltersData() { $filters_data = array ( 'c' => array('type' => 1, 'nesting_lvl' => 0, 'foldered' => 1), 'p' => array('type' => 4, 'slider_step' => 1), 'm' => array('type' => 3), 'search_filter' => array('type' => 2), 'multilang' => array(), ); return $filters_data; } public function prepareMultilangData($data) { $sorted_data = array(); foreach ($data as $filter_key => $multilang_values) { foreach ($multilang_values as $name => $values) { foreach ($values as $id_lang => $value) { $sorted_data[$id_lang][$filter_key][$name] = strip_tags($value); } } } return $sorted_data; } public function validateSettings(&$values, $fields, $update_values = true) { $errors = array(); foreach ($values as $name => $value) { if (isset($fields[$name])) { if ($error = $this->validateField($value, $fields[$name], $update_values, true)) { $errors[$name] = $error; } } elseif ($update_values) { unset($values[$name]); } } return $errors; } public function validateField(&$value, $field, $update_value = true, $error_label = true) { $error = false; $validate = isset($field['validate']) ? $field['validate'] : false; if ($value === '' && !empty($field['required'])) { $error = sprintf($this->l('%s: please fill this value'), $field['display_name']); } elseif ($validate && !Validate::$validate($value)) { $error = ($error_label ? $field['display_name'].': ' : '').$this->l('incorrect value'); } if ($error && $update_value) { $value = $field['value']; } return $error; } public function saveTemplate( $id_template, $template_controller, $template_name, $filters_data = array(), $controller_ids = array(), $additional_settings = array() ) { if (!$id_template) { $id_template = $this->getNewTemplateId(); $additional_settings += $this->getDefaultAdditionalSettings($template_controller); } if (!$filters_data) { $filters_data = $this->getDefaultFiltersData(); } $multilang_data = $this->prepareMultilangData($filters_data['multilang']); unset($filters_data['multilang']); $this->validateTempalateFilters($filters_data, $template_controller); $current_hook = $this->getCurrentHook(); // active status is inserted only first time. After that it is updated using toggleActiveStatus $active = $this->isHookAvailableOnControllerPage($current_hook, $template_controller); $encoded_filters_data = Tools::jsonEncode($filters_data); $encoded_additional_settings = Tools::jsonEncode($additional_settings); $template_rows = $template_lang_rows = $controller_ids_rows = array(); foreach ($this->shop_ids as $id_shop) { $template_rows[] = '( '.(int)$id_template.', '.(int)$id_shop.', \''.pSQL($template_controller).'\', '.(int)$active.', \''.pSQL($template_name).'\', \''.pSQL($encoded_filters_data).'\', \''.pSQL($encoded_additional_settings).'\' )'; if (in_array($template_controller, $this->getControllersWithMultipleIDs())) { $controller_ids = $controller_ids ? $controller_ids : array(0); foreach ($controller_ids as $id) { $controller_ids_rows[$id.'_'.$id_shop] = '('.(int)$id.', '.(int)$id_template.', '.(int)$id_shop.')'; } } foreach ($multilang_data as $id_lang => $data) { $encoded_lang_data = Tools::jsonEncode($data); $row = (int)$id_template.', '.(int)$id_shop.', '.(int)$id_lang.', \''.pSQL($encoded_lang_data).'\''; $template_lang_rows[] = '('.$row.')'; } } $sql = array(); if ($template_rows) { $sql['template_data'] = ' INSERT INTO '._DB_PREFIX_.'af_templates VALUES '.implode(', ', $template_rows).' ON DUPLICATE KEY UPDATE template_name=VALUES(template_name), template_controller=VALUES(template_controller), template_filters=VALUES(template_filters), additional_settings=VALUES(additional_settings) '; } if ($template_lang_rows) { $sql['template_lang_data'] = ' INSERT INTO '._DB_PREFIX_.'af_templates_lang VALUES '.implode(', ', $template_lang_rows).' ON DUPLICATE KEY UPDATE data=VALUES(data) '; } if ($controller_ids_rows) { $sql['controller_ids_delete'] = ' DELETE FROM '._DB_PREFIX_.'af_'.pSQL($template_controller).'_templates WHERE id_template = '.(int)$id_template.' AND id_shop IN ('.pSQL($this->getImplodedContextShopIds()).') '; $sql['controller_ids_insert'] = ' INSERT INTO '._DB_PREFIX_.'af_'.pSQL($template_controller).'_templates VALUES '.implode(', ', $controller_ids_rows).' ON DUPLICATE KEY UPDATE id_'.pSQL($template_controller).' = VALUES(id_'.pSQL($template_controller).') '; } foreach ($sql as $s) { if (!$this->db->execute($s)) { $this->errors[] = $this->l('Template not saved'); } } if ($this->errors) { return false; } return $id_template; } public function validateTempalateFilters(&$filters_data, $template_controller) { if ($template_controller == 'manufacturer' && isset($filters_data['m'])) { unset($filters_data['m']); } if ($template_controller == 'supplier' && isset($filters_data['s'])) { unset($filters_data['s']); } foreach ($filters_data as $key => &$f) { if (isset($f['range_step'])) { $f['range_step'] = trim(preg_replace('/[^0-9,minmax-]/', '', $f['range_step']), ',') ?: 100; } if (isset($f['slider_step'])) { $step = (float)str_replace(',', '.', $f['slider_step']); $f['slider_step'] = $this->removeScientificNotation($step) ?: 1; } if (in_array($f['type'], array(3, 4))) { $f['quick_search'] = 0; // no quick_search for sliders and selects } if (isset($f['visible_items'])) { $f['visible_items'] = (int)$f['visible_items'] ?: ''; } } } public function parseStr($str) { $params = array(); parse_str(str_replace('&', '&', $str), $params); return $params; } /** * af_templates table has a composite KEY that cannot be autoincremented **/ public function getNewTemplateId() { $max_id = $this->db->getValue('SELECT MAX(id_template) FROM '._DB_PREFIX_.'af_templates'); return (int)$max_id + 1; } public function addJS($file_name, $custom_path = '') { $path = ($custom_path ? $custom_path : 'modules/'.$this->name.'/views/js/').$file_name; if ($this->is_17) { // priority should be more than 90 in order to be loaded after jqueryUI $params = array('server' => $custom_path ? 'remote' : 'local', 'priority' => 100); $this->context->controller->registerJavascript(sha1($path), $path, $params); } else { $path = $custom_path ? $path : __PS_BASE_URI__.$path; $this->context->controller->addJS($path); } } public function addCSS($file_name, $custom_path = '', $media = 'all') { $path = ($custom_path ? $custom_path : 'modules/'.$this->name.'/views/css/').$file_name; if ($this->is_17) { $params = array('media' => $media, 'server' => $custom_path ? 'remote' : 'local'); $this->context->controller->registerStylesheet(sha1($path), $path, $params); } else { $path = $custom_path ? $path : __PS_BASE_URI__.$path; $this->context->controller->addCSS($path, $media); } } public function isMobilePhone() { return $this->context->getDevice() == Context::DEVICE_MOBILE; } public function isTablet() { return $this->context->getDevice() == Context::DEVICE_TABLET; } public function addCustomMedia() { foreach (array('css', 'js') as $type) { $path = 'specific/'.$this->getSpecificThemeIdentifier().'.'.$type; if (file_exists(_PS_MODULE_DIR_.$this->name.'/views/'.$type.'/'.$path)) { $method_name = 'add'.Tools::strtoupper($type); $this->$method_name($path); } } $this->addJS('custom.js'); $this->addCSS('custom.css'); } public function loadIconFontIfRequired() { if (!empty($this->settings['iconclass']['load_font'])) { $this->addCSS('icons.css'); } } public function hookDisplayHeader() { $css = ''; if ($this->defineFilterParams()) { $this->addJS('front.js'); $this->addCSS('front.css'); $this->loadIconFontIfRequired(); if ($this->is_17) { $this->addCSS('front-17.css'); } else { Media::addJsDef(array( 'af_upd_search_form' => $this->isTemplateAvailable('search'), )); $this->addJS('front-16.js'); } if (!empty($this->slider_required)) { $this->addJS('slider.js'); $this->addCSS('slider.css'); } $this->addCustomMedia(); $load_more = $this->settings['general']['p_type'] > 1; Media::addJsDef(array( 'af_ajax_path' => $this->context->link->getModuleLink($this->name, 'ajax', array('ajax' => 1)), 'af_id_cat' => (int)$this->id_cat_current, 'current_controller' => Tools::getValue('controller'), 'load_more' => $load_more, 'af_product_count_text' => $this->products_data['product_count_text'], 'show_load_more_btn' => !$this->products_data['hide_load_more_btn'], 'af_product_list_class' => $this->product_list_class, 'page_link_rewrite_text' => $this->page_link_rewrite_text, 'af_classes' => $this->getLayoutClasses(), 'af_ids' => $this->settings['themeid'], 'is_17' => (int)$this->is_17, )); if (!empty($this->context->controller->seopage_data)) { $fixed_criteria = $this->context->controller->seopage_data['all_required_filters_hidden']; Media::addJsDef(array( 'af_sp_fixed_criteria' => $fixed_criteria, 'af_sp_base_url' => !$fixed_criteria ? $this->sp->url('build') : current(explode('?', $this->context->controller->seopage_data['canonical'])), )); } if (!$this->is_17) { $tpl_vars = $this->context->smarty->tpl_vars; $max_items = $tpl_vars['comparator_max_item']->value; $min_items_txt = $this->l('Please select at least one product'); $max_items_txt = $this->l('You cannot add more than %d product(s) to the product comparison'); Media::addJsDef(array( // comparator variables 'comparator_max_item' => $max_items, 'comparedProductsIds' => $tpl_vars['compared_products']->value, 'min_item' => addslashes($min_items_txt), 'max_item' => sprintf(addslashes($max_items_txt), $max_items), )); } if ($load_more) { // hide pagination if load more is used $css .= '.'.$this->settings['themeclass']['pagination'].'{display:none;}'; } if ($compact_width = (int)$this->settings['general']['compact']) { // position:fixed will be used to detect compact view in front.js $css .= '@media(max-width:'.(int)$compact_width.'px){#amazzing_filter{position:fixed;opacity:0;}}'; } } elseif (Tools::getValue('controller') == 'myaccount') { // additional styles on my account page $this->loadIconFontIfRequired(); if ($this->is_17) { $this->addCSS('front-17.css'); } } return $css ? '' : ''; } public function isTemplateAvailable($controller) { $cache_id = 'tpl-avl-'.$controller.'-'.$this->id_shop; $is_available = $this->cache('get', $cache_id); if ($is_available === false) { $is_available = (int)$this->db->getValue(' SELECT id_template FROM '._DB_PREFIX_.'af_templates WHERE template_controller = \''.pSQL($controller).'\' AND id_shop = '.(int)$this->id_shop.' AND active = 1 '); $this->cache('save', $cache_id, $is_available); } return $is_available; } public function getSubmittedParams() { if (is_callable(array('Tools', 'getAllValues'))) { $params = Tools::getAllValues(); } else { // retro compatibility $params = $_POST + $_GET; } return $params; } public function getInitialFiltersByGroup($filter_group) { $values = Tools::getValue($filter_group); return $values ? explode(',', $values) : array(); } public function getSubcategories($id_lang, $id_parent = false, $imploded_customer_groups = '', $nesting_lvl = 0) { $id_parent = $id_parent ? $id_parent : $this->context->shop->getCategory(); $current_category_data = $this->db->getRow(' SELECT * FROM '._DB_PREFIX_.'category WHERE id_category = '.(int)$id_parent.' '); $max_depth = $nesting_lvl ? $current_category_data['level_depth'] + $nesting_lvl : 0; $nleft = $current_category_data['nleft']; $nright = $current_category_data['nright']; $categories = $this->db->executeS(' SELECT c.id_category AS id, c.id_parent, cl.name, cl.link_rewrite AS link, category_shop.position FROM '._DB_PREFIX_.'category c '.Shop::addSqlAssociation('category', 'c').' LEFT JOIN '._DB_PREFIX_.'category_lang cl ON c.id_category = cl.id_category '.($imploded_customer_groups ? 'INNER JOIN '._DB_PREFIX_.'category_group cg ON cg.id_category = c.id_category Isolators / Disconnectors (31)

isolators / disconnectors

Isolators / Disconnectors

Product added to your list
Product added to compare.

We use cookies to improve user experience, and analyze website traffic. For these reasons, we may share your site usage data with our analytics partners. By clicking “Accept Cookies,” you consent to store on your device all the technologies described in our Terms & Conditions & Privacy Policy