* @copyright 2007-2020 Amazzing
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
*/
class AmazzingFilter extends Module
{
public $errors = array();
public $generated_links = array();
public function __construct()
{
if (!defined('_PS_VERSION_')) {
exit;
}
$this->name = 'amazzingfilter';
$this->tab = 'front_office_features';
$this->version = '3.1.3';
$this->author = 'Amazzing';
$this->need_instance = 0;
$this->module_key = '702061a17e404432e6b85a85ad14afb0';
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l('Amazzing filter');
$this->description = $this->l('Powerful layered navigation with flexible settings');
$this->definePublicVariables();
}
public function definePublicVariables()
{
$this->csv_dir = $this->local_path.'indexes/';
$this->indexation_process_file_path = $this->csv_dir.'index_all';
$this->db = Db::getInstance();
$this->saved_txt = $this->l('Saved');
$this->error_txt = $this->l('Error');
$this->product_list_class = 'af-product-list';
$this->is_17 = Tools::substr(_PS_VERSION_, 0, 3) === '1.7';
$this->page_link_rewrite_text = $this->is_17 ? 'page' : 'p';
$this->shop_ids = Shop::getContextListShopID();
$this->all_shop_ids = Shop::getShops(false, null, true);
$this->id_lang = $this->context->language->id;
$this->id_shop = $this->context->shop->id;
$this->custom_overrides_dir = $this->local_path.'override_files/';
$this->qs_min_values = 10;
$this->i = array(
'table' => _DB_PREFIX_.'af_index',
'variable_keys' => array('p', 'n', 't'),
'default' => array('g' => 'PS_UNIDENTIFIED_GROUP', 'c' => 'PS_CURRENCY_DEFAULT'),
'max_column_suffixes' => 15,
);
}
public function install()
{
if (Shop::isFeatureActive()) {
Shop::setContext(Shop::CONTEXT_ALL);
}
$this->installation_process = true;
if (!parent::install()
|| !$this->registerHook('displayLeftColumn')
|| !$this->registerHook('displayHeader')
// || !$this->registerHook('displayHome')
|| !$this->registerHook('displayBackOfficeHeader')
|| !$this->registerHook('actionProductAdd')
|| !$this->registerHook('actionProductUpdate')
|| !$this->registerHook('actionIndexProduct')
|| !$this->registerHook('actionObjectAddAfter')
|| !$this->registerHook('actionObjectDeleteAfter')
|| !$this->registerHook('actionObjectUpdateAfter')
|| !$this->registerHook('actionObjectCombinationAddAfter')
|| !$this->registerHook('actionAdminTagsControllerSaveAfter')
|| !$this->registerHook('actionAdminTagsControllerDeleteBefore')
|| !$this->registerHook('actionAdminTagsControllerDeleteAfter')
|| !$this->registerHook('actionProductDelete')
|| !$this->registerHook('actionProductListOverride')
|| !$this->registerHook('productSearchProvider')
|| !$this->registerHook('displayCustomerAccount')
|| !$this->prepareDatabaseTables()
|| !$this->installDemoData()) {
$this->uninstall();
return false;
}
foreach ($this->getSettingsKeys() as $type) {
if ($type == 'seopage' && Module::isInstalled('af_seopages')) {
$this->sp = Module::getInstanceByName('af_seopages');
}
$this->saveSettings($type); // will be saved for all shops, becasue context is set to ALL above
}
$this->indexationTable('install'); // should be installed and adjusted after settings are ready
$this->updatePosition(Hook::getIdByName('displayLeftColumn'), 0, 1);
$this->processAvailableOverrides('add');
unlink(_PS_CACHE_DIR_.'class_index.php'); // In some cases overrides are not reset automatically
return true;
}
public function prepareDatabaseTables()
{
$sql = array();
$sql[] = 'CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'af_templates (
id_template int(10) unsigned NOT NULL AUTO_INCREMENT,
id_shop int(10) NOT NULL,
template_controller varchar(128) NOT NULL,
active tinyint(1) NOT NULL DEFAULT 1,
template_name text NOT NULL,
template_filters text NOT NULL,
additional_settings text NOT NULL,
PRIMARY KEY (id_template, id_shop),
KEY template_controller (template_controller),
KEY active (active)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
$sql[] = 'CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'af_templates_lang (
id_template int(10) unsigned NOT NULL,
id_shop int(10) NOT NULL,
id_lang int(10) NOT NULL,
data text NOT NULL,
PRIMARY KEY (id_template, id_shop, id_lang)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
$sql[] = 'CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'af_settings (
id_shop int(10) unsigned NOT NULL,
type varchar(16) NOT NULL,
value text NOT NULL,
PRIMARY KEY (id_shop, type)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
$sql[] = 'CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'af_customer_filters (
id_customer int(10) unsigned NOT NULL,
filters text NOT NULL,
PRIMARY KEY (id_customer)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
foreach ($this->getControllersWithMultipleIDs() as $controller) {
$sql[] = 'CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'af_'.pSQL($controller).'_templates (
id_'.pSQL($controller).' int(10) unsigned NOT NULL,
id_template int(10) NOT NULL,
id_shop int(10) NOT NULL,
PRIMARY KEY (id_'.pSQL($controller).', id_template, id_shop)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
}
$this->mergedValues()->extendSQL('install', $sql);
return $this->runSql($sql);
}
public function installDemoData()
{
$installed = true;
foreach ($this->getAvailableControllers(true) as $controller => $controller_name) {
$template_name = sprintf($this->l('Template for %s'), $controller_name);
$installed &= (bool)$this->saveTemplate(0, $controller, $template_name);
}
return $installed;
}
public function getAvailableControllers($include_category_controller = false)
{
$controllers = array(
'category' => $this->l('Category pages'),
'seopage' => $this->l('Custom SEO pages'),
'manufacturer' => $this->l('Manufacturer pages'),
'supplier' => $this->l('Supplier pages'),
'index' => $this->l('Home page'),
'pricesdrop' => $this->l('Prices drop page'),
'newproducts' => $this->l('New products page'),
'bestsales' => $this->l('Best sales page'),
'search' => $this->l('Search results'),
);
if (!$include_category_controller) {
unset($controllers['category']);
}
return $controllers;
}
public function getControllersWithMultipleIDs($only_keys = true)
{
$controllers = array(
'category' => $this->l('Selected categories'),
'seopage' => $this->l('Selected SEO pages'),
'manufacturer' => $this->l('Selected manufacturers'),
'supplier' => $this->l('Selected suppliers'),
);
return $only_keys ? array_keys($controllers) : $controllers;
}
public function uninstall()
{
$sql = array();
$sql[] = 'DROP TABLE IF EXISTS '._DB_PREFIX_.'af_templates';
$sql[] = 'DROP TABLE IF EXISTS '._DB_PREFIX_.'af_templates_lang';
$sql[] = 'DROP TABLE IF EXISTS '._DB_PREFIX_.'af_settings';
$sql[] = 'DROP TABLE IF EXISTS '._DB_PREFIX_.'af_customer_filters';
foreach ($this->getControllersWithMultipleIDs() as $controller) {
$sql[] = 'DROP TABLE IF EXISTS '._DB_PREFIX_.'af_'.pSQL($controller).'_templates';
}
$this->mergedValues()->extendSQL('uninstall', $sql);
if (!parent::uninstall() || !$this->runSql($sql) || !$this->indexationTable('uninstall')) {
return false;
}
$this->cache('clear', '');
$this->processAvailableOverrides('remove');
return true;
}
public function runSql($sql)
{
foreach ($sql as $s) {
if (!$this->db->Execute($s)) {
return false;
}
}
return true;
}
public function processAvailableOverrides($action)
{
$action .= 'Override';
$overrides_data = $this->getOverridesData();
foreach ($overrides_data as $data) {
$this->processOverride($action, $data['path'], false);
}
}
public function indexationTable($action)
{
$ret = true;
switch ($action) {
case 'install':
$required_columns = $this->indexationColumns('getRequired');
$columns = array();
foreach ($required_columns['primary'] as $c_name) {
$columns[] = $c_name.' int(10) unsigned NOT NULL';
}
$specific_types = array('w' => 'decimal(20,2)', 'd' => 'datetime', 'q' => 'tinyint(1)',
'v' => 'tinyint(1)');
foreach ($required_columns['main'] as $c_name) {
$type = isset($specific_types[$c_name]) ? $specific_types[$c_name] : 'TEXT';
$columns[] = $c_name.' '.$type.' NOT NULL';
}
$ret &= $this->db->execute('
CREATE TABLE IF NOT EXISTS '.pSQL($this->i['table']).' ('.implode(', ', $columns).',
PRIMARY KEY('.implode(', ', $required_columns['primary']).'))
ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8
');
$ret &= $this->indexationColumns('adjust');
break;
case 'uninstall':
$ret &= $this->db->execute('DROP TABLE IF EXISTS '.pSQL($this->i['table']));
break;
}
return $ret;
}
public function indexationColumns($action, $id_shop = 0, $cache_time = 0)
{
if ($cache_time = (int)$cache_time) {
$cache_id = 'indexationColumns_'.$action.'_'.$id_shop;
if (!$ret = $this->cache('get', $cache_id, '', $cache_time)) {
$ret = $this->indexationColumns($action, $id_shop);
$this->cache('save', $cache_id, $ret);
}
return $ret;
}
$this->defineSettings();
$ret = true;
switch ($action) {
case 'adjust':
$required_columns = $this->indexationColumns('getRequiredFormatted');
$existing_variable_columns = array_diff(
$this->indexationColumns('getExisting'),
array_merge($required_columns['primary'], $required_columns['main'])
);
$sql = array();
if ($to_remove = array_diff($existing_variable_columns, $required_columns['variable'])) {
$sql[] = 'ALTER TABLE '.pSQL($this->i['table']).' DROP '.implode(', DROP ', $to_remove);
}
if (array_diff($required_columns['variable'], $existing_variable_columns)) {
$to_add = array(); // IMPORTANT: keep original ordering
$prev_column = end($required_columns['main']);
foreach ($required_columns['variable'] as $c_name) {
if (!in_array($c_name, $existing_variable_columns)) {
$type = (Tools::substr($c_name, 0, 1) == 'p' ? 'decimal(20,2)' : 'TEXT');
$to_add[] = 'ADD '.$c_name.' '.$type.' NOT NULL AFTER '.$prev_column;
}
$prev_column = $c_name;
}
if ($to_add) {
$sql[] = 'ALTER TABLE '.pSQL($this->i['table']).' '.implode(', ', $to_add);
}
}
if ($sql) {
$comment = $required_columns['variable'] ? 'some columns are not normalized on purpose' : '';
$sql[] = 'ALTER TABLE '.pSQL($this->i['table']).' COMMENT = \''.pSQL($comment).'\'';
$sql[] = 'OPTIMIZE TABLE '.pSQL($this->i['table']);
$ret &= $this->runSql($sql);
}
break;
case 'getExisting':
$ret = array_column($this->db->executeS('SHOW COLUMNS FROM '.pSQL($this->i['table'])), 'Field');
break;
case 'getRequiredFormatted':
$ret = $this->indexationColumns('getRequired', $id_shop);
$formatted_variable_columns = array();
foreach ($ret['variable'] as $c_name => $identifiers) {
foreach ($identifiers as $suffix) {
$formatted_variable_columns[] = $c_name.'_'.$suffix;
}
}
$ret['variable'] = $formatted_variable_columns;
break;
case 'getRequired':
$ret = array(
'primary' => array('id_product', 'id_shop'),
'main' => array('c', 'a', 'f', 'm', 's', 'w', 'r', 'd', 'q', 'v', 'g'),
'variable' => $this->indexationColumns('getVariableData', $id_shop),
);
break;
case 'getVariableData':
$ret = array();
foreach ($this->i['variable_keys'] as $c_name) {
if (!empty($this->settings['indexation'][$c_name])) {
switch ($c_name) {
case 'p':
foreach ($this->getSuffixes('group', $id_shop) as $id_group) {
foreach ($this->getSuffixes('currency', $id_shop) as $id_currency) {
$suffix = $id_group.'_'.$id_currency;
$ret[$c_name][$suffix] = $suffix;
}
}
break;
case 'n':
case 't':
if ($suffixes = $this->getSuffixes('lang', $id_shop)) {
$ret[$c_name] = $suffixes;
}
break;
}
}
}
break;
case 'getAvailableSuffixesCount':
$ret = array('group' => 0, 'currency' => 0, 'lang' => 0);
foreach (array_keys($ret) as $t_name) {
$c_name = 'id_'.$t_name;
$ret[$t_name] = (int)$this->db->getValue('
SELECT COUNT('.pSQL($c_name).') FROM '._DB_PREFIX_.pSQL($t_name).' main WHERE 1
'.$this->specificIndexationQuery($c_name, 'main', 0, false).'
');
}
break;
}
return $ret;
}
public function indexationData($action, $params = array())
{
$ret = true;
switch ($action) {
case 'get':
$query = $this->indexationData('prepareQuery', $params);
$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
}
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()
{
$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' => 0,
'type' => 'switcher',
),
'oos_behaviour' => array(
'display_name' => $this->l('Out of stock behaviour'),
'value' => 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)) {
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'),
);
}
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),
'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
AND cg.id_group IN ('.pSQL($imploded_customer_groups).')' : '').'
WHERE id_lang = '.(int)$id_lang.'
AND c.active = 1
AND c.nright < '.(int)$nright.'
AND c.nleft > '.(int)$nleft.'
'.($max_depth ? 'AND c.level_depth <= '.(int)$max_depth : '').'
AND cl.id_shop = '.(int)$this->context->shop->id.'
GROUP BY c.id_category
ORDER BY cl.name ASC, c.id_category ASC
');
return $categories;
}
public function getName($resource_type, $id, $id_lang = false, $id_shop = false)
{
if (!$id_shop) {
$id_shop = $this->context->shop->id;
}
if (!$id_lang) {
$id_lang = $this->context->language->id;
}
$name = $this->db->getValue('
SELECT name FROM `'._DB_PREFIX_.bqSQL($resource_type).'_lang`
WHERE `id_'.bqSQL($resource_type).'` = '.(int)$id.'
AND `id_shop` = '.(int)$id_shop.' AND `id_lang` = '.(int)$id_lang.'
');
return $name;
}
public function prepareTplVariables($current_filters)
{
$this->c_groups = $this->formatIDs($this->context->customer->getGroups(), true);
$filters = $this->prepareFiltersData($current_filters);
$initial_params = $this->prepareInitialParams($filters);
if (!empty($this->sp)) {
$this->sp->processCanonical($initial_params, $this->current_controller);
}
$hidden_inputs = $this->prepareHiddenInputs();
$f_params = $hidden_inputs + $initial_params + array('count_all_matches' => 1);
$this->products_data = $this->getFilteredProducts($f_params);
if (!$this->products_data['filtered_ids_count'] && !array_column($filters, 'has_selection')) {
$filters = array();
}
$this->preparePaginationVars($f_params);
$this->context->smarty->assign(array(
'filters' => $this->prepareFiltersForDisplay($filters, $initial_params),
'available_options' => $initial_params['available_options'],
'numeric_slider_values' => $initial_params['numeric_slider_values'],
'hidden_inputs' => $hidden_inputs,
'count_data' => $this->products_data['count_data'],
'class' => $this->product_list_class, // used in product-list.tpl
'af_classes' => $this->getLayoutClasses(),
'af_ids' => $this->settings['themeid'],
'current_controller' => $this->current_controller,
'total_products' => $this->products_data['filtered_ids_count'],
'is_17' => $this->is_17,
'af_layout_type' => $this->settings['general']['layout'],
'af' => 1,
'is_iphone' => $this->isIphone(),
));
$this->context->filtered_result = array(
'products' => $this->products_data['products'],
'total' => $this->products_data['filtered_ids_count'],
'controller' => $this->current_controller,
'sorting' => $f_params['orderBy'].'.'.$f_params['orderWay'],
);
}
public function isIphone()
{
if (!isset($this->context->cookie->is_iphone)) {
$this->context->cookie->__set('is_iphone', (int)$this->context->getMobileDetect()->isIphone());
}
return $this->context->cookie->is_iphone;
}
public function prepareFiltersData($filters)
{
$standard_filters = $this->getStandardFilters();
$special_filters = $this->getSpecialFilters();
$range_filters = array('p' => $this->l('Price'), 'w' => $this->l('Weight'));
$predefined_group_names = $standard_filters + $range_filters;
$horizontal_layout = $this->settings['general']['layout'] == 'horizontal';
foreach ($filters as $key => &$f) {
$f['name'] = !empty($f['custom_name']) ? $f['custom_name'] : '';
$f['classes'] = array();
if ($f['type'] == 5) {
$f['type'] = $f['textbox'] = 1;
$f['color_display'] = 0;
}
if ($f['special'] = isset($special_filters[$key])) {
$f['first_char'] = $f['id_group']= $f['input_group_id'] = $f['is_slider'] = '';
$f['name'] = $f['name'] ?: $special_filters[$key];
$f['values'] = array(1 => array('name' => $f['name'], 'id' => 1, 'link' => 1, 'identifier' => $key));
$f['submit_name'] = $key;
} else {
$f['first_char'] = Tools::substr($key, 0, 1);
$f['id_group'] = $f['input_group_id'] = Tools::substr($key, 1);
$f['is_slider'] = $f['type'] == 4;
if ($f['first_char'] == 'c') {
$f['id_parent'] = $f['input_group_id'] = $f['id_group'] ?: $this->id_cat_current;
}
$f['values'] = $this->getFilterValues($f, $key);
$first_value = current($f['values']) ?: array();
if ($f['is_slider'] || isset($range_filters[$key])) {
$this->slider()->setExtensions($f, $this->is_17);
}
if (!$f['name']) {
if (isset($predefined_group_names[$key])) {
$f['name'] = $predefined_group_names[$key];
} elseif ($f['first_char'] == 'c') {
$f['name'] = $f['id_group'] ? $this->getName('category', $f['id_group']) :
$this->l('Categories');
} elseif (isset($first_value['group_name'])) { // attributes, features. Can be optimized
$f['name'] = $first_value['group_name'];
}
}
if ($f['input_group_id']) {
$f['submit_name'] = $f['first_char'].'['.$f['input_group_id'].'][]';
} else {
$f['submit_name'] = $key.'[]';
}
if ($horizontal_layout) {
$f['minimized'] = 1;
$f['visible_items'] = '';
}
}
$f['link'] = $this->generateLink($f['name'], $key);
}
$this->processCustomerFiltersIfRequired($filters);
return $filters;
}
public function prepareInitialParams(&$filters)
{
$initial_params = array_fill_keys(array('available_options', 'numeric_slider_values', 'sliders'), array());
$submitted_data = array();
foreach ($filters as $key => &$f) {
$initial_params['available_options'][$key] = array();
$submitted_data[$f['link']] = $this->getInitialFiltersByGroup($f['link']);
foreach ($f['values'] as &$v) {
$id = $v['id'];
if (isset($f['forced_values'][$id])) {
$f['forced_values'][$id] = $v['link'];
$submitted_data[$f['link']][] = $v['link'];
}
if ($v['selected'] = in_array($v['link'], $submitted_data[$f['link']])) {
$f['has_selection'] = 1;
if ($f['special']) {
$initial_params[$key] = $id;
} elseif ($f['input_group_id']) {
$initial_params[$f['first_char']][$f['input_group_id']][] = $id;
} else {
$initial_params[$key][] = $id;
}
}
$initial_params['available_options'][$key][$id] = $id;
if ($f['is_slider']) {
$possible_range = explode('-', $v['name']);
$number = $this->extractNumberFromString($possible_range[0]);
if (!empty($possible_range[1])) {
$number .= '-'.$this->extractNumberFromString($possible_range[1]);
}
// NOTE: keep 'numeric_slider_values' synchronized with 'available_options'
$initial_params['numeric_slider_values'][$key][$id] = $number;
}
}
if ($f['is_slider']) {
$f['values'] = array();
if (!empty($submitted_data[$f['link']])) {
$f['has_selection'] = 1;
$range = ExtendedTools::explodeRangeValue($submitted_data[$f['link']][0]);
$f['values'] = array('from' => $range[0], 'to' => $range[1]);
}
$initial_params['sliders'][$key] = $f['values'];
} elseif (isset($f['range_step'])) {
$initial_params[$key.'_range_step'] = $f['range_step'];
if (!empty($submitted_data[$f['link']])) {
$f['has_selection'] = 1;
$initial_params[$key] = $submitted_data[$f['link']];
// values will be defined later in prepareRangeFilters()
}
}
if (empty($initial_params['primary_filter']) && !empty($f['has_selection']) &&
!$f['is_slider'] && !$f['special']) {
$initial_params['primary_filter'] = $key;
}
}
return $initial_params;
}
public function prepareHiddenInputs()
{
$product_sorting = $this->getProductSorting();
$hidden_inputs = array(
'id_manufacturer' => (int)Tools::getValue('id_manufacturer'),
'id_supplier' => (int)Tools::getValue('id_supplier'),
'page' => (int)Tools::getValue($this->page_link_rewrite_text, 1),
'nb_items' => $this->getNbItems(),
'controller_product_ids' => implode(',', $this->controller_product_ids),
'current_controller' => $this->current_controller,
'page_name' => $this->getPageName($this->current_controller),
'id_parent_cat' => $this->id_cat_current,
'orderBy' => $product_sorting['by'],
'orderWay' => $product_sorting['way'],
'defaultSorting' => $this->settings['general']['default_order_by'].
':'.$this->settings['general']['default_order_way'],
'customer_groups' => $this->c_groups,
'random_seed' => $this->getRandomSeed($this->settings['general']['random_upd']),
);
if (!$this->is_17) {
$pb_id = $this->settings['themeid']['pagination_bottom'];
$pb_suffix = str_replace($this->settings['themeid']['pagination'].'_', '', $pb_id);
$hidden_inputs['pagination_bottom_suffix'] = $pb_suffix;
$hidden_inputs['hide_right_column'] = !$this->context->controller->display_column_right;
$hidden_inputs['hide_left_column'] = !$this->context->controller->display_column_left;
}
return $hidden_inputs + $this->settings['general'];
}
public function getNbItems()
{
$nb_items = $this->settings['general']['npp'];
$this->nb_items_options = array($nb_items, $nb_items * 2, $nb_items * 5);
if ($custom_nb_items = (int)Tools::getValue('n')) {
$nb_items = $custom_nb_items;
if (!$this->is_17 && !in_array($custom_nb_items, $this->nb_items_options)) {
$this->nb_items_options[] = $custom_nb_items;
sort($this->nb_items_options);
}
}
return $nb_items;
}
public function preparePaginationVars($params)
{
$params['total_products'] = $this->products_data['filtered_ids_count'];
$this->validatePageNumber($params);
if ($this->is_17) {
$this->context->forced_nb_items = $params['nb_items'];
} else {
$this->assignCustomPaginationAndSorting($params);
}
}
public function setClasses(&$f, $key)
{
$f['classes'] += array_filter(array(
$key => 1,
'has-slider' => $f['is_slider'],
'type-'.$f['type'] => !$f['is_slider'],
'tb' => !empty($f['textbox']),
'special' => $f['special'],
'folderable' => isset($f['foldered']),
'foldered' => !empty($f['foldered']),
'closed' => !empty($f['minimized']),
'has-selection' => !empty($f['has_selection']),
));
}
public function prepareFiltersForDisplay($filters, &$initial_params)
{
$this->prepareRangeFilters($filters, $initial_params);
$this->prepareSliderFilters($filters, $initial_params);
foreach ($filters as $key => &$f) {
$this->setClasses($f, $key);
if ($f['is_slider']) {
continue; // processed in prepareSliderFilters
}
if ($this->products_data['count_data'] && empty($f['has_selection'])) {
$f['classes']['no-available-items'] = 1;
}
if ($f['first_char'] == 'c' && $f['nesting_lvl'] != 1 &&
!$this->settings['indexation']['subcat_products']) {
$parent_ids = array_keys($this->prepareTreeValues($f['values'], $f['id_parent']));
foreach ($parent_ids as $id_parent) { // keep upper level categories without matches in tree
$this->products_data['all_matches']['c-'.$id_parent] = 1;
}
}
$remove_unused_options = in_array($f['first_char'], array('a', 'f', 'c'));
foreach ($f['values'] as $i => &$v) {
if ($remove_unused_options && !isset($this->products_data['all_matches'][$v['identifier']])) {
unset($f['values'][$i]);
unset($initial_params['available_options'][$key][$v['id']]);
continue;
}
if (!empty($f['classes']['no-available-items']) &&
!empty($this->products_data['count_data'][$v['identifier']])) {
unset($f['classes']['no-available-items']);
if (!$remove_unused_options) {
break; // will not break for colors
}
}
if (!empty($f['color_display'])) {
$this->setColorStyle($v);
}
}
if (empty($f['values'])) {
unset($filters[$key]);
unset($initial_params['available_options'][$key]);
} else {
if (!empty($f['visible_items']) && $f['type'] < 3 && $f['visible_items'] < count($f['values'])) {
$f['cut_off'] = $f['classes']['cut-off'] = $f['visible_items'];
}
if (!empty($f['sort_by'])) {
$f['values'] = $this->sortByKey($f['values'], $f['sort_by']);
}
if (!empty($f['quick_search'])) {
$f['quick_search'] = count($f['values']) >= $this->qs_min_values; // before prepareTreeValues()
}
if ($f['first_char'] == 'c' &&
!$f['values'] = $this->prepareTreeValues($f['values'], $f['id_parent'])) {
unset($filters[$key]);
unset($initial_params['available_options'][$key]);
}
}
}
return $filters;
}
public function prepareRangeFilters(&$filters, &$initial_params)
{
foreach ($this->products_data['ranges'] as $key => $r) {
if (empty($r['max'])) {
unset($filters[$key]);
unset($initial_params['available_options'][$key]);
}
if (!empty($filters[$key]) && isset($r['available_range_options'])) {
$initial_params['available_options'][$key] = $r['available_range_options'];
$submitted_ranges = isset($initial_params[$key]) ? $initial_params[$key] : array();
foreach ($r['available_range_options'] as $range) {
$filters[$key]['values'][] = array(
'name' => $filters[$key]['prefix'].$range.$filters[$key]['suffix'],
'id' => $range,
'link' => $range,
'identifier' => $filters[$key]['first_char'].'-'.$range,
'selected' => in_array($range, $submitted_ranges),
);
}
}
}
}
public function prepareSliderFilters(&$filters, &$initial_params)
{
foreach (array_keys($initial_params['sliders']) as $key) {
if (isset($filters[$key])) {
if (isset($initial_params['numeric_slider_values'][$key])) {
// prepare data for numeric sliders; remove sliders without matches
$numbers = $initial_params['numeric_slider_values'][$key];
foreach (array_keys($numbers) as $id) {
$identifier = $filters[$key]['first_char'].'-'.$id;
if (!isset($this->products_data['all_matches'][$identifier])) {
unset($numbers[$id]);
unset($initial_params['available_options'][$key][$id]);
unset($initial_params['numeric_slider_values'][$key][$id]);
}
}
if (!$numbers) {
unset($filters[$key]);
unset($initial_params['available_options'][$key]);
unset($initial_params['numeric_slider_values'][$key]);
continue;
}
$number_values = explode('-', implode('-', $numbers));
$filters[$key]['values']['min'] = min($number_values);
$filters[$key]['values']['max'] = max($number_values);
} elseif (isset($this->products_data['ranges'][$key])) {
$filters[$key]['values'] = $this->products_data['ranges'][$key];
}
if (!empty($filters[$key]['values'])) {
$filters[$key]['values'] = $this->slider()->fillValues($filters[$key]['values']);
if ($filters[$key]['values']['max'] == 0) {
$filters[$key]['classes']['hidden'] = 1;
}
$this->slider_required = 1; // will be used to load slider script
}
}
}
}
public function getCacheIDForFilterValues($f)
{
if ($f['first_char'] == 'c') {
$caching_params = array($f['id_parent'], $f['nesting_lvl'], $this->id_shop, $this->id_lang);
$caching_params = array_merge($caching_params, $this->formatIDs($this->c_groups));
} else {
$caching_params = array($f['id_group'], $this->id_shop, $this->id_lang);
}
return $this->cacheID($f['first_char'].'_list', $caching_params);
}
public function getFilterValues($f, $key)
{
$cache_id = $this->getCacheIDForFilterValues($f);
if ($cache_id && $values = $this->cache('get', $cache_id)) {
return $values;
}
$values = $this->getRawFilterValues($f);
foreach ($values as &$v) {
$v['identifier'] = $f['first_char'].'-'.$v['id'];
if (!isset($v['link'])) {
$v['link'] = $this->generateLink($v['name'], $v['id'], $key);
} else {
$v['link'] = $this->getUniqueLink($v['link'], $v['id'], $key);
}
}
if ($cache_id) {
$this->cache('save', $cache_id, $values);
}
return $values;
}
public function getRawFilterValues($f)
{
$values = array();
switch ($f['first_char']) {
case 'c':
$values = $this->getSubcategories($this->id_lang, $f['id_parent'], $this->c_groups, $f['nesting_lvl']);
break;
case 'a':
case 'f':
$method_name = $f['first_char'] == 'a' ? 'getAttributes' : 'getFeatures';
$values = $this->$method_name($this->id_lang, $f['id_group']);
break;
case 'm':
case 's':
$resource = $f['first_char'] == 'm' ? 'manufacturer' : 'supplier';
$values = $this->db->executeS('
SELECT '.pSQL($f['first_char']).'.id_'.pSQL($resource).' as id, name
FROM '._DB_PREFIX_.pSQL($resource).' '.pSQL($f['first_char']).'
'.Shop::addSqlAssociation($resource, $f['first_char']).'
WHERE active = 1 ORDER BY name ASC
');
break;
case 't':
$values = $this->db->executeS('
SELECT id_tag as id, name FROM '._DB_PREFIX_.'tag
WHERE id_lang = '.(int)$this->id_lang.' ORDER BY name ASC
');
break;
case 'q':
$values = array(
array('id' => 1, 'name' => $this->l('New')),
array('id' => 2, 'name' => $this->l('Used')),
array('id' => 3, 'name' => $this->l('Refurbished')),
);
break;
}
return $values;
}
public function setColorStyle(&$v)
{
$img_name = (isset($v['id_original']) ? $v['id_original'] : $v['id']).'.jpg';
if (file_exists(_PS_COL_IMG_DIR_.$img_name)) {
$v['color_style'] = 'background:url('._THEME_COL_DIR_.$img_name.') 50% 50% no-repeat;';
} elseif (isset($v['color'])) {
$v['color_style'] = 'background-color:'.($v['color'] ?: '#FFFFFF');
if (ExtendedTools::isBrightColor($v['color'])) {
$v['bright'] = 1;
}
}
}
public function getFilterIdentifiers()
{
return array('c', 'a', 'f', 'm', 's', 't', 'q', 'p', 'w');
}
public function getRandomSeed($upd_random)
{
$patterns = array(1 => 'ymdH', 2 => 'ymd', 3 => 'ymW');
return isset($patterns[$upd_random]) ? date($patterns[$upd_random]) : mt_rand(0, 100000);
}
public function validatePageNumber(&$params)
{
$pages_nb = $this->getNumberOfPages($params['total_products'], $params['nb_items']);
$page_exceeded = $pages_nb && $pages_nb < $params['page'];
if ($params['page'] < 1 || ($params['page'] == 1 && Tools::isSubmit($this->page_link_rewrite_text)) ||
$page_exceeded) {
$updated_page = $page_exceeded ? $pages_nb : 1;
$url = $this->context->link->getPaginationLink(false, false);
$url = $this->updateQueryString($url, array($this->page_link_rewrite_text => $updated_page));
$this->redirect301($url);
}
}
public function redirect301($url)
{
return Tools::redirect($url, __PS_BASE_URI__, null, array('HTTP/1.0 301 Moved Permanently'));
}
public function assignCustomPaginationAndSorting($params)
{
// pagination
$this->assignSmartyVariablesForPagination(
$params['page'],
$params['total_products'],
$params['nb_items'],
$this->sanitizeURL($_SERVER['REQUEST_URI'])
);
if (!empty($this->nb_items_options)) {
$this->context->smarty->assign(array('nArray' => $this->nb_items_options));
}
$this->context->controller->p = $params['page'];
$this->context->controller->n = $params['nb_items'];
$this->context->custom_pagination = 1;
// sorting
$this->context->controller->orderBy = $params['orderBy'];
$this->context->controller->orderWay = $params['orderWay'];
$this->context->smarty->assign(array(
'orderby' => $this->context->controller->orderBy,
'orderway' => $this->context->controller->orderWay,
'orderbydefault' => $this->settings['general']['default_order_by'],
'orderwaydefault' => $this->settings['general']['default_order_way'],
'stock_management' => (int)Configuration::get('PS_STOCK_MANAGEMENT'),
));
$this->context->custom_sorting = 1;
}
public function getPageName($controller_name)
{
$custom_names = array(
'bestsales' => 'best-sales',
'pricesdrop' => 'prices-drop',
'newproducts' => 'new-products',
'seopage' => 'category',
);
return isset($custom_names[$controller_name]) ? $custom_names[$controller_name] : $controller_name;
}
public function sanitizeURL($url, $remove_page_param = true)
{
$url = Tools::safeOutput($url);
if ($remove_page_param) {
$url = preg_replace('/(?:(\?)|&)'.$this->page_link_rewrite_text.'=\d+/', '$1', $url);
}
return $url;
}
public function prepareTreeValues($values, $id_root)
{
$tree_values = array();
foreach ($values as $v) {
$tree_values[$v['id_parent']][$v['id']] = $v;
}
return isset($tree_values[$id_root]) ? $tree_values : array();
}
public function getSpecificSorting($controller)
{
$specific_sorting = array(
'newproducts' => array('by' => 'date_add', 'way' => 'desc'),
'pricesdrop' => array('by' => 'price', 'way' => 'asc'),
'search' => array('by' => 'position', 'way' => 'desc'),
);
return isset($specific_sorting[$controller]) ? $specific_sorting[$controller] : false;
}
public function getProductSorting()
{
if (!isset($this->context->forced_sorting)) {
$sorting = array(
'by' => Tools::getValue('orderby', $this->settings['general']['default_order_by']),
'way' => Tools::getValue('orderway', $this->settings['general']['default_order_way'])
);
if ($this->is_17) {
$order = explode('.', Tools::getValue('order'));
if (count($order) == 3) {
$sorting = array('by' => $order[1], 'way' => $order[2]);
}
}
$this->validateSorting($sorting);
$this->context->forced_sorting = $sorting;
}
return $this->context->forced_sorting;
}
public function validateSorting(&$sorting)
{
foreach ($sorting as $key => $value) {
$available_options = $this->getOptions('order'.$key);
if (!isset($available_options[$value])) {
$sorting[$key] = current(array_keys($available_options));
}
}
}
public function displayHook($hook_name)
{
if (empty($this->params_defined)) {
return;
}
$this->context->smarty->assign(array(
'hook_name' => $hook_name,
));
$html = $this->display(__FILE__, 'amazzingfilter.tpl');
if ($this->settings['general']['p_type'] > 1 && $hook_name != 'displayHome') {
$html .= $this->display(__FILE__, 'dynamic-loading.tpl');
}
return $html;
}
public function sortByKey($array, $key)
{
$method_name = 'sortBy'.Tools::ucfirst($key);
if (method_exists($this, $method_name)) {
usort($array, array($this, $method_name));
} elseif (($all = $key == 'numbers_in_name') || $key == 'first_num') {
foreach ($array as &$el) {
$el['number'] = $this->extractNumberFromString($el['name'], $all);
}
$array = $this->sortByKey($array, 'number');
}
return $array;
}
public function extractNumberFromString($string, $all = true, $no_scientific_notation = true)
{
if ($replacements = $this->getNumReplacements()) {
$string = str_replace(array_keys($replacements), $replacements, $string);
}
$number = $all ? (float)preg_replace('/[^0-9.]/', '', $string) : (float)$string;
if ($no_scientific_notation) {
$number = $this->removeScientificNotation($number);
}
return $number;
}
public function removeScientificNotation($number)
{
return rtrim(rtrim(number_format($number, 12, '.', ''), 0), '.');
}
public function getNumReplacements()
{
if (!isset($this->num_replacements)) {
$this->num_replacements = array();
$standard_values = array('tho_sep' => '', 'dec_sep' => '.'); // tho before dec!
foreach ($standard_values as $key => $standard_value) {
if (!empty($this->settings['general'][$key]) && $this->settings['general'][$key] != $standard_value) {
$this->num_replacements[$this->settings['general'][$key]] = $standard_value;
}
}
}
return $this->num_replacements;
}
public function sortByPosition($a, $b)
{
return $a['position'] - $b['position'];
}
public function sortById($a, $b)
{
return $a['id'] - $b['id'];
}
public function sortByNumber($a, $b)
{
return $a['number'] > $b['number'] ? 1 : -1;
}
public function sortByName($a, $b)
{
return strcmp($a['name'], $b['name']);
}
public function assignSearchResultIDs($id_lang)
{
// s: 1.7; search_query: 1.6 or some 3rd party modules;
if ($query = Tools::getValue('s', Tools::getValue('search_query', Tools::getValue('ref')))) {
$query = Tools::replaceAccentedChars(urldecode($query));
$ajax = $this->context->controller->ajax;
if ($this->custom_search == 'ambjolisearch' && class_exists('AmbSearch')) {
$abjolisearchmodule = Module::getInstanceByName('ambjolisearch');
$searcher = new AmbSearch(true, $this->context, $abjolisearchmodule);
$id_cat = (int)Tools::getValue('ajs_cat');
$id_man = (int)Tools::getValue('ajs_man');
$searcher->search($id_lang, $query, 1, null, 'position', 'desc', $id_cat, $id_man);
$this->controller_product_ids = $searcher->getResultIds();
} elseif ($this->custom_search == 'elasticjetsearch') {
$ejs_module = Module::getInstanceByName('elasticjetsearch');
$params = array(
'order_by' => 'position',
'order_way' => 'desc',
'page' => 1,
'items_per_page' => 100000,
'af' => 1, // may be used for improved compatibility
);
$this->controller_product_ids = $ejs_module->getElasticManager()->search($query, $params)->getList();
} else {
$this->context->properties_not_required = 1;
if (class_exists('IqitSearch') && !$this->is_17) {
$search_query_cat = (int)Tools::getValue('search_query_cat');
$search = IqitSearch::find($id_lang, $query, $search_query_cat, 1, 100000);
} elseif ($this->custom_search == 'tmsearch' && class_exists('TmSearchSearch')) {
$searcher = new TmSearchSearch();
$search_query_cat = Tools::getValue('search_categories');
$search = $searcher->tmfind($id_lang, $query, $search_query_cat, 1, 100000);
} elseif ($this->custom_search == 'leoproductsearch' && class_exists('ProductSearch')) {
$search = ProductSearch::find(
$id_lang,
$query,
1,
100000,
'position',
'desc',
$ajax,
true,
$this->context,
Tools::getValue('cate')
);
} else {
$search = Search::find($id_lang, $query, 1, 100000, 'position', 'desc', $ajax);
}
$this->context->properties_not_required = 0;
$search_result = isset($search['result']) ? $search['result'] : $search;
foreach ($search_result as $product) {
$this->controller_product_ids[] = $product['id_product'];
}
}
// sorting by position is reverse in search results
$this->controller_product_ids = array_reverse($this->controller_product_ids);
} elseif ($tag = Tools::getValue('tag')) {
$tag = Tools::replaceAccentedChars(urldecode($tag));
$products = $this->db->executeS('
SELECT pt.id_product FROM '._DB_PREFIX_.'tag t
INNER JOIN '._DB_PREFIX_.'product_tag pt ON pt.id_tag = t.id_tag
'.Shop::addSqlAssociation('product', 'pt').'
WHERE t.name LIKE \'%'.pSQL($tag).'%\'
AND t.id_lang = '.(int)$id_lang.' AND product_shop.active = 1
');
foreach ($products as $product) {
$this->controller_product_ids[] = $product['id_product'];
}
}
}
public function detectPossibleCustomSearchController($controller)
{
$compatible_modules = array(
'module-ambjolisearch-jolisearch' => 'ambjolisearch',
'module-leoproductsearch-productsearch' => 'leoproductsearch',
'module-iqitsearch-searchiqit' => 'iqitsearch',
'module-tmsearch-tmsearch' => 'tmsearch',
);
$this->custom_search = false;
if (isset($compatible_modules[$controller])) {
$this->custom_search = $compatible_modules[$controller];
} elseif ($controller == 'search' && Module::getModuleIdByName('elasticjetsearch') &&
Module::isEnabled('elasticjetsearch')) {
$this->custom_search = 'elasticjetsearch';
}
return $this->custom_search;
}
public function detectController()
{
if (!empty($this->context->controller->seopage_data)) {
$controller = 'seopage';
} else {
$c = Tools::getValue('controller');
if (Tools::getValue('fc') == 'module' && Tools::isSubmit('module')) {
$c = 'module-'.Tools::getValue('module').'-'.$c;
}
$available_controllers = $this->getAvailableControllers(true);
$controller = isset($available_controllers[$c]) ? $c : false;
if ((!$controller || $controller == 'search') && $this->detectPossibleCustomSearchController($c)) {
$controller = 'search';
}
}
return $controller;
}
public function defineFilterParams()
{
if (isset($this->params_defined)) {
return $this->params_defined;
}
$this->params_defined = false;
if (!$controller = $this->detectController()) {
return false;
}
$id_lang = $this->context->language->id;
$this->id_cat_current = Tools::getValue('id_category', (int)$this->context->shop->getCategory());
$this->controller_product_ids = array();
$current_id = in_array($controller, $this->getControllersWithMultipleIDs()) ?
Tools::getValue('id_'.$controller) : 0;
if (!empty($this->context->controller->seopage_data)) {
$current_id = $this->context->controller->seopage_data['id_seopage'];
}
$template = $this->db->getRow('
SELECT t.id_template, t.template_filters AS filters, t.additional_settings, tl.data AS lang
FROM '._DB_PREFIX_.'af_templates t
LEFT JOIN '._DB_PREFIX_.'af_templates_lang tl
ON tl.id_template = t.id_template
AND tl.id_shop = t.id_shop
AND tl.id_lang = '.(int)$id_lang.'
'.($current_id ? ' INNER JOIN '._DB_PREFIX_.'af_'.pSQL($controller).'_templates ct
ON ct.id_template = t.id_template AND ct.id_shop = t.id_shop
AND (ct.id_'.pSQL($controller).' = '.(int)$current_id.' OR ct.id_'.pSQL($controller).' = 0)' : '').'
WHERE t.active = 1 AND t.template_controller = \''.pSQL($controller).'\'
AND t.id_shop = '.(int)$this->context->shop->id.'
ORDER BY '.($current_id ? 'ct.id_'.pSQL($controller).' DESC, ' : '').'t.id_template DESC
');
if (empty($template['filters'])) {
return false;
} elseif ($controller != 'category') {
switch ($controller) {
case 'pricesdrop':
case 'bestsales':
case 'newproducts':
$this->controller_product_ids = $this->getSpecialControllerIds($controller);
break;
case 'search':
$this->assignSearchResultIDs($id_lang);
break;
case 'manufacturer':
case 'supplier':
if (!Tools::getValue('id_'.$controller)) {
return false;
}
break;
case 'index':
if (!$this->is_17) {
$this->addCSS('product_list.css', _THEME_CSS_DIR_);
}
break;
}
}
$this->defineSettings();
$this->current_controller = $controller;
$additional_settings = Tools::jsonDecode($template['additional_settings'], true);
$this->settings['general'] = $additional_settings + $this->settings['general'];
if ($this->settings['general']['default_order_by'] == 'random') {
$this->settings['general']['default_order_way'] = 'desc'; // keep same way for random ordering
}
$filters = Tools::jsonDecode($template['filters'], true);
$filters_lang = $template['lang'] ? Tools::jsonDecode($template['lang'], true) : array();
$current_filters = array_merge_recursive($filters, $filters_lang);
if (!empty($this->context->controller->seopage_data)) {
$this->context->controller->seopage_data['all_required_filters_hidden'] = true;
foreach ($this->context->controller->seopage_data['required_filters'] as $key => $forced_values) {
if (!isset($current_filters[$key])) {
$current_filters[$key] = array('type' => 'hidden');
if ($key == 'c') {
$current_filters[$key]['nesting_lvl'] = 0;
}
} else {
$this->context->controller->seopage_data['all_required_filters_hidden'] = false;
}
$current_filters[$key]['forced_values'] = $forced_values;
}
}
$this->prepareTplVariables($current_filters);
$this->params_defined = true;
return true;
}
public function isNewQuery($alias = 'product_shop')
{
$nb_days_new = Configuration::get('PS_NB_DAYS_NEW_PRODUCT');
return pSQL($alias).'.date_add > "'.pSQL(date('Y-m-d H:i:s', strtotime('-'.(int)$nb_days_new.' DAY'))).'"';
}
public function getSpecialControllerIds($controller)
{
$ids = $items = array();
switch ($controller) {
case 'newproducts':
$items = $this->db->executeS('
SELECT id_product
FROM '._DB_PREFIX_.'product_shop product_shop
WHERE id_shop = '.(int)$this->context->shop->id.'
AND active = 1 AND '.$this->isNewQuery().'
');
break;
case 'bestsales':
$items = $this->db->executeS('
SELECT ps.id_product
FROM '._DB_PREFIX_.'product_sale ps
'.Shop::addSqlAssociation('product', 'ps').'
WHERE product_shop.active = 1
ORDER BY ps.quantity DESC
');
break;
case 'pricesdrop':
$id_address = $this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
$a = Address::getCountryAndState($id_address);
$id_country = (int)$a['id_country'] ?: (int)Configuration::get('PS_COUNTRY_DEFAULT');
$current_date = date('Y-m-d H:i:00');
$ids = SpecificPrice::getProductIdByDate(
$this->context->shop->id,
$this->context->currency->id,
$id_country,
$this->context->customer->id_default_group,
$current_date,
$current_date,
$this->context->customer->id
);
$ids = array_combine($ids, $ids);
break;
}
foreach ($items as $i) {
$ids[$i['id_product']] = $i['id_product'];
}
return $ids;
}
public function hookDisplayLeftColumn()
{
return $this->displayHook('displayLeftColumn');
}
public function hookDisplayRightColumn()
{
return $this->displayHook('displayRightColumn');
}
public function hookDisplayTopColumn()
{
return $this->displayHook('displayTopColumn');
}
public function hookDisplayAmazzingFilter()
{
return $this->displayHook('displayAmazzingFilter');
}
public function hookDisplayHome()
{
return $this->displayHook('displayHome');
}
/**
* index Product when customer clicks save in 1.6
* actionIndexProduct is defined in override/controllers/admin/AdminProductController.php
*/
public function hookActionIndexProduct($params)
{
if (!empty($params['product'])) {
$id_product = is_object($params['product']) ? $params['product']->id : $params['product'];
// index products only in context shops if not defined otherwise
$shop_ids = isset($params['shop_ids']) ? $params['shop_ids'] : $this->shop_ids;
return empty($params['unindex_all']) ? $this->indexProduct($id_product, $shop_ids) :
$this->unindexProducts($id_product, $shop_ids);
}
}
public function hookActionProductAdd($params)
{
return $this->hookActionProductUpdate($params);
}
public function hookActionProductUpdate($params)
{
$this->defineSettings();
if (!empty($params['id_product']) && $this->readyToIndexOnProductUpdate()) {
// this hook can be called anywhere, so make sure product is indexed for all shops if not defined otherwise
$shop_ids = isset($params['shop_ids']) ? $params['shop_ids'] : $this->all_shop_ids;
$this->indexProduct((int)$params['id_product'], $shop_ids);
}
}
public function readyToIndexOnProductUpdate()
{
if (!empty($this->context->controller) && get_class($this->context->controller) == 'AdminProductsController') {
// product sheet or product list
$ready = true;
if ($this->is_17 && Tools::isSubmit('combinations')) {
// 1.7: when standard sheet is submitted, actionProductUpdate is called multile times:
// 1 time after $adminProductController->postCoreProcess()
// then 1 time for each combination: $adminProductWrapper->processProductAttribute()
// if no combinations, it is called 1 extra time in $adminProductWrapper->processQuantityUpdate()
// check: /src/PrestaShopBundle/Controller/Admin/ProductController.php
if (!isset($this->expected_extra_hook_calls)) {
$this->expected_extra_hook_calls = count(Tools::getValue('combinations')) ?: 1;
}
if (!empty($this->expected_extra_hook_calls)) {
$this->expected_extra_hook_calls--;
$ready = false; // wait for last hook call
}
} elseif (!$this->is_17 && Tools::isSubmit('submitted_tabs')) {
// 1.6: when standard sheet is submitted, not all data is available on actionProductUpdate
// because $this->processFeatures() is called after $object->update() in AdminProductsController.php
// so product is not indexed here. It will be indexed when actionIndexProduct is called in override
$ready = false;
}
} else {
$ready = $this->settings['indexation']['auto'];
}
return $ready;
}
public function hookActionObjectCombinationAddAfter($params)
{
// save this value for reindexing product after mass combinations generation in 1.6
if (!$this->is_17 && empty($this->context->cookie->af_index_product)) {
$this->context->cookie->__set('af_index_product', $params['object']->id_product);
}
}
public function hookActionObjectAddAfter($params)
{
$this->hookActionObjectUpdateAfter($params);
}
public function hookActionObjectDeleteAfter($params)
{
$this->hookActionObjectUpdateAfter($params);
}
public function hookActionObjectUpdateAfter($params)
{
if (isset($params['object']) && $cls = get_class($params['object'])) {
$cache_dependencies = array(
'Category' => 'c_list',
'Attribute' => 'a_list',
'FeatureValue' => 'f_list',
'Combination' => 'comb_data',
'Order' => 'comb_data',
'StockAvailable' => 'comb_data',
);
if (isset($cache_dependencies[$cls])) {
$this->cache('clear', $cache_dependencies[$cls]);
} elseif (in_array($cls, array('Language', 'Currency', 'Group'))) {
$this->cache('clear', 'indexationColumns');
$this->i['suffixes'] = array();
$this->indexationColumns('adjust');
}
}
}
public function hookActionAdminTagsControllerSaveAfter()
{
$id_lang = Tools::getValue('id_lang');
$id_tag = Tools::getValue('id_tag');
$product_ids = Tools::getValue('products');
$this->updateTagInIndex($id_lang, $id_tag, $product_ids);
}
public function hookActionAdminTagsControllerDeleteBefore($params)
{
$id_tag = Tools::getValue('id_tag');
$id_lang = (int)$this->db->getValue('
SELECT id_lang FROM '._DB_PREFIX_.'tag WHERE id_tag = '.(int)$id_tag.'
');
$this->context->tag_to_delete = array(
'id_tag' => $id_tag,
'id_lang' => $id_lang,
);
}
public function hookActionAdminTagsControllerDeleteAfter($params)
{
if (!empty($this->context->tag_to_delete)) {
$id_lang = $this->context->tag_to_delete['id_lang'];
$id_tag = $this->context->tag_to_delete['id_tag'];
$this->updateTagInIndex($id_lang, $id_tag);
}
}
public function updateTagInIndex($id_lang, $id_tag, $product_ids = array())
{
$var_data = $this->indexationColumns('getVariableData');
if (isset($var_data['t']) && in_array($id_lang, $var_data['t'])) {
$product_ids = $this->formatIDs($product_ids);
$upd_rows = array();
$t_col = 't_'.$id_lang;
$upd_columns = 'id_product, id_shop, '.$t_col;
// tag may be removed from some products and added to others, so check all rows
$data = $this->db->executeS('SELECT '.pSQL($upd_columns).' FROM '.pSQL($this->i['table']));
foreach ($data as $row) {
$tags = $this->formatIDs($row[$t_col]);
if (isset($product_ids[$row['id_product']])) {
$tags[$id_tag] = $id_tag;
} else {
unset($tags[$id_tag]);
}
$tags = implode(',', $tags);
if ($tags != $row[$t_col]) {
$row[$t_col] = $tags;
$upd_rows[] = '(\''.implode('\', \'', $row).'\')';
}
}
if ($upd_rows) {
$this->db->execute('
INSERT INTO '.pSQL($this->i['table']).' ('.pSQL($upd_columns).')
VALUES '.implode(', ', $upd_rows).'
ON DUPLICATE KEY UPDATE '.pSQL($t_col).'=VALUES('.pSQL($t_col).')
');
}
}
}
public function hookActionProductDelete($params)
{
if (!empty($params['product']->id)) {
$id_product = $params['product']->id;
$this->unindexProducts(array($id_product));
}
}
public function hookActionProductListOverride($params)
{
if (!isset($this->products_data)) {
return;
}
$params['hookExecuted'] = true;
$params['catProducts'] = $this->products_data['products'];
$params['nbProducts'] = $this->products_data['filtered_ids_count'];
}
public function getFeatures($id_lang, $id_group = false, $merge_if_required = true)
{
$f = $this->db->executeS('
SELECT v.id_feature_value AS id, v.id_feature AS id_group, v.custom,
vl.value AS name, fl.name AS group_name
FROM '._DB_PREFIX_.'feature_value v
INNER JOIN '._DB_PREFIX_.'feature_value_lang vl
ON (v.id_feature_value = vl.id_feature_value AND vl.id_lang = '.(int)$id_lang.')
INNER JOIN '._DB_PREFIX_.'feature f
ON f.id_feature = v.id_feature
INNER JOIN '._DB_PREFIX_.'feature_lang fl
ON (fl.id_feature = v.id_feature AND fl.id_lang = '.(int)$id_lang.')
'.($id_group ? ' AND v.id_feature = '.(int)$id_group : '').'
ORDER BY vl.value, v.id_feature_value
');
if ($merge_if_required && !empty($this->settings['general']['merged_features'])) {
$f = $this->mergedValues()->mapRows($f, $id_lang, $id_group, 'feature');
}
return $f;
}
public function getAttributes($id_lang, $id_group = false, $merge_if_required = true)
{
$a = $this->db->executeS('
SELECT DISTINCT a.id_attribute AS id, a.position, a.color, al.name,
agl.public_name AS group_name, ag.id_attribute_group AS id_group, ag.is_color_group
FROM '._DB_PREFIX_.'attribute_group ag
INNER JOIN '._DB_PREFIX_.'attribute_group_lang agl
ON (ag.id_attribute_group = agl.id_attribute_group AND agl.id_lang = '.(int)$id_lang.')
INNER JOIN '._DB_PREFIX_.'attribute a
ON a.id_attribute_group = ag.id_attribute_group
INNER JOIN '._DB_PREFIX_.'attribute_lang al
ON (a.id_attribute = al.id_attribute AND al.id_lang = '.(int)$id_lang.')
'.Shop::addSqlAssociation('attribute_group', 'ag').'
'.Shop::addSqlAssociation('attribute', 'a').'
WHERE a.id_attribute IS NOT NULL AND al.name IS NOT NULL AND agl.id_attribute_group IS NOT NULL
'.($id_group ? ' AND ag.id_attribute_group = '.(int)$id_group : '').'
ORDER BY al.name, a.id_attribute
');
if ($merge_if_required && !empty($this->settings['general']['merged_attributes'])) {
$a = $this->mergedValues()->mapRows($a, $id_lang, $id_group, 'attribute');
}
return $a;
}
public function generateLink($string, $identifier = '', $group = 'default')
{
$string = str_replace(array(',', '.', '*'), '-', $string);
$link = Tools::str2url($string) ?: $identifier;
return $this->getUniqueLink($link, $identifier, $group);
}
public function getUniqueLink($link, $identifier, $group)
{
if (!isset($this->generated_links[$group][$link])) {
$this->generated_links[$group][$link] = 1;
} elseif ($identifier) {
$link = $identifier.($link ? '-'.$link : '');
}
return $link;
}
public function ajaxEraseIndex()
{
$id_shop = Tools::getValue('id_shop');
$deleted = $this->indexationData('erase', array('id_shop' => $id_shop));
$indexation_data = $this->indexationInfo('count', array($id_shop));
$missing = isset($indexation_data[$id_shop]['missing']) ? $indexation_data[$id_shop]['missing']: 0;
$ret = array(
'deleted' => $deleted,
'missing' => $missing,
);
exit(Tools::jsonEncode($ret));
}
public function ajaxRunProductIndexer($all_identifier, $products_per_request = 1000)
{
$ret = array();
if ($all_identifier) {
$this->reIndexProducts($all_identifier, $products_per_request);
$ret['indexation_process_data'] = $this->getIndexationProcessData($all_identifier, true);
} else {
$this->indexMissingProducts($products_per_request);
}
$ret['indexation_data'] = $this->indexationInfo('count');
exit(Tools::jsonEncode($ret));
}
public function reIndexProducts($all_identifier, $products_per_request, $shop_ids = array())
{
if (!$saved_data = $this->getIndexationProcessData($all_identifier, false)) {
$saved_data = array('identifier' => $all_identifier, 'data' => array());
foreach ($this->indexationInfo('ids', $shop_ids, true) as $id_shop => $data) {
$saved_data['data'][$id_shop]['missing'] = array_merge($data['indexed'], $data['missing']);
$saved_data['data'][$id_shop]['indexed'] = array();
}
}
$indexed_num = 0;
foreach ($saved_data['data'] as $id_shop => &$data) {
if (empty($data['missing'])) {
unset($saved_data['data'][$id_shop]);
} elseif ($ids_to_index = array_slice($data['missing'], 0, $products_per_request)) {
$indexed_ids = $this->indexProduct($ids_to_index, array($id_shop), false);
$data['missing'] = array_diff($data['missing'], $indexed_ids);
$data['indexed'] = array_merge($data['indexed'], $indexed_ids);
if (empty($data['missing'])) {
unset($saved_data['data'][$id_shop]);
}
$indexed_num += count($indexed_ids);
break;
}
}
$saved_data = !empty($saved_data['data']) ? $saved_data : array();
$this->saveData($this->indexation_process_file_path, $saved_data);
return $indexed_num;
}
public function indexMissingProducts($products_per_request, $shop_ids = array())
{
$indexed_num = 0;
foreach ($this->indexationInfo('ids', $shop_ids, true) as $id_shop => $data) {
if (!empty($data['missing'])) {
$product_ids = array_slice($data['missing'], 0, $products_per_request);
$this->indexProduct($product_ids, array($id_shop));
$indexed_num += count($product_ids);
break;
}
}
return $indexed_num;
}
public function getIndexationProcessData($all_identifier, $return_count = true)
{
$ret = $indexation_data = $this->getData($this->indexation_process_file_path, $all_identifier);
if ($return_count && !empty($indexation_data['data'])) {
$ret = array();
foreach ($indexation_data['data'] as $id_shop => $data) {
foreach ($data as $name => $ids) {
$ret[$id_shop][$name] = count($ids);
}
}
}
return $ret;
}
public function getData($path, $identifier = false)
{
$data = file_exists($path) ? Tools::jsonDecode(Tools::file_get_contents($path), true) : array();
if ($data && $identifier && (!isset($data['identifier']) || $data['identifier'].'' != $identifier.'')) {
$time_before_reset = 60;
$time_diff = $time_before_reset - (time() - filemtime($path));
if ($time_diff > 1) {
$err = $this->l('Please wait, someone else is performing same action').
'. '.sprintf($this->l('%s seconds left before automatic reset.'), $time_diff);
$this->throwError($err);
exit($err); // may be used in cron indexation and other non-ajax requests
} else {
$data = array();
}
}
return $data;
}
public function saveData($path, $data, $append = false)
{
$data = is_string($data) ? $data : Tools::jsonEncode($data);
if ($data) {
return $append ? file_put_contents($path, $data, FILE_APPEND) : file_put_contents($path, $data);
} else {
return unlink($path);
}
}
public function formatIDs($ids, $return_string = false)
{
$ids = is_array($ids) ? $ids : explode(',', $ids);
$ids = array_map('intval', $ids);
$ids = array_combine($ids, $ids);
unset($ids[0]);
return $return_string ? implode(',', $ids) : $ids;
}
public function getShopsForIndexation($predefined_ids = array())
{
if (!$shop_ids = $predefined_ids ?: $this->shop_ids) {
$shop_ids = array($this->context->shop->id);
}
return $shop_ids;
}
public function getAllParents($id_cat)
{
if (!isset($this->i['all_parents'][$id_cat])) {
$cat_obj = new Category($id_cat);
$this->i['all_parents'][$id_cat] = array_column($this->db->executeS('
SELECT id_category AS id FROM '._DB_PREFIX_.'category
WHERE nleft < '.(int)$cat_obj->nleft.' AND nright > '.(int)$cat_obj->nright.' AND id_parent > 0
ORDER BY level_depth ASC
'), 'id', 'id');
}
return $this->i['all_parents'][$id_cat];
}
public function getDataForPriceCalculation($id_shop)
{
if (!isset($this->i['p_data'][$id_shop])) {
foreach (array('group' => 'g', 'currency' => 'c') as $name => $key) {
$identifier = 'id_'.$name;
$default = Configuration::get($this->i['default'][$key], null, null, $id_shop);
$join_on = $identifier.' = main.'.$identifier;
$this->i['p_data'][$id_shop][$name] = array();
$query = new DbQuery();
$query->select('DISTINCT(main.'.pSQL($identifier).'), sp.id_specific_price AS has_specific_price');
$query->select('main.'.pSQL($identifier).' = '.pSQL($default).' AS is_default');
if ($name == 'currency') {
$query->select('s.conversion_rate');
} else {
$query->select('main.reduction, main.price_display_method AS no_tax');
}
$query->from($name, 'main');
$query->innerJoin($name.'_shop', 's', 's.'.pSQL($join_on).' AND s.id_shop = '.(int)$id_shop);
$query->leftJoin('specific_price', 'sp', 'sp.'.pSQL($join_on).' AND sp.id_shop = s.id_shop');
// default currency/group go first
$query->orderBy('main.'.pSQL($identifier).' <> '.pSQL($default).' ASC, main.'.pSQL($identifier).' ASC');
$query->where('1'.$this->specificIndexationQuery($identifier, 'main', $id_shop));
foreach ($this->db->executeS($query) as $row) {
$this->i['p_data'][$id_shop][$name][$row[$identifier]] = $row;
}
}
}
return $this->i['p_data'][$id_shop];
}
public function getSuffixes($resource, $id_shop = 0, $validate = true)
{
if (!isset($this->i['suffixes'][$id_shop][$resource])) {
$c_name = 'id_'.$resource;
$suffixes = array_column($this->db->executeS('
SELECT main.'.pSQL($c_name).' FROM '._DB_PREFIX_.pSQL($resource).' main
'.($id_shop ? ' INNER JOIN '._DB_PREFIX_.pSQL($resource).'_shop s
ON s.'.pSQL($c_name).' = main.'.pSQL($c_name).' AND s.id_shop = '.(int)$id_shop : '').'
WHERE 1'.$this->specificIndexationQuery($c_name, 'main', $id_shop).'
ORDER BY main.'.pSQL($c_name).' ASC
'), $c_name, $c_name); // NOTE: result is ordered by ID, not like in getDataForPriceCalculation
if ($validate) {
$this->validateSuffixes($suffixes, $resource, $id_shop);
}
$this->i['suffixes'][$id_shop][$resource] = $suffixes;
}
return $this->i['suffixes'][$id_shop][$resource];
}
public function validateSuffixes(&$suffixes, $resource, $id_shop)
{
if (count($suffixes) > $this->i['max_column_suffixes']) {
$dependent_options = array('group' => 'p_g', 'currency' => 'p_c');
if (isset($dependent_options[$resource])) {
$this->settings['indexation'][$dependent_options[$resource]] = 0;
$suffixes = $this->getSuffixes($resource, $id_shop, false);
} else {
$suffixes = array();
}
}
}
public function specificIndexationQuery($c_name, $t_name, $id_shop = 0, $check_settings = true)
{
$where = '';
$specific_resources = array('id_group' => 'g', 'id_currency' => 'c');
if (isset($specific_resources[$c_name])) {
$key = $specific_resources[$c_name];
if ($check_settings && !$this->settings['indexation']['p_'.$key] && isset($this->i['default'][$key])) {
$config_key = $this->i['default'][$key];
$where = ' AND '.pSQL($t_name.'.'.$c_name);
if ($id_shop) {
$where .= ' = '.(int)Configuration::get($config_key, null, null, $id_shop);
} else {
$default_ids = array();
foreach ($this->all_shop_ids as $id_shop) {
$default_ids[] = Configuration::get($config_key, null, null, $id_shop);
}
$where .= ' IN ('.pSQL($this->formatIDs($default_ids, true)).')';
}
}
if ($key == 'c') {
$where .= ' AND '.pSQL($t_name).'.deleted = 0 AND '.pSQL($t_name).'.active = 1';
}
} elseif ($c_name == 'id_lang') {
$where = ' AND '.pSQL($t_name).'.active = 1';
}
return $where;
}
public function prepareContextForIndexation($id_shop)
{
if (empty($this->context->currency)) {
$this->context->currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
}
if (empty($this->context->employee) && empty($this->context->cart)) {
$this->context->cart = new Cart();
}
$this->backup_context = array(
'shop_context' => Shop::getContext(),
'shop_context_id' => null,
'shop_id' => $this->context->shop->id,
'currency_id' => $this->context->currency->id,
'customer_id' => !empty($this->context->customer) ? (int)$this->context->customer->id : 0,
);
if ($this->backup_context['shop_context'] == Shop::CONTEXT_GROUP) {
$this->backup_context['shop_context_id'] = $this->context->shop->id_shop_group;
} elseif ($this->backup_context['shop_context'] == Shop::CONTEXT_SHOP) {
$this->backup_context['shop_context_id'] = $this->context->shop->id;
}
// the following context values will be used for calculating prices and later restored in restoreContext()
$this->context->customer = new Customer();
$this->context->shop = new Shop($id_shop);
Shop::setContext(Shop::CONTEXT_SHOP, $id_shop);
}
public function setCustomContextValues($id_group, $id_currency)
{
// specifc group/currency are used for calculating prices and later restored in restoreContext()
$this->context->customer->id_default_group = $id_group;
if ($this->context->currency->id != $id_currency) {
$this->context->currency = new Currency($id_currency);
}
}
public function restoreContext()
{
if (!empty($this->backup_context)) {
Shop::setContext($this->backup_context['shop_context'], $this->backup_context['shop_context_id']);
$this->context->shop = new Shop($this->backup_context['shop_id']);
$this->context->currency = new Currency($this->backup_context['currency_id']);
$this->context->customer = new Customer($this->backup_context['customer_id']);
}
}
public function indexProduct($product_ids, $shop_ids = array(), $return_string = true)
{
if (Tools::getValue('no_indexation')) {
$product_ids = array();
}
foreach ($this->getShopsForIndexation($shop_ids) as $id_shop) {
$this->updateIndexationData($product_ids, $id_shop);
}
return $this->formatIDs($product_ids, $return_string);
}
public function updateIndexationData($product_ids, $id_shop, $params = array())
{
if (!$product_ids = $this->formatIDs($product_ids)) {
return;
}
$indexation_columns = $this->prepareColumnsForIndexation($id_shop, $params);
$column_names = $rows = $upd = array();
$this->prepareContextForIndexation($id_shop);
$ids_to_unindex = array();
foreach ($product_ids as $id) {
$p_obj = new Product($id, false, null, $id_shop);
if (!$p_obj->active || $p_obj->visibility == 'none') {
$ids_to_unindex[] = $p_obj->id;
continue;
}
$forced_values = isset($params['main_values'][$id]) ?: array();
$row = array('id_product' => $id, 'id_shop' => $id_shop);
foreach ($indexation_columns['main'] as $c_name) {
$value = isset($forced_values[$c_name]) ? $forced_values[$c_name] :
$this->prepareIndexationValue($p_obj, $id_shop, $c_name);
$row[$c_name] = pSQL(is_array($value) ? $this->formatIDs($value, true) : $value);
}
foreach ($indexation_columns['variable'] as $c_name => $c_suffixes) {
$value = isset($forced_values[$c_name]) ? $forced_values[$c_name] :
$this->prepareIndexationValue($p_obj, $id_shop, $c_name);
foreach ($c_suffixes as $suffix) {
$v = isset($value[$suffix]) ? $value[$suffix] : '';
$row[$c_name.'_'.$suffix] = pSQL(is_array($v) ? $this->formatIDs($v, true) : $v);
}
}
$rows[] = '(\''.implode('\', \'', $row).'\')';
if (!$column_names) {
$column_names = array_keys($row);
}
}
if ($ids_to_unindex) {
$this->unindexProducts($ids_to_unindex, array($id_shop));
}
$this->restoreContext();
if ($column_names && $rows && $upd = array_diff($column_names, $indexation_columns['primary'])) {
foreach ($upd as $i => $c_name) {
$upd[$i] = $c_name.' = VALUES('.$c_name.')';
}
$query = array('
INSERT INTO '.pSQL($this->i['table']).' ('.implode(', ', $column_names).')
VALUES '.implode(', ', $rows).' ON DUPLICATE KEY UPDATE '.pSQL(implode(', ', $upd)).'
');
return $this->runSql($query);
}
}
public function prepareColumnsForIndexation($id_shop, $params = array())
{
// $params = ['main_columns' => ['f'], 'variable_columns' => []]; // re-index only features
// $params = ['main_columns' => ['a'], 'variable_columns' => ['t']]; // re-index only attributes and tags
$indexation_columns = $this->indexationColumns('getRequired', $id_shop, 3600);
if (isset($params['main_columns'])) {
$indexation_columns['main'] = array_intersect($indexation_columns['main'], $params['main_columns']);
}
if (isset($params['variable_columns'])) {
foreach (array_keys($indexation_columns['variable']) as $key) {
if (!in_array($key, $params['variable_columns'])) {
unset($indexation_columns['variable'][$key]);
}
}
}
return $indexation_columns;
}
public function prepareIndexationValue($p_obj, $id_shop, $type)
{
$value = array();
$id_product = $p_obj->id;
switch ($type) {
case 'c':
$value = array_column($this->db->executeS('
SELECT id_category AS id FROM '._DB_PREFIX_.'category_product cp
WHERE id_product = '.(int)$id_product.'
'), 'id', 'id');
if ($this->settings['indexation']['subcat_products']) { // indexation settings are same for all shops
foreach ($value as $id_cat) {
$value += $this->getAllParents($id_cat);
}
}
break;
case 'a':
$atts = $this->db->executeS('
SELECT pac.id_attribute, map.id_merged FROM '._DB_PREFIX_.'product_attribute_combination pac
LEFT JOIN '._DB_PREFIX_.'af_merged_attribute_map map ON map.id_original = pac.id_attribute
INNER JOIN '._DB_PREFIX_.'product_attribute pa
ON pa.id_product_attribute = pac.id_product_attribute AND pa.id_product = '.(int)$id_product.'
INNER JOIN '._DB_PREFIX_.'product_attribute_shop pas
ON pas.id_product_attribute = pa.id_product_attribute AND pas.id_shop = '.(int)$id_shop.'
');
foreach ($atts as $att) {
$value[$att['id_attribute']] = $att['id_attribute'];
if (!empty($att['id_merged'])) {
$value['map'.$att['id_merged']] = 'map'.$att['id_merged'];
}
}
$value = implode(',', $value); // skip formatIDs in updateIndexationData because of possible map_xx
break;
case 'f':
$feats = $this->db->executeS('
SELECT fp.id_feature_value, map.id_merged FROM '._DB_PREFIX_.'feature_product fp
LEFT JOIN '._DB_PREFIX_.'af_merged_feature_map map ON map.id_original = fp.id_feature_value
WHERE fp.id_product = '.(int)$id_product.'
');
foreach ($feats as $feat) {
$value[$feat['id_feature_value']] = $feat['id_feature_value'];
if (!empty($feat['id_merged'])) {
$value['map'.$feat['id_merged']] = 'map'.$feat['id_merged'];
}
}
$value = implode(',', $value); // same as atts
break;
case 's':
$value = array_column($this->db->executeS('
SELECT id_supplier AS id FROM '._DB_PREFIX_.'product_supplier
WHERE id_product = '.(int)$id_product.'
'), 'id', 'id');
break;
case 'w':
if ($ipa = Product::getDefaultAttribute($id_product)) {
$value = (float)$this->db->getValue('
SELECT SUM(p.weight + pas.weight)
FROM '._DB_PREFIX_.'product p
LEFT JOIN '._DB_PREFIX_.'product_attribute pa
ON (pa.id_product = p.id_product AND pa.id_product_attribute = '.(int)$ipa.')
LEFT JOIN '._DB_PREFIX_.'product_attribute_shop pas
ON (pas.id_product_attribute = pa.id_product_attribute AND pas.id_shop = '.(int)$id_shop.')
WHERE p.id_product = '.(int)$id_product.'
');
} else {
$value = (float)$this->db->getValue('
SELECT weight FROM '._DB_PREFIX_.'product WHERE id_product = '.(int)$id_product.'
');
}
break;
case 'p':
$default_prices = array();
$calc_data = $this->getDataForPriceCalculation($id_shop);
foreach ($calc_data['currency'] as $id_currency => $c) {
$group_tax_prices = array();
foreach ($calc_data['group'] as $id_group => $g) {
$group_has_specific_price = $g['has_specific_price'] || $g['reduction'] > 0;
if (!$c['has_specific_price'] && isset($default_prices[$id_group])) {
$price = Tools::ps_round($default_prices[$id_group] * $c['conversion_rate'], 2);
} elseif (!$group_has_specific_price && isset($group_tax_prices[$g['no_tax']])) {
$price = $group_tax_prices[$g['no_tax']];
} else {
$this->setCustomContextValues($id_group, $id_currency);
$price = Product::getPriceStatic($id_product, !$g['no_tax'], null, 2);
}
if (!$group_has_specific_price) {
$group_tax_prices[$g['no_tax']] = $price;
}
if ($c['is_default'] && !$c['has_specific_price']) {
$default_prices[$id_group] = $price; // default currency is first in loop
}
$value[$id_group.'_'.$id_currency] = $price;
}
}
break;
case 't':
$tags = $this->db->executeS('
SELECT t.id_tag, t.id_lang FROM '._DB_PREFIX_.'tag t
INNER JOIN '._DB_PREFIX_.'product_tag pt
ON (pt.id_tag = t.id_tag AND pt.id_product = '.(int)$id_product.')
');
foreach ($tags as $t) {
$value[$t['id_lang']][$t['id_tag']] = $t['id_tag'];
}
break;
case 'n':
case 'r':
case 'd':
case 'm':
$fields = array('n' => 'name', 'r' => 'reference', 'd' => 'date_add', 'm' => 'id_manufacturer');
$value = $p_obj->{$fields[$type]};
break;
case 'q':
$q = array('new' => 1, 'used' => 2, 'refurbished' => 3);
$value = isset($q[$p_obj->condition]) ? $q[$p_obj->condition] : 1;
break;
case 'v': // restricted visibility
$v = array('both' => 0, 'catalog' => 1, 'search' => 2, 'none' => 3);
$value = isset($v[$p_obj->visibility]) ? $v[$p_obj->visibility] : 0;
break;
case 'g': // restricted groups
$groups_having_access = array_column($this->db->executeS('
SELECT DISTINCT(cg.id_group) FROM '._DB_PREFIX_.'category_group cg
INNER JOIN '._DB_PREFIX_.'category_product cp
ON cp.id_category = cg.id_category AND cp.id_product = '.(int)$id_product.'
'), 'id_group');
if (!isset($this->i['available_groups'][$id_shop])) {
$this->i['available_groups'][$id_shop] = array_column($this->db->executeS('
SELECT DISTINCT(id_group) FROM '._DB_PREFIX_.'group_shop WHERE id_shop = '.(int)$id_shop.'
'), 'id_group');
}
$value = array_diff($this->i['available_groups'][$id_shop], $groups_having_access);
break;
}
return $value;
}
public function unindexProducts($product_ids, $shop_ids = array())
{
if ($product_ids = $this->formatIDs($product_ids)) {
$shop_ids = $shop_ids ? $shop_ids : $this->all_shop_ids;
return $this->indexationData('erase', array('id_product' => $product_ids, 'id_shop' => $shop_ids));
}
}
public function assignSmartyVariablesForPagination($page, $products_num, $npp, $current_url = '')
{
$pages_nb = $this->getNumberOfPages($products_num, $npp);
$siblings = 2; // 2 pages before and after active page in pagination
$this->context->smarty->assign(array(
'current_url' => $current_url,
'p' => $page,
'start' => ($page - $siblings > 1) ? $page-$siblings : 1,
'stop' => ($page + $siblings < $pages_nb) ? $page+$siblings : $pages_nb,
'pages_nb' => $pages_nb,
'nb_products' => $products_num,
'n' => $npp,
'products_per_page' => $npp,
// 'no_follow' => 1,
));
}
public function getNumberOfPages($products_num, $products_per_page)
{
return $products_per_page ? (int)ceil($products_num/$products_per_page) : 0;
}
public function ajaxGetFilteredProducts($params)
{
$this->current_controller = $params['current_controller'];
$products_data = $this->getFilteredProducts($params);
$this->context->forced_sorting = array('by' => $params['orderBy'], 'way' => $params['orderWay']);
$this->context->controller->addColorsToProductList($products_data['products']);
$this->context->smarty->assign(array(
'products' => $products_data['products'],
'class' => $this->product_list_class,
'page_name' => $params['page_name'],
'link' => $this->context->link,
'static_token' => Tools::getToken(false),
'af' => 1,
));
// assign smarty variables for pagination
$page = $products_data['page'];
$products_num = $products_data['filtered_ids_count'];
$npp = $products_data['products_per_page'];
$current_url = $this->sanitizeURL(Tools::getValue('current_url'));
$ret = array(
'product_count_text' => utf8_encode($products_data['product_count_text']),
'count_data' => $products_data['count_data'],
'ranges' => $products_data['ranges'],
'products_num' => $products_num,
'time' => $products_data['time'],
'hide_load_more_btn' => $products_data['hide_load_more_btn'],
'trigger' => $products_data['trigger'],
);
if (!empty($params['layout_required'])) {
$ret['layout'] = utf8_encode($this->renderLayout());
}
$this->specificThemeAjaxActions($params);
if ($this->is_17) {
// $controller->page_name is often used by third party modules or theme configurators in PS 1.7
$this->context->controller->page_name = $params['page_name'];
Hook::exec('actionProductSearchAfter', array('products' => $products_data['products']));
$current_sorting_option = 'product.'.$params['orderBy'].'.'.$params['orderWay'];
$default_sorting_option = 'product.'.$params['default_order_by'].'.'.$params['default_order_way'];
$options = $this->getSortingOptions($current_sorting_option, $default_sorting_option, $current_url);
$current_label = isset($options[$current_sorting_option]['label']) ?
$options[$current_sorting_option]['label'] : '';
$this->context->smarty->assign(array(
'listing' => array(
'products' => $products_data['products'],
'pagination' => $this->getPaginationVariables($page, $products_num, $npp, $current_url),
'sort_orders' => $options,
'sort_selected' => $current_label,
'current_url' => $current_url,
),
'urls' => $this->context->controller->getTemplateVarUrls(),
'configuration' => $this->context->controller->getTemplateVarConfiguration(),
'page' => array('page_name' => $params['page_name']),
));
$tpl_path = 'templates/catalog/_partials/';
$product_list_html = $this->fetchThemeTpl($tpl_path.'products.tpl');
$product_list_top_html = $this->fetchThemeTpl($tpl_path.'products-top.tpl');
$product_list_bottom_html = $this->fetchThemeTpl($tpl_path.'products-bottom.tpl');
$ret['product_list_top_html'] = utf8_encode($product_list_top_html);
$ret['product_list_bottom_html'] = utf8_encode($product_list_bottom_html);
} else {
if ($ret['trigger'] != 'af_page') {
$product_total_text = $products_num == 1 ? $this->l('There is 1 product.') :
sprintf($this->l('There are %d products.'), $products_num);
$ret['product_total_text'] = utf8_encode($product_total_text);
}
$this->context->smarty->assign(array(
'hide_left_column' => $params['hide_left_column'],
'hide_right_column' => $params['hide_right_column'],
));
$product_list_html = $this->context->smarty->fetch(_PS_THEME_DIR_.'product-list.tpl');
$this->assignSmartyVariablesForPagination($page, $products_num, $npp, $current_url);
$pagination_html = $this->context->smarty->fetch(_PS_THEME_DIR_.'pagination.tpl');
$this->context->smarty->assign('paginationId', $params['pagination_bottom_suffix']);
$pagination_bottom_html = $this->context->smarty->fetch(_PS_THEME_DIR_.'pagination.tpl');
$ret['pagination_html'] = utf8_encode($pagination_html);
$ret['pagination_bottom_html'] = utf8_encode($pagination_bottom_html);
}
if (!$products_num) {
$product_list_html = $this->display(__FILE__, 'views/templates/front/no-products.tpl');
}
$ret['product_list_html'] = utf8_encode($product_list_html);
if ($params['current_controller'] == 'seopage') {
$this->sp->extendAjaxResponse($ret, $params);
}
exit(Tools::jsonEncode($ret));
}
public function specificThemeAjaxActions(&$params)
{
$identifier = $this->getSpecificThemeIdentifier();
switch ($identifier) {
case 'warehouse-17':
$this->context->controller->php_self = $params['page_name']; // used in iqitthemeeditor
$available_views = array('grid' => 1, 'list' => 1);
$list_view = !empty($params['listView']) && !empty($available_views[$params['listView']]) ?
$params['listView'] : 'grid';
$this->context->cookie->__set('product_list_view', $list_view);
break;
case 'warehouse-16':
$this->context->controller->php_self = $params['page_name']; // used in themeeditor
break;
case 'ayon-16':
$this->context->smarty->assign(array('nc_p_hover' => Configuration::get('NC_P_HOVERS'),));
break;
case 'AngarTheme-17':
$this->context->smarty->assign(array(
'display_quickview' => (int)Configuration::get('PS_QUICK_VIEW'),
'psversion' => Configuration::get('ANGARTHEMECONFIGURATOR_PSVERSION'),
));
break;
case 'venedor-17':
if (!empty($this->context->controller->ajax) &&
$pkts_module = Module::getInstanceByName('pk_themesettings')) {
// $pkts_module->getOptions() returns too many opions.
// so we select only options, related to product_miniature
// some pm_ options are encoded, but they are not used in dynamic listing
$pkts_options = array_column($this->db->executeS('
SELECT name, value FROM '.pSQL($pkts_module->mdb).'
WHERE id_shop = '.(int)$this->context->shop->id.' AND name LIKE \'pm_%\'
'), 'value', 'name');
$this->context->smarty->assign(array('pkts' => $pkts_options));
}
break;
case 'at_decor-17':
case 'at_classico-17':
case 'at_oreo-17':
$apb = Module::getInstanceByName('appagebuilder');
if ($apb->active) {
$product_settings = ApPageBuilderProductsModel::getActive($apb->getConfig('USE_MOBILE_THEME'));
$this->context->smarty->assign(array(
'productProfileDefault' => $product_settings['plist_key'],
'productClassWidget' => $product_settings['class']
));
if (class_exists('apPageHelper')) {
apPageHelper::setGlobalVariable($this->context);
}
}
break;
}
}
public function fetchThemeTpl($path)
{
$html = '';
if (file_exists(_PS_THEME_DIR_.$path)) {
$html = $this->context->smarty->fetch(_PS_THEME_DIR_.$path);
} elseif (file_exists(_PS_PARENT_THEME_DIR_.$path)) {
$html = $this->context->smarty->fetch(_PS_PARENT_THEME_DIR_.$path);
}
return $html;
}
public function getSpecificThemeIdentifier()
{
return $this->getCurrentThemeName().'-'.($this->is_17 ? '17' : '16');
}
public function getCurrentThemeName()
{
$theme_name = _THEME_NAME_;
if ($this->is_17 && _PARENT_THEME_NAME_) {
$theme_name = _PARENT_THEME_NAME_; // _THEME_NAME_ can be different if child theme is used
}
return $theme_name;
}
public function renderLayout()
{
$this->context->smarty->assign(array(
'product_list_class' => $this->product_list_class,
'af_ids' => $this->settings['themeid'],
));
return $this->display(__FILE__, 'views/templates/front/basic-layout'.($this->is_17 ? '-17' : '').'.tpl');
}
public function formatOrder($by, $way)
{
$compact_order_names = array('name' => 'n', 'date_add' => 'd', 'reference' => 'r', 'price' => 'p');
if (isset($compact_order_names[$by])) {
$by = $compact_order_names[$by];
}
if (!in_array($way, array('asc', 'desc'))) {
$way = 'asc';
}
return array('by' => $by, 'way' => $way);
}
public function getFilteredProducts($params)
{
$start_time = microtime(true);
$this->prepareParamsForFiltering($params);
$filtered_data = $this->getFilteredData($params);
$ret = $this->prepareDataForDisplay($filtered_data, $params);
$ret['time'] = microtime(true) - $start_time;
// d(microtime(true) - $start_time);
return $ret;
}
public function prepareParamsForFiltering(&$params)
{
$params += array(
'id_shop' => $this->context->shop->id,
'id_shop_group' => $this->context->shop->id_shop_group,
'id_lang' => $this->context->language->id,
'id_currency' => $this->context->currency->id,
'id_customer_group' => $this->context->customer->id_default_group,
'trigger' => Tools::getValue('trigger', 'af_page'),
'order' => $this->formatOrder($params['orderBy'], $params['orderWay']),
'filter_identifiers' => $this->getFilterIdentifiers(),
'to_count_additionally' => array(),
'available_options' => array(), // empty array is used if available_options are not set in $params
);
$this->use_merged_attributes = !empty($params['merged_attributes']);
if (!empty($params['numeric_slider_values'])) {
$this->slider()->assignParamsForNumericSliders($params);
}
// define "or" params basing on primary_filter
if (!empty($params['primary_filter'])) {
$primary_filter = $params['primary_filter'];
$first_char = Tools::substr($primary_filter, 0, 1);
$or_id = Tools::substr($primary_filter, 1);
if (in_array($first_char, $params['filter_identifiers'])) {
if ($or_id && !empty($params[$first_char][$or_id])) {
$params['or-'.$first_char][$or_id] = $params[$first_char][$or_id];
unset($params[$first_char][$or_id]);
$key_for_additional_params = $primary_filter;
} elseif (!empty($params[$primary_filter])) {
$params['or-'.$primary_filter] = $params[$primary_filter];
unset($params[$primary_filter]);
$key_for_additional_params = $primary_filter;
}
if (isset($key_for_additional_params) &&
!empty($params['available_options'][$key_for_additional_params])) {
$to_count_additionally = $params['available_options'][$key_for_additional_params];
if (!is_array($to_count_additionally)) {
$to_count_additionally = explode(',', $to_count_additionally);
}
$params['to_count_additionally'] = array_flip($to_count_additionally);
}
}
}
// remove 0 options obtained from selects
foreach (array('', 'or-') as $prefix) {
foreach ($params['filter_identifiers'] as $i) {
$i = $prefix.$i;
if (!empty($params[$i])) {
if (isset($params[$i][0])) {
if (!$params[$i][0]) {
unset($params[$i]);
}
} else {
foreach (array_keys($params[$i]) as $id_group) {
if (!$params[$i][$id_group][0]) {
unset($params[$i][$id_group]);
}
}
}
if (empty($params[$i])) {
unset($params[$i]);
}
}
}
}
$params['selected_atts'] = isset($params['a']) ? $params['a'] : array();
if (isset($params['or-a'])) {
$params['selected_atts'] += $params['or-a'];
}
foreach ($params['selected_atts'] as $id_group => $atts) {
$params['selected_atts'][$id_group] = array_combine($atts, $atts);
}
ksort($params['selected_atts']);
$params['check_stock'] = !empty($params['combinations_stock']);
$params['check_existence'] = !empty($params['combinations_existence']) && $params['selected_atts'];
if ($params['check_stock'] || ($params['selected_atts'] && $params['combination_results'])) {
$this->selected_combinations = array();
}
if (isset($params['controller_product_ids'])) {
$params['controller_product_ids'] = $this->formatIDs($params['controller_product_ids']);
}
// ranges
$params['ranges'] = array('p' => array(), 'w' => array());
$params['non_slider_ranges'] = array();
foreach ($params['ranges'] as $identifier => $range) {
$range['selected_values'] = array();
if ($range['is_slider'] = isset($params['sliders'][$identifier])) { // sliders
$slider = $params['sliders'][$identifier];
if ($this->slider()->isTriggered($slider)) {
$range['selected_values'] = array(array($slider['from'], $slider['to']));
}
} elseif (isset($params['available_options'][$identifier])) { // range values
if (isset($params[$identifier])) {
$range['selected_values'] = $params[$identifier];
} elseif (isset($params['or-'.$identifier])) {
$range['selected_values'] = $params['or-'.$identifier];
}
foreach ($range['selected_values'] as &$val) {
$val = ExtendedTools::explodeRangeValue($val);
}
$range['step'] = isset($params[$identifier.'_range_step']) ? $params[$identifier.'_range_step'] : '';
$params['non_slider_ranges'][$identifier] = $identifier;
} else {
unset($params['ranges'][$identifier]);
continue;
}
$range['calculate_min_max'] = Tools::getValue('controller') != 'ajax';
$params['ranges'][$identifier] = $range;
}
if (!empty($params['in_stock'])) {
$params['oos_behaviour'] = 3;
}
$params['customer_groups'] = explode(',', $params['customer_groups']);
$params['special_ids'] = array();
foreach (array_keys($this->getSpecialFilters()) as $s) {
if ($s != 'in_stock' && !empty($params['available_options'][$s])) {
$params['special_ids'][$s] = $this->getSpecialControllerIds($s);
}
}
// adjust price-related params basing on indexations settings
if (!$this->settings['indexation']['p']) {
foreach (array('available_options', 'ranges', 'non_slider_ranges', 'sliders') as $param_key) {
unset($params[$param_key]['p']);
}
} elseif (isset($params['available_options']['p']) || isset($params['sliders']['p'])
|| $params['order']['by'] == 'p') {
$suffixes = array('g' => $params['id_customer_group'], 'c' => $params['id_currency']);
foreach (array_keys($suffixes) as $key) {
if (!$this->settings['indexation']['p_'.$key]) {
$suffixes[$key] = Configuration::get($this->i['default'][$key]);
if ($key == 'c' && $this->context->currency->conversion_rate != 1) {
$suffixes[$key] .= ' * '.$this->context->currency->conversion_rate;
}
}
}
$params['p_identifier'] = 'p_'.$suffixes['g'].'_'.$suffixes['c'];
}
}
public function getFilteredData(&$params)
{
// &$params is passed by reference because 'ranges' may be updated: min/max and available_range_options
$filtered_ids = $move_to_the_end = $all_matches = $sorted_combinations = $sorted_qties = array();
$count_data = $this->prepareCountData($params);
$stock_params = array(
'id_shop' => $params['id_shop'],
'id_shop_group' => $params['id_shop_group'],
'check_stock' => 1,
'oos_behaviour' => $params['oos_behaviour'],
);
if ($params['check_stock'] || $params['check_existence']) {
$stock_params['check_stock'] = $params['check_stock'];
$this->prepareSortedCombinationsData($stock_params, $sorted_combinations, $sorted_qties);
}
$grouped_parameters = array('c', 'a', 'f');
$ungrouped_parameters = array('m', 's', 't', 'q');
$merged_parameters = array_merge($grouped_parameters, $ungrouped_parameters);
foreach ($this->indexationData('get', $params) as $p) {
$id = $p['id'];
foreach (array('c', 'a', 'f', 'm', 's', 't', 'q', 'g') as $key) {
$p[$key] = !empty($p[$key]) ? explode(',', $p[$key]) : array();
}
if (!empty($p['g']) && !array_diff($params['customer_groups'], $p['g'])) {
continue;
}
$continue = false;
foreach ($grouped_parameters as $key) {
// todo:: unset all_matches[c-...] if product was excluded because of OOS and no filters are selected
if (isset($params['count_all_matches'])) {
foreach ($p[$key] as $param_id) {
$all_matches[$key.'-'.$param_id] = 1;
}
}
if (!$continue && isset($params[$key])) {
foreach ($params[$key] as $options_in_group) {
if (!array_intersect($p[$key], $options_in_group)) {
$continue = true; // don't exit the loop here in order to collect data for $all_matches
}
}
}
}
// define min/max values for ranges
foreach ($params['ranges'] as $identifier => $range) {
if ($range['calculate_min_max']) {
if (!isset($range['max']) || $p[$identifier] > $range['max']) {
$params['ranges'][$identifier]['max'] = $p[$identifier];
}
if (!isset($range['min']) || $p[$identifier] < $range['min']) {
$params['ranges'][$identifier]['min'] = $p[$identifier];
}
}
}
if ($continue) {
continue;
}
// special filters: new product/bestsales etc
foreach ($params['special_ids'] as $k => $ids) {
if (!empty($params[$k]) && !isset($ids[$id])) {
continue 2;
}
}
foreach ($ungrouped_parameters as $key) {
if (!empty($params[$key]) && !array_intersect($p[$key], $params[$key])) {
continue 2;
}
}
$included_in_count = array();
// ----- exclude non-existant/out-of-stock combinations
if ($params['check_stock'] || $params['check_existence']) {
$product_combinations = isset($sorted_combinations[$id]) ? $sorted_combinations[$id] : array();
$p['qty'] = isset($sorted_qties[$id][0]) ? $sorted_qties[$id][0] : 0;
$product_included = !$product_combinations && !$params['selected_atts'] &&
isset($sorted_qties[$id][0]);
if ($params['check_stock'] && $product_combinations) {
$p['qty'] = 0;
}
$p['a'] = array();
foreach ($product_combinations as $id_comb => $atts) {
foreach ($atts as $id_group => $id_att) {
foreach ($params['selected_atts'] as $id_group_selected => $att_ids_selected) {
if ($id_group_selected != $id_group && !array_intersect($att_ids_selected, $atts)) {
continue 2;
}
}
$p['a'][$id_att] = $id_att;
$count_key = 'a-'.$id_att;
if (isset($params['or-a'][$id_group]) && isset($count_data[$count_key]) &&
!isset($included_in_count[$count_key])) {
$count_data[$count_key]++;
$included_in_count[$count_key] = 1;
}
if (!$params['selected_atts'] || isset($params['selected_atts'][$id_group][$id_att])) {
// other atts are already matching
$product_included = true;
if ($params['check_stock'] && isset($sorted_qties[$id][$id_comb]) &&
$sorted_qties[$id][$id_comb] > 0) {
$p['qty'] += $sorted_qties[$id][$id_comb];
}
foreach ($atts as $id_att) {
$p['a'][$id_att] = $id_att;
}
if (isset($this->selected_combinations) && !isset($this->selected_combinations[$id])) {
$this->selected_combinations[$id] = $id_comb;
}
// $p['id_product_attribute'] = $id_comb;
// $p['qty_current_combination'] = $sorted_qties[$id][$id_comb];
continue 2;
}
}
}
if ($p['qty'] < 1 && $params['oos_behaviour'] == 1) {
$move_to_the_end[$id] = $id;
}
if (!$product_included) {
continue;
}
}
if (!$params['check_stock'] && ($params['oos_behaviour'] ||
!empty($params['available_options']['in_stock']))) {
$p['qty'] = $this->getProductQty($id, $stock_params);
if ($p['qty'] < 1) {
if ($params['oos_behaviour'] == 1) {
$move_to_the_end[$id] = $id;
} elseif ($params['oos_behaviour'] > 1 && empty($this->allow_oos[$id])) {
continue;
}
}
}
foreach ($params['ranges'] as $identifier => $range) {
if ($range['selected_values']) {
$within_range = false;
foreach ($range['selected_values'] as $from_to) {
if ($p[$identifier] >= $from_to[0] && $p[$identifier] <= $from_to[1]) {
$within_range = true;
break;
}
}
if (isset($params['or-'.$identifier]) && isset($count_data[$identifier])) {
$k = $p[$identifier].'';
if (!isset($count_data[$identifier][$k])) {
$count_data[$identifier][$k] = 0;
}
$count_data[$identifier][$k]++;
$included_in_count[$identifier][$k] = 1;
}
if (!$within_range) {
continue 2;
}
}
}
// additional matches for triggered criteria
foreach ($grouped_parameters as $key) {
if (isset($params['or-'.$key])) {
foreach ($p[$key] as $param_id) {
if (isset($params['to_count_additionally'][$param_id])) {
$k = $key.'-'.$param_id;
if (isset($count_data[$k]) && !isset($included_in_count[$k])) {
$count_data[$k]++;
$included_in_count[$k] = 1;
}
}
}
foreach ($params['or-'.$key] as $options_in_group) {
if (!array_intersect($p[$key], $options_in_group)) {
continue 3;
}
}
break; // only one 'or-...' is possible in current version
}
}
foreach ($ungrouped_parameters as $key) {
if (isset($params['or-'.$key])) {
foreach ($p[$key] as $param_id) {
if (isset($params['to_count_additionally'][$param_id])) {
$k = $key.'-'.$param_id;
if (isset($count_data[$k]) && !isset($included_in_count[$k])) {
$count_data[$k]++;
$included_in_count[$k] = 1;
}
}
}
if (!array_intersect($p[$key], $params['or-'.$key])) {
continue 2;
}
break;
}
}
foreach ($merged_parameters as $key) {
foreach ($p[$key] as $param_id) {
$k = $key.'-'.$param_id;
if (isset($count_data[$k]) && !isset($included_in_count[$k])) {
$count_data[$k]++;
}
}
}
foreach ($params['special_ids'] as $k => $ids) {
if (isset($count_data[$k]) && isset($ids[$id])) {
$count_data[$k]++;
}
}
if (isset($count_data['in_stock']) && $p['qty'] > 0) {
$count_data['in_stock']++;
}
foreach ($params['non_slider_ranges'] as $identifier) {
$k = $p[$identifier].'';
if (isset($count_data[$identifier]) && !isset($included_in_count[$identifier][$k])) {
if (!isset($count_data[$identifier][$k])) {
$count_data[$identifier][$k] = 0;
}
$count_data[$identifier][$k]++;
}
}
$filtered_ids[$id] = $id;
switch ($params['order']['by']) {
case 'n':
case 'd':
case 'r':
case 'p':
$filtered_ids[$id] = $p[$params['order']['by']];
break;
case 'quantity':
$filtered_ids[$id] = isset($p['qty']) ? $p['qty'] : $this->getProductQty($id, $stock_params);
break;
case 'sales':
$filtered_ids[$id] = isset($p['sales']) ? $p['sales'] : $this->getProductSales($id);
break;
case 'position':
$filtered_ids[$id] = $this->getProductPosition($id, $params);
break;
case 'manufacturer_name':
$filtered_ids[$id] = $this->getManufacturerName((int)current($p['m']));
break;
}
}
// prepare data for ranged filters (price/weight)
foreach ($params['ranges'] as $identifier => &$r) {
if (isset($r['step'])) {
if ($range_options = $params['available_options'][$identifier]) {
$range_options = explode(',', $range_options);
} else {
// available_options may be empty on first page load, because min/max were not known yet
// so we prepare range options here, basing on current min/max values
$range_options = $r['available_range_options'] = $this->getRangeOptions($r);
}
$exploded_range_options = array();
foreach ($range_options as $range_option) {
$exploded_range_options[$identifier.'-'.$range_option] = explode('-', $range_option);
}
if (!empty($count_data[$identifier])) {
foreach ($count_data[$identifier] as $value => $num) {
foreach ($exploded_range_options as $key => $opt) {
if ($value <= $opt[1] && $value >= $opt[0]) {
if (!isset($count_data[$key])) {
$count_data[$key] = 0;
}
$count_data[$key] += $num;
break;
}
}
}
unset($count_data[$identifier]);
}
}
}
unset($r);
$this->sortFilteredIDs($filtered_ids, $move_to_the_end, $params);
return array(
'ids' => $filtered_ids,
'count' => $count_data,
'all_matches' => $all_matches,
);
}
public function prepareCountData($params)
{
$count_data = array();
if ($params['count_data'] || $params['hide_zero_matches'] || $params['dim_zero_matches']) {
foreach ($params['non_slider_ranges'] as $k) {
$count_data[$k] = array(); // processed in a special way
}
foreach (array_keys($this->getSpecialFilters()) as $k) {
if (isset($params['available_options'][$k])) {
$count_data[$k] = 0;
}
}
foreach ($params['available_options'] as $k => $options) {
if ($options && !isset($count_data[$k])) {
$k = Tools::substr($k, 0, 1);
if (!is_array($options)) {
$options = explode(',', $options);
}
foreach ($options as $id_opt) {
$count_data[$k.'-'.$id_opt] = 0;
}
}
}
}
return $count_data;
}
public function prepareDataForDisplay($filtered_data, $params)
{
$page_keepers = array('af_page', 'p_type');
if (!empty($params['page']) && in_array($params['trigger'], $page_keepers)) {
$page = (int)$params['page'];
} else {
$page = 1;
}
$products_per_page = $params['nb_items'];
$offset = ($page - 1) * $products_per_page;
$ids = array_slice($filtered_data['ids'], $offset, $products_per_page);
if (isset($this->selected_combinations)) {
if (!$params['check_stock'] && !$params['check_existence'] && empty($this->selected_combinations)) {
$this->selected_combinations = $this->getSelectedCombinations($ids, $params['selected_atts']);
} else {
$sc = array();
foreach ($ids as $id) {
if (!empty($this->selected_combinations[$id])) {
$sc[$id] = $this->selected_combinations[$id];
}
}
$this->selected_combinations = $sc;
}
}
$total = count($filtered_data['ids']);
$ret = array(
'filtered_ids_count' => $total,
'page' => $page,
'products_per_page' => $products_per_page,
'products' => $this->getProductsInfos($ids, $params['id_lang'], $params['id_shop']),
'count_data' => $filtered_data['count'],
'all_matches' => $filtered_data['all_matches'],
'ranges' => $params['ranges'], // TODO: optimize ???
'trigger' => $params['trigger'],
'product_count_text' => '',
'hide_load_more_btn' => false,
);
if ($params['p_type'] > 1) { // load more/infinite scroll
$page_from = isset($params['page_from']) ? $params['page_from'] : $page;
$page_to = isset($params['page_to']) ? $params['page_to'] : $page;
$from = $page_from * $products_per_page - $products_per_page + 1;
$to = $page_to * $products_per_page;
if ($total <= $to) {
$to = $total;
$ret['hide_load_more_btn'] = true;
}
if ($total) {
$txt = $this->l('Showing %1$d - %2$d of %3$d items');
$ret['product_count_text'] = sprintf($txt, $from, $to, $total);
}
}
return $ret;
}
public function sortFilteredIDs(&$filtered_ids, &$move_to_the_end, $params)
{
if ($params['order']['by'] == 'random') {
srand($params['random_seed']);
shuffle($filtered_ids); // 0 => $id_0, 1 => $id_1, 2 => $id_2 etc...
} else {
if ($params['order']['by'] == 'p' && !empty($params['combination_results'])) {
$this->adjustCombinationPrices($params['id_shop'], $filtered_ids, $params['selected_atts']);
}
$params['order']['way'] == 'asc' ? asort($filtered_ids) : arsort($filtered_ids);
$filtered_ids = array_keys($filtered_ids);
}
// instockfirst
if ($move_to_the_end && $params['order']['by'] != 'quantity') {
$oos_ids = array();
foreach ($filtered_ids as $pos => $id) {
if (!empty($move_to_the_end[$id])) {
$oos_ids[] = $id;
unset($filtered_ids[$pos]);
}
}
if (is_array($filtered_ids)) {
$filtered_ids = array_merge($filtered_ids, $oos_ids);
} else {
$filtered_ids = $oos_ids;
}
unset($move_to_the_end);
}
}
public function prepareSortedCombinationsData($params, &$sorted_combinations, &$sorted_qties)
{
$cache_id = $this->cacheID('comb_data', $params);
if (!$cache_id || !$cached_data = $this->cache('get', $cache_id)) {
$raw_data = $this->db->executeS('
SELECT
sa.id_product_attribute as id_comb,
sa.quantity as qty,
pac.id_attribute as id_att,
sa.id_product,
a.id_attribute_group as id_group
FROM '._DB_PREFIX_.'stock_available sa
INNER JOIN '._DB_PREFIX_.'product_shop ps
ON ps.id_product = sa.id_product AND ps.active = 1
AND ps.id_shop = '.(int)$params['id_shop'].'
LEFT JOIN '._DB_PREFIX_.'product_attribute_shop pas
ON pas.id_product_attribute = sa.id_product_attribute
AND pas.id_product = sa.id_product AND pas.id_shop = ps.id_shop
LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac
ON pac.id_product_attribute = pas.id_product_attribute
LEFT JOIN '._DB_PREFIX_.'attribute a
ON a.id_attribute = pac.id_attribute
WHERE '.pSQL($this->stockQuery($params)).'
ORDER BY sa.quantity > 0 DESC, pas.default_on DESC, pas.price ASC, pac.id_attribute ASC
');
foreach ($raw_data as $d) {
$sorted_qties[$d['id_product']][$d['id_comb']] = $d['qty'];
if ($d['id_comb'] && $d['id_group']) {
$sorted_combinations[$d['id_product']][$d['id_comb']][$d['id_group']] = $d['id_att'];
}
}
if (!empty($this->use_merged_attributes)) {
$this->mergedValues()->mapAttributesInSortedCombinations($raw_data, $sorted_combinations);
}
if ($cache_id) {
$data = array('combinations' => $sorted_combinations, 'qties' => $sorted_qties);
$this->cache('save', $cache_id, $data);
}
} else {
$sorted_combinations = $cached_data['combinations'];
$sorted_qties = $cached_data['qties'];
}
}
public function stockQuery($params)
{
$query = $this->addStockShopAssociaton($params['id_shop'], $params['id_shop_group']);
if ($params['check_stock'] && $params['oos_behaviour'] > 1) {
$allow_ordering_oos = '-1';
if ($params['oos_behaviour'] == 2) {
$allow_ordering_oos = '1'.(Configuration::get('PS_ORDER_OUT_OF_STOCK') ? ',2' : '');
}
$query .= ' AND (sa.quantity > 0 OR sa.out_of_stock IN ('.$allow_ordering_oos.'))';
}
return $query;
}
public function addStockShopAssociaton($id_shop, $id_shop_group)
{
$shared_stock = $this->db->getValue('
SELECT share_stock FROM '._DB_PREFIX_.'shop_group
WHERE id_shop_group = '.(int)$id_shop_group.'
');
return $shared_stock ? 'sa.id_shop_group = '.(int)$id_shop_group : 'sa.id_shop = '.(int)$id_shop;
}
public function getRangeOptions($range_data)
{
$range_options = array();
$min = isset($range_data['min']) ? floor($range_data['min']) : 0;
$max = isset($range_data['max']) ? ceil($range_data['max']) : 0;
$step = $range_data['step'];
if (Tools::strpos($step, ',') !== false) {
$step = str_replace(array('min', 'max'), array($min, $max), $step);
$custom_options = explode(',', $step);
foreach ($custom_options as $option) {
$range_options[] = implode('-', ExtendedTools::explodeRangeValue($option));
}
} else {
$step = (int)$step ?: 100;
for ($i = 0; $i < $max; $i += $step) {
$to = $i + $step;
if ($to < $min) {
continue;
}
if ($to > $max) {
$to = $max;
}
$from = count($range_options) ? $i : $min;
$range_options[$i] = $from.'-'.$to;
}
}
return $range_options;
}
public function getProductQty($id, $stock_params)
{
if (!isset($this->qty_data)) {
$this->qty_data = $this->allow_oos = array();
$raw_data = $this->db->executeS('
SELECT sa.id_product, sa.quantity as qty
FROM '._DB_PREFIX_.'stock_available sa
INNER JOIN '._DB_PREFIX_.'product_shop ps
ON ps.id_product = sa.id_product AND ps.active = 1
AND ps.id_shop = '.(int)$stock_params['id_shop'].'
WHERE sa.id_product_attribute = 0
AND '.$this->stockQuery($stock_params).'
');
foreach ($raw_data as $d) {
$this->qty_data[$d['id_product']] = $d['qty'];
if ($d['qty'] < 1 && $stock_params['oos_behaviour'] == 2) {
$this->allow_oos[$d['id_product']] = 1;
}
}
}
$qty = isset($this->qty_data[$id]) ? $this->qty_data[$id] : 0;
return $qty;
}
public function getProductSales($id)
{
if (!isset($this->sales_data)) {
$this->sales_data = array();
$raw_data = $this->db->executeS('
SELECT ps.id_product, ps.quantity FROM '._DB_PREFIX_.'product_sale ps
'.Shop::addSqlAssociation('product', 'ps').'
WHERE product_shop.active = 1 AND product_shop.visibility <> "none"
');
foreach ($raw_data as $d) {
$this->sales_data[$d['id_product']] = $d['quantity'];
}
}
return isset($this->sales_data[$id]) ? $this->sales_data[$id] : 0;
}
public function getProductPosition($id_product, $params)
{
if (!isset($this->all_positions)) {
$this->all_positions = array();
if (!empty($params['controller_product_ids'])) {
$position = 1;
foreach ($params['controller_product_ids'] as $id) {
$this->all_positions[$id] = $position++;
}
} else {
$position_id_cat = $params['id_parent_cat'];
// if only 1 category is checked, sort by positions in that category
// TODO: add compatibility with top level category blocks (e.g. c31)
foreach (array('or-c', 'c') as $k) {
if (!empty($params[$k])) {
foreach ($params[$k] as $categories) {
if (count($categories) == 1) {
$position_id_cat = current($categories);
break 2;
}
}
}
}
$raw_data = $this->db->executeS('
SELECT id_product AS id, position FROM '._DB_PREFIX_.'category_product
WHERE id_category = '.(int)$position_id_cat.'
');
foreach ($raw_data as $d) {
$this->all_positions[$d['id']] = $d['position'];
}
}
}
return isset($this->all_positions[$id_product]) ? $this->all_positions[$id_product] : 'n';
}
public function getManufacturerName($id_manufacturer)
{
if (!isset($this->m_names)) {
$this->m_names = array();
$raw_data = $this->db->executeS('
SELECT id_manufacturer AS id, name FROM '._DB_PREFIX_.'manufacturer WHERE active = 1
');
foreach ($raw_data as $d) {
$this->m_names[$d['id']] = $d['name'];
}
}
return isset($this->m_names[$id_manufacturer]) ? $this->m_names[$id_manufacturer] : '';
}
/* temporary workaround for calculating/predicting combination prices for proper sorting */
public function adjustCombinationPrices($id_shop, &$filtered_ids, $selected_atts)
{
if ($selected_atts) {
if (empty($this->selected_combinations)) {
$ids = array_keys($filtered_ids);
$this->selected_combinations = $this->getSelectedCombinations($ids, $selected_atts);
}
if ($imploded_combination_ids = $this->formatIDs($this->selected_combinations, true)) {
$raw_data = $this->db->executeS('
SELECT pa.id_product AS id, pa.id_product_attribute AS ipa, pa.price,
pa.default_on AS df, ps.price AS base_price
FROM '._DB_PREFIX_.'product_attribute pa
INNER JOIN '._DB_PREFIX_.'product_shop ps
ON ps.id_product = pa.id_product AND ps.id_shop = '.(int)$id_shop.'
WHERE pa.id_product_attribute IN ('.pSQL($imploded_combination_ids).') OR pa.default_on = 1
');
$non_default_impacts = $rates = array();
foreach ($raw_data as $d) {
$id = $d['id'];
$raw_price = $d['base_price'] + $d['price'];
if ($d['df']) {
$indexed_price = isset($filtered_ids[$id]) ? $filtered_ids[$id] : 0;
$rates[$id] = $raw_price ? $indexed_price/$raw_price : 1;
} else {
$non_default_impacts[$id] = $raw_price;
}
}
foreach ($non_default_impacts as $id_product => $raw_price) {
if (isset($rates[$id_product]) && isset($filtered_ids[$id_product])) {
$filtered_ids[$id_product] = $raw_price * $rates[$id_product];
}
}
}
}
}
public function getSelectedCombinations($product_ids, $selected_atts)
{
$selected_combinations = $att_ids = $sorted_combinations = array();
if (!$product_ids || !$selected_atts) {
return $selected_combinations;
}
if (!empty($this->use_merged_attributes)) {
$this->mergedValues()->replaceMergedAttsWithOriginalValues($selected_atts);
}
$selected_groups_count = count($selected_atts);
foreach ($selected_atts as $atts) {
$att_ids += $atts;
}
$imploded_att_ids = implode(', ', $att_ids);
$imploded_product_ids = implode(', ', $product_ids);
$raw_data = $this->db->executeS('
SELECT pac.id_attribute, pac.id_product_attribute as id_comb, pa.id_product
FROM '._DB_PREFIX_.'product_attribute_combination pac
LEFT JOIN '._DB_PREFIX_.'product_attribute pa
ON pa.id_product_attribute = pac.id_product_attribute
WHERE pa.id_product IN ('.pSQL($imploded_product_ids).')
AND pac.id_attribute IN ('.pSQL($imploded_att_ids).')
ORDER BY pa.default_on DESC, pa.id_product_attribute ASC
');
foreach ($raw_data as $d) {
$sorted_combinations[$d['id_product']][$d['id_comb']][$d['id_attribute']] = $d['id_attribute'];
}
foreach ($sorted_combinations as $id_product => $combinations) {
foreach ($combinations as $id_comb => $atts) {
if (!isset($selected_combinations[$id_product]) && count($atts) == $selected_groups_count) {
$selected_combinations[$id_product] = $id_comb;
}
}
}
return $selected_combinations;
}
public function getProductsInfos($ids, $id_lang, $id_shop, $get_all_properties = true)
{
if (!$ids) {
return array();
}
$products_infos = array();
$products_data = $this->db->executeS('
SELECT p.*, product_shop.*, pl.*, image.id_image, il.legend, m.name AS manufacturer_name,
'.$this->isNewQuery().' AS new
FROM '._DB_PREFIX_.'product p
'.Shop::addSqlAssociation('product', 'p').'
INNER JOIN '._DB_PREFIX_.'product_lang pl
ON (pl.id_product = p.id_product'.Shop::addSqlRestrictionOnLang('pl').'
AND pl.id_lang = '.(int)$id_lang.')
LEFT JOIN '._DB_PREFIX_.'image image
ON (image.id_product = p.id_product AND image.cover = 1)
LEFT JOIN '._DB_PREFIX_.'image_lang il
ON (il.id_image = image.id_image AND il.id_lang = '.(int)$id_lang.')
LEFT JOIN '._DB_PREFIX_.'manufacturer m
ON m.id_manufacturer = p.id_manufacturer
WHERE p.id_product IN ('.pSQL($this->formatIDs($ids, true)).')
');
$positions = array_flip($ids);
if ($this->is_17 && $get_all_properties) {
$factory = new ProductPresenterFactory($this->context, new TaxConfiguration());
$factory_presenter = $factory->getPresenter();
$factory_settings = $factory->getPresentationSettings();
$lang_obj = new Language($id_lang);
}
if (!empty($this->selected_combinations)) {
$combination_images = $this->getCombinationImages($this->selected_combinations, $id_lang);
}
foreach ($products_data as $pd) {
$id_product = (int)$pd['id_product'];
// oos data is kept updated in stock_available table
// joining this table in query significantly increases time if there are many $ids
$pd['out_of_stock'] = StockAvailable::outOfStock($id_product, $id_shop);
// cache_default_attribute is kept up to date in indexProduct()
$pd['id_product_attribute'] = $pd['cache_default_attribute'];
if (!empty($this->selected_combinations[$id_product])) {
$pd['id_product_attribute'] = (int)$this->selected_combinations[$id_product];
if (!empty($combination_images[$id_product])) {
$pd['id_image'] = $combination_images[$id_product]['id_image'];
$pd['legend'] = $combination_images[$id_product]['legend'];
}
}
if ($get_all_properties) {
$pd = Product::getProductProperties($id_lang, $pd);
if ($this->is_17 && Tools::getValue('controller') == 'ajax') {
$pd = $factory_presenter->present($factory_settings, $pd, $lang_obj);
}
if (!$this->is_17 && $pd['id_product_attribute'] != $pd['cache_default_attribute']) {
$pd['link'] .= $this->addAnchor($id_product, (int)$pd['id_product_attribute'], true);
}
}
$products_infos[$positions[$id_product]] = $pd;
}
ksort($products_infos);
return $products_infos;
}
public function getCombinationImages($combination_ids, $id_lang)
{
$combination_images_data = $this->db->executeS('
SELECT i.id_product, i.id_image, il.legend
FROM '._DB_PREFIX_.'image i
INNER JOIN '._DB_PREFIX_.'product_attribute_image pai
ON pai.id_image = i.id_image
LEFT JOIN '._DB_PREFIX_.'image_lang il
ON (il.id_image = i.id_image AND il.id_lang = '.(int)$id_lang.')
WHERE pai.id_product_attribute IN ('.pSQL($this->formatIDs($combination_ids, true)).')
ORDER BY i.cover DESC, i.position ASC
');
$combination_images = array();
foreach ($combination_images_data as $row) {
if (!isset($combination_images[$row['id_product']])) {
$combination_images[$row['id_product']] = $row;
}
}
return $combination_images;
}
/*
* Based on $product->getAnchor()
*/
public function addAnchor($id_product, $id_product_attribute, $with_id = false)
{
$attributes = Product::getAttributesParams($id_product, $id_product_attribute);
$anchor = '#';
$sep = Configuration::get('PS_ATTRIBUTE_ANCHOR_SEPARATOR');
foreach ($attributes as &$a) {
foreach ($a as &$b) {
$b = str_replace($sep, '_', Tools::link_rewrite($b));
}
$id = ($with_id && !empty($a['id_attribute']) ? (int)$a['id_attribute'].$sep : '');
$anchor .= '/'.$id.$a['group'].$sep.$a['name'];
}
return $anchor;
}
public function processCustomerFiltersIfRequired(&$filters)
{
$smarty_vars = array('applied_customer_filters' => array());
if ($this->context->customer->id && $this->getAdjustableCustomerFilters(false)) {
$smarty_vars['my_filters_link'] = $this->context->link->getModuleLink($this->name, 'myfilters');
}
if ($customer_filters = $this->getCustomerFilters($this->context->customer->id)) {
foreach ($customer_filters as $key => $cf) {
if (isset($filters[$key])) {
if ($filters[$key]['is_slider']) {
continue; // no customer filters in sliders
}
if (count($cf) > 1) {
$filters[$key]['type'] = 1; // force checkbox if more than one filters in group are selected
}
foreach ($cf as $id) {
$filters[$key]['forced_values'][$id] = $id;
$smarty_vars['applied_customer_filters'][$key][$id] = $id;
// make sure customer filter options are available even if there are no matching products
$this->products_data['all_matches'][$filters[$key]['first_char'].'-'.$id] = 1;
}
if ($filters[$key]['type'] == '3') { // names should be defined for customer selected options
$sorted_names = array_column($filters[$key]['values'], 'name', 'id');
foreach ($smarty_vars['applied_customer_filters'][$key] as $id) {
$smarty_vars['applied_customer_filters'][$key][$id] = isset($sorted_names[$id]) ?
$sorted_names[$id] : $id;
}
}
}
}
}
$this->context->smarty->assign($smarty_vars);
}
public function getCustomerFilters($id_customer)
{
if (!$this->getAdjustableCustomerFilters(false)) {
return false;
}
$customer_filters = $this->db->getValue('
SELECT filters FROM '._DB_PREFIX_.'af_customer_filters
WHERE id_customer = '.(int)$id_customer.'
');
if ($customer_filters) {
$customer_filters = Tools::jsonDecode($customer_filters, true);
}
return $customer_filters;
}
public function ajaxSaveCustomerFilters()
{
if (!$this->context->customer->id) {
exit();
}
$submitted_filters = Tools::getValue('filters');
$available_filters = $this->getAvailableFilters();
$data_to_save = array();
foreach (array_keys($available_filters) as $f) {
if (!empty($submitted_filters[$f])) {
foreach ($submitted_filters[$f] as $id) {
$data_to_save[$f][$id] = $id;
}
}
}
$data_to_save = Tools::jsonEncode($data_to_save);
$query = '
REPLACE INTO '._DB_PREFIX_.'af_customer_filters
VALUES ('.(int)$this->context->customer->id.', \''.pSQL($data_to_save).'\')
';
$ret = array('success' => $this->db->execute($query));
exit(Tools::jsonEncode($ret));
}
public function getAdjustableCustomerFilters($decode = true)
{
$adjustable_fitlers = Configuration::get('AF_SAVED_CUSTOMER_FILTERS');
if ($decode) {
$adjustable_fitlers = $adjustable_fitlers ? Tools::jsonDecode($adjustable_fitlers, true) : array();
}
return $adjustable_fitlers;
}
public function hookDisplayCustomerAccount()
{
if ($this->getAdjustableCustomerFilters(false)) {
$this->defineSettings();
$this->context->smarty->assign(array(
'href' => $this->context->link->getModuleLink($this->name, 'myfilters'),
'layout_classes' => $this->getLayoutClasses(),
'is_17' => $this->is_17,
));
return $this->display(__FILE__, 'views/templates/hook/my-filters-tab.tpl');
}
}
public function cacheID($type, $params)
{
if (!empty($this->settings['caching'][$type])) {
return $type.'_'.implode('_', array_map('intval', $params));
}
}
public function cache($action, $cache_id, $data = '', $cache_time = 3600)
{
$ret = true;
$full_path = $this->local_path.'cache/'.$cache_id;
switch ($action) {
case 'get':
if ($ret = file_exists($full_path) && (time() - filemtime($full_path) < $cache_time)) {
$ret = Tools::jsonDecode(Tools::file_get_contents($full_path), true);
}
break;
case 'save':
$ret = file_put_contents($full_path, Tools::jsonEncode($data)) !== false;
break;
case 'clear':
// cached file names can include different parameters, so we unlink all files matching main path
foreach (glob($full_path.'*') as $path) {
$ret &= unlink($path);
}
break;
case 'info':
if ($files = $info = glob($full_path.'*')) {
$info = sprintf(
$this->l('Cache size: %1$s | last updated: %2$s'),
Tools::formatBytes(array_sum(array_map('filesize', $files))).'b',
date('Y-m-d H:i:s', (max(array_map('filemtime', $files))))
);
}
$ret = $info ?: $this->l('No data');
break;
}
return $ret;
}
public function getCronToken()
{
return Tools::encrypt($this->name);
}
public function getCronURL($id_shop, $params = array())
{
$required_params = array(
'token' => $this->getCronToken(),
'id_shop' => $id_shop,
);
foreach ($params as $name => $value) {
$required_params[$name] = $value;
}
return $this->context->link->getModuleLink($this->name, 'cron', $required_params, null, null, $id_shop);
}
public function throwError($errors, $render_html = true)
{
if (!is_array($errors)) {
$errors = array($errors);
}
if ($render_html) {
$this->context->smarty->assign(array('errors' => $errors));
$html = $this->display(__FILE__, 'views/templates/admin/errors.tpl');
if (!Tools::isSubmit('ajax')) {
return $html;
} else {
$errors = utf8_encode($html);
}
}
die(Tools::jsonEncode(array('errors' => $errors)));
}
/*
* new methods, since 1.7
*/
public function getPaginationVariables($page, $products_num, $products_per_page, $current_url)
{
require_once('src/AmazzingFilterProductSearchProvider.php');
$provider = new AmazzingFilterProductSearchProvider($this);
return $provider->getPaginationVariables($page, $products_num, $products_per_page, $current_url);
}
public function updateQueryString($url, $new_params = array())
{
$url = explode('?', $url);
$updated_params = !empty($url[1]) ? $this->parseStr($url[1]) : array();
foreach ($new_params as $name => $value) {
$updated_params[$name] = $value;
if (($name == $this->page_link_rewrite_text && $value == 1) || $value === null) {
unset($updated_params[$name]);
}
}
$replacements = array('%2F' => '/', '%2C' => ',');
$updated_params = str_replace(array_keys($replacements), $replacements, http_build_query($updated_params));
$updated_url = $url[0].(!empty($updated_params) ? '?'.$updated_params : '');
return $updated_url;
}
public function getSortingOptions($current_option, $default_option = '', $current_url = '')
{
$options = $this->getAvailableSortingOptions($default_option);
$url_without_order = $this->updateQueryString($current_url, array('order' => null));
$url_without_order .= !strstr($current_url, '?') ? '?' : '&';
$processed_options = array();
foreach ($options as $k => $opt_name) {
$k_exploded = explode('.', $k);
$processed_options[$k] = array(
'entity' => $k_exploded[0],
'field' => $k_exploded[1],
'direction' => $k_exploded[2],
'label' => $opt_name,
'urlParameter' => $k,
'url' => $url_without_order.'order='.$k,
'current' => $k == $current_option,
);
}
return $processed_options;
// this is simplified version of ProductListingFrontController::getTemplateVarSortOrders()
// standard options can be obtained like that:
// use PrestaShop\PrestaShop\Core\Product\Search\SortOrderFactory; at the top
// $options = (new SortOrderFactory($this->getTranslator()))->getDefaultSortOrders();
}
public function getAvailableSortingOptions($default_option = '')
{
$options = array( // standard options
'product.position.asc' => $this->l('Relevance'),
'product.date_add.desc' => $this->l('New products first'),
'product.name.asc' => $this->l('Name, A to Z'),
'product.name.desc' => $this->l('Name, Z to A'),
'product.price.asc' => $this->l('Price, low to high'),
'product.price.desc' => $this->l('Price, high to low'),
'product.quantity.desc' => $this->l('In stock'),
'product.random.desc' => $this->l('Random'),
);
$extra_options = array( // options, that can be defined additionally in module settings
'product.position.desc' => $this->l('Relevance, reverse'),
'product.date_add.asc' => $this->l('Old products first'),
'product.quantity.asc' => $this->l('Stock, reverse'),
'product.sales.desc' => $this->l('Best sales'),
'product.sales.asc' => $this->l('Lowest sales'),
'product.reference.asc' => $this->l('Reference, A to Z'),
'product.reference.desc' => $this->l('Reference, reverse'),
'product.manufacturer_name.asc' => $this->l('Brand, A to Z'),
'product.manufacturer_name.desc' => $this->l('Brand, reverse'),
);
$default_option = $default_option ?: 'product.'.$this->settings['general']['default_order_by'].
'.'.$this->settings['general']['default_order_way'];
if (isset($extra_options[$default_option])) {
$options = array($default_option => $extra_options[$default_option]) + $options;
}
if ($this->current_controller == 'search') { // sorting by position is reverse in search results
$options = array('product.position.desc' => $options['product.position.asc']) + $options;
unset($options['product.position.asc']);
}
return $options;
}
public function hookProductSearchProvider($params)
{
if ($this->defineFilterParams()) {
require_once('src/AmazzingFilterProductSearchProvider.php');
return new AmazzingFilterProductSearchProvider($this);
} else {
return false;
}
}
public function bo()
{
if (!isset($this->bo_obj)) {
require_once($this->local_path.'classes/bo.php');
$this->bo_obj = new Bo();
}
return $this->bo_obj;
}
public function slider()
{
if (!isset($this->slider_obj)) {
require_once($this->local_path.'classes/Slider.php');
$this->slider_obj = new Slider($this->context);
}
return $this->slider_obj;
}
public function mergedValues()
{
if (!isset($this->merged_values)) {
require_once($this->local_path.'classes/MergedValues.php');
$this->merged_values = new MergedValues($this);
}
return $this->merged_values;
}
}