first commit
5
modules/ps_facetedsearch/.bablerc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
]
|
||||
}
|
||||
43
modules/ps_facetedsearch/_dev/back/blocklayered.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
.bootstrap .filter_list .filter_list_item {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
margin-bottom: 4px;
|
||||
background-color: white;
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 0 0 3px, rgba(0, 0, 0, 0.1) 0 -2px 0 inset;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bootstrap .filter_panel {
|
||||
min-height: 20px;
|
||||
padding: 7px 7px 0px 7px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #ebebeb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.bootstrap .filter_panel header {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.bootstrap .prestashop-switch span {
|
||||
display: none;
|
||||
}/*# sourceMappingURL=blocklayered.css.map */
|
||||
1
modules/ps_facetedsearch/_dev/back/blocklayered.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["blocklayered.scss","blocklayered.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;EAAA;AAmBE;EACC,cAAA;EACA,WAAA;EACA,cAAA;EACA,kBAAA;EACA,uBAAA;EAEA,yEAAA;EAKA,kBAAA;EACC,eAAA;ACAJ;ADGE;EACC,gBAAA;EACA,wBAAA;EACA,mBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;EAEA,+CAAA;ACDH;ADGI;EACC,kBAAA;ACDL;ADMI;EACE,aAAA;ACJN","file":"blocklayered.css"}
|
||||
56
modules/ps_facetedsearch/_dev/back/blocklayered.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
.bootstrap {
|
||||
.filter_list .filter_list_item {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
margin-bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 0 3px, rgba(0, 0, 0, 0.1) 0 -2px 0 inset;
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 0 0 3px, rgba(0, 0, 0, 0.1) 0 -2px 0 inset;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter_panel {
|
||||
min-height: 20px;
|
||||
padding: 7px 7px 0px 7px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #ebebeb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 3px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
|
||||
header {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.prestashop-switch {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
247
modules/ps_facetedsearch/_dev/back/index.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import './blocklayered.scss';
|
||||
|
||||
/* eslint-disable no-unused-vars, no-alert */
|
||||
function checkForm() {
|
||||
let isCategorySelected = false;
|
||||
let isFilterSelected = false;
|
||||
|
||||
$('#categories-treeview input[type=checkbox]').each(function checkCategoriesCheckboxes() {
|
||||
if ($(this).prop('checked')) {
|
||||
isCategorySelected = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.filter_list_item input[type=checkbox]').each(function checkFilterListCheckboxes() {
|
||||
if ($(this).prop('checked')) {
|
||||
isFilterSelected = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!isCategorySelected) {
|
||||
alert(translations.no_selected_categories);
|
||||
$('#categories-treeview input[type=checkbox]').first().focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isFilterSelected) {
|
||||
alert(translations.no_selected_filters);
|
||||
$('#filter_list_item input[type=checkbox]').first().focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
$('.ajaxcall').click(function onAjaxCall() {
|
||||
if (this.legend === undefined) {
|
||||
this.legend = $(this).html();
|
||||
}
|
||||
|
||||
if (this.running === undefined) {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
if (this.running === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$('.ajax-message').hide();
|
||||
this.running = true;
|
||||
|
||||
if (typeof (this.restartAllowed) === 'undefined' || this.restartAllowed) {
|
||||
$(this).html(this.legend + translations.in_progress);
|
||||
$('#indexing-warning').show();
|
||||
}
|
||||
|
||||
this.restartAllowed = false;
|
||||
const type = $(this).attr('rel');
|
||||
|
||||
$.ajax({
|
||||
url: `${this.href}&ajax=1`,
|
||||
context: this,
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success() {
|
||||
this.running = false;
|
||||
this.restartAllowed = true;
|
||||
$('#indexing-warning').hide();
|
||||
$(this).html(this.legend);
|
||||
|
||||
$('#ajax-message-ok span').html(
|
||||
type === 'price' ? translations.url_indexation_finished : translations.attribute_indexation_finished,
|
||||
);
|
||||
|
||||
$('#ajax-message-ok').show();
|
||||
},
|
||||
error() {
|
||||
this.restartAllowed = true;
|
||||
$('#indexing-warning').hide();
|
||||
|
||||
$('#ajax-message-ko span').html(
|
||||
type === 'price' ? translations.url_indexation_failed : translations.attribute_indexation_failed,
|
||||
);
|
||||
|
||||
$('#ajax-message-ko').show();
|
||||
$(this).html(this.legend);
|
||||
this.running = false;
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
let totalCount = 0;
|
||||
$('.ajaxcall-recurcive').each((it, elm) => {
|
||||
$(elm).click(function onAjaxRecursiveCall(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.cursor === undefined) {
|
||||
this.cursor = 0;
|
||||
}
|
||||
|
||||
if (this.legend === undefined) {
|
||||
this.legend = $(this).html();
|
||||
}
|
||||
|
||||
if (this.running === undefined) {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
if (this.running === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$('.ajax-message').hide();
|
||||
|
||||
this.running = true;
|
||||
|
||||
if (typeof (this.restartAllowed) === 'undefined' || this.restartAllowed) {
|
||||
$(this).html(this.legend + translations.in_progress);
|
||||
$('#indexing-warning').show();
|
||||
}
|
||||
|
||||
this.restartAllowed = false;
|
||||
|
||||
$.ajax({
|
||||
url: `${this.href}&ajax=1&cursor=${this.cursor}`,
|
||||
context: this,
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success(res) {
|
||||
this.running = false;
|
||||
if (res.result) {
|
||||
this.cursor = 0;
|
||||
totalCount = 0;
|
||||
$('#indexing-warning').hide();
|
||||
$(this).html(this.legend);
|
||||
$('#ajax-message-ok span').html(translations.price_indexation_finished);
|
||||
$('#ajax-message-ok').show();
|
||||
return;
|
||||
}
|
||||
|
||||
totalCount += parseInt(res.count, 10);
|
||||
this.cursor = parseInt(res.cursor, 10);
|
||||
$(this).html(
|
||||
this.legend + translations.price_indexation_in_progress.replace(
|
||||
'%s',
|
||||
`${totalCount}/${res.total}`,
|
||||
),
|
||||
);
|
||||
$(this).click();
|
||||
},
|
||||
error(res) {
|
||||
this.restartAllowed = true;
|
||||
$('#indexing-warning').hide();
|
||||
$('#ajax-message-ko span').html(translations.price_indexation_failed);
|
||||
$('#ajax-message-ko').show();
|
||||
$(this).html(this.legend);
|
||||
|
||||
this.cursor = 0;
|
||||
this.running = false;
|
||||
},
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof PS_LAYERED_INDEXED !== 'undefined' && PS_LAYERED_INDEXED) {
|
||||
$('#url-indexe').click();
|
||||
$('#full-index').click();
|
||||
}
|
||||
|
||||
$('.sortable').sortable({
|
||||
forcePlaceholderSize: true,
|
||||
});
|
||||
|
||||
$('.filter_list_item input[type=checkbox]').click(function onFilterLickItemCheckboxesClicked() {
|
||||
const currentSelectedFiltersCount = parseInt($('#selected_filters').html(), 10);
|
||||
|
||||
$('#selected_filters').html(
|
||||
$(this).prop('checked') ? currentSelectedFiltersCount + 1 : currentSelectedFiltersCount - 1,
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof window.filters !== 'undefined') {
|
||||
const filters = JSON.parse(window.filters);
|
||||
let container = null;
|
||||
let $el;
|
||||
Object.keys(filters).forEach((filter) => {
|
||||
$el = $(`#${filter}`);
|
||||
$el.prop('checked', true);
|
||||
$('#selected_filters').html(parseInt($('#selected_filters').html(), 10) + 1);
|
||||
$(`select[name="${filter}_filter_type"]`).val(filters[filter].filter_type);
|
||||
$(`select[name="${filter}_filter_show_limit"]`).val(filters[filter].filter_show_limit);
|
||||
if (container === null) {
|
||||
container = $(`#${filter}`).closest('ul');
|
||||
$el.closest('li').detach().prependTo(container);
|
||||
} else {
|
||||
$el.closest('li').detach().insertAfter(container);
|
||||
}
|
||||
|
||||
container = $el.closest('li');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('ready', () => {
|
||||
const layeredDefaultCategory = $('input[name="ps_layered_filter_by_default_category"]');
|
||||
layeredDefaultCategory.on('change', function initializeOptions(event) {
|
||||
const elm = $(this);
|
||||
|
||||
if (!elm.prop('checked')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elm.val() === '1') {
|
||||
$('input[name="ps_layered_full_tree"][value="0"]').prop('checked', true);
|
||||
$('input[name="ps_layered_full_tree"]').prop('disabled', true);
|
||||
} else {
|
||||
$('input[name="ps_layered_full_tree"]').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
layeredDefaultCategory.filter('[value="1"]').trigger('change');
|
||||
});
|
||||
26
modules/ps_facetedsearch/_dev/cldr/exception/localization.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
class LocalizationException {
|
||||
constructor(message) {
|
||||
this.message = message;
|
||||
this.name = 'LocalizationException';
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalizationException;
|
||||
29
modules/ps_facetedsearch/_dev/cldr/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import NumberFormatter from './number-formatter';
|
||||
import NumberSymbol from './number-symbol';
|
||||
import PriceSpecification from './specifications/price';
|
||||
import NumberSpecification from './specifications/number';
|
||||
|
||||
export {
|
||||
PriceSpecification,
|
||||
NumberSpecification,
|
||||
NumberFormatter,
|
||||
NumberSymbol,
|
||||
};
|
||||
317
modules/ps_facetedsearch/_dev/cldr/number-formatter.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
/**
|
||||
* These placeholders are used in CLDR number formatting templates.
|
||||
* They are meant to be replaced by the correct localized symbols in the number formatting process.
|
||||
*/
|
||||
import NumberSymbol from './number-symbol';
|
||||
import PriceSpecification from './specifications/price';
|
||||
import NumberSpecification from './specifications/number';
|
||||
|
||||
const escapeRE = require('lodash.escaperegexp');
|
||||
|
||||
const CURRENCY_SYMBOL_PLACEHOLDER = '¤';
|
||||
const DECIMAL_SEPARATOR_PLACEHOLDER = '.';
|
||||
const GROUP_SEPARATOR_PLACEHOLDER = ',';
|
||||
const MINUS_SIGN_PLACEHOLDER = '-';
|
||||
const PERCENT_SYMBOL_PLACEHOLDER = '%';
|
||||
const PLUS_SIGN_PLACEHOLDER = '+';
|
||||
|
||||
class NumberFormatter {
|
||||
/**
|
||||
* @param NumberSpecification specification Number specification to be used
|
||||
* (can be a number spec, a price spec, a percentage spec)
|
||||
*/
|
||||
constructor(specification) {
|
||||
this.numberSpecification = specification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the passed number according to specifications.
|
||||
*
|
||||
* @param int|float|string number The number to format
|
||||
* @param NumberSpecification specification Number specification to be used
|
||||
* (can be a number spec, a price spec, a percentage spec)
|
||||
*
|
||||
* @return string The formatted number
|
||||
* You should use this this value for display, without modifying it
|
||||
*/
|
||||
format(number, specification) {
|
||||
if (specification !== undefined) {
|
||||
this.numberSpecification = specification;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to work on the absolute value first.
|
||||
* Then the CLDR pattern will add the sign if relevant (at the end).
|
||||
*/
|
||||
const num = Math.abs(number).toFixed(this.numberSpecification.getMaxFractionDigits());
|
||||
|
||||
let [majorDigits, minorDigits] = this.extractMajorMinorDigits(num);
|
||||
majorDigits = this.splitMajorGroups(majorDigits);
|
||||
minorDigits = this.adjustMinorDigitsZeroes(minorDigits);
|
||||
|
||||
// Assemble the final number
|
||||
let formattedNumber = majorDigits;
|
||||
|
||||
if (minorDigits) {
|
||||
formattedNumber += DECIMAL_SEPARATOR_PLACEHOLDER + minorDigits;
|
||||
}
|
||||
|
||||
// Get the good CLDR formatting pattern. Sign is important here !
|
||||
const pattern = this.getCldrPattern(number < 0);
|
||||
formattedNumber = this.addPlaceholders(formattedNumber, pattern);
|
||||
formattedNumber = this.replaceSymbols(formattedNumber);
|
||||
|
||||
formattedNumber = this.performSpecificReplacements(formattedNumber);
|
||||
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number's major and minor digits.
|
||||
*
|
||||
* Major digits are the "integer" part (before decimal separator),
|
||||
* minor digits are the fractional part
|
||||
* Result will be an array of exactly 2 items: [majorDigits, minorDigits]
|
||||
*
|
||||
* Usage example:
|
||||
* list(majorDigits, minorDigits) = this.getMajorMinorDigits(decimalNumber);
|
||||
*
|
||||
* @param DecimalNumber number
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
extractMajorMinorDigits(number) {
|
||||
// Get the number's major and minor digits.
|
||||
const result = number.toString().split('.');
|
||||
const majorDigits = result[0];
|
||||
const minorDigits = (result[1] === undefined) ? '' : result[1];
|
||||
|
||||
return [majorDigits, minorDigits];
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits major digits into groups.
|
||||
*
|
||||
* e.g.: Given the major digits "1234567", and major group size
|
||||
* configured to 3 digits, the result would be "1 234 567"
|
||||
*
|
||||
* @param string majorDigits The major digits to be grouped
|
||||
*
|
||||
* @return string The grouped major digits
|
||||
*/
|
||||
splitMajorGroups(digit) {
|
||||
if (!this.numberSpecification.isGroupingUsed()) {
|
||||
return digit;
|
||||
}
|
||||
|
||||
// Reverse the major digits, since they are grouped from the right.
|
||||
const majorDigits = digit.split('').reverse();
|
||||
|
||||
// Group the major digits.
|
||||
let groups = [];
|
||||
groups.push(majorDigits.splice(0, this.numberSpecification.getPrimaryGroupSize()));
|
||||
while (majorDigits.length) {
|
||||
groups.push(majorDigits.splice(0, this.numberSpecification.getSecondaryGroupSize()));
|
||||
}
|
||||
|
||||
// Reverse back the digits and the groups
|
||||
groups = groups.reverse();
|
||||
const newGroups = [];
|
||||
groups.forEach((group) => {
|
||||
newGroups.push(group.reverse().join(''));
|
||||
});
|
||||
|
||||
// Reconstruct the major digits.
|
||||
return newGroups.join(GROUP_SEPARATOR_PLACEHOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or remove trailing zeroes, depending on specified min and max fraction digits numbers.
|
||||
*
|
||||
* @param string minorDigits Digits to be adjusted with (trimmed or padded) zeroes
|
||||
*
|
||||
* @return string The adjusted minor digits
|
||||
*/
|
||||
adjustMinorDigitsZeroes(minorDigits) {
|
||||
let digit = minorDigits;
|
||||
|
||||
if (digit.length > this.numberSpecification.getMaxFractionDigits()) {
|
||||
// Strip any trailing zeroes.
|
||||
digit = digit.replace(/0+$/, '');
|
||||
}
|
||||
|
||||
if (digit.length < this.numberSpecification.getMinFractionDigits()) {
|
||||
// Re-add needed zeroes
|
||||
digit = digit.padEnd(
|
||||
this.numberSpecification.getMinFractionDigits(),
|
||||
'0',
|
||||
);
|
||||
}
|
||||
|
||||
return digit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CLDR formatting pattern.
|
||||
*
|
||||
* @see http://cldr.unicode.org/translation/number-patterns
|
||||
*
|
||||
* @param bool isNegative If true, the negative pattern
|
||||
* will be returned instead of the positive one
|
||||
*
|
||||
* @return string The CLDR formatting pattern
|
||||
*/
|
||||
getCldrPattern(isNegative) {
|
||||
if (isNegative) {
|
||||
return this.numberSpecification.getNegativePattern();
|
||||
}
|
||||
|
||||
return this.numberSpecification.getPositivePattern();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholder number symbols with relevant numbering system's symbols.
|
||||
*
|
||||
* @param string number
|
||||
* The number to process
|
||||
*
|
||||
* @return string
|
||||
* The number with replaced symbols
|
||||
*/
|
||||
replaceSymbols(number) {
|
||||
const symbols = this.numberSpecification.getSymbol();
|
||||
|
||||
const map = {};
|
||||
map[DECIMAL_SEPARATOR_PLACEHOLDER] = symbols.getDecimal();
|
||||
map[GROUP_SEPARATOR_PLACEHOLDER] = symbols.getGroup();
|
||||
map[MINUS_SIGN_PLACEHOLDER] = symbols.getMinusSign();
|
||||
map[PERCENT_SYMBOL_PLACEHOLDER] = symbols.getPercentSign();
|
||||
map[PLUS_SIGN_PLACEHOLDER] = symbols.getPlusSign();
|
||||
|
||||
return this.strtr(number, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* strtr() for JavaScript
|
||||
* Translate characters or replace substrings
|
||||
*/
|
||||
strtr(str, pairs) {
|
||||
const substrs = Object.keys(pairs).map(escapeRE);
|
||||
|
||||
return str.split(RegExp(`(${substrs.join('|')})`))
|
||||
.map((part) => pairs[part] || part)
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing placeholders to the number using the passed CLDR pattern.
|
||||
*
|
||||
* Missing placeholders can be the percent sign, currency symbol, etc.
|
||||
*
|
||||
* e.g. with a currency CLDR pattern:
|
||||
* - Passed number (partially formatted): 1,234.567
|
||||
* - Returned number: 1,234.567 ¤
|
||||
* ("¤" symbol is the currency symbol placeholder)
|
||||
*
|
||||
* @see http://cldr.unicode.org/translation/number-patterns
|
||||
*
|
||||
* @param formattedNumber
|
||||
* Number to process
|
||||
* @param pattern
|
||||
* CLDR formatting pattern to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
addPlaceholders(formattedNumber, pattern) {
|
||||
/*
|
||||
* Regex groups explanation:
|
||||
* # : literal "#" character. Once.
|
||||
* (,#+)* : any other "#" characters group, separated by ",". Zero to infinity times.
|
||||
* 0 : literal "0" character. Once.
|
||||
* (\.[0#]+)* : any combination of "0" and "#" characters groups, separated by '.'.
|
||||
* Zero to infinity times.
|
||||
*/
|
||||
return pattern.replace(/#?(,#+)*0(\.[0#]+)*/, formattedNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some more specific replacements.
|
||||
*
|
||||
* Specific replacements are needed when number specification is extended.
|
||||
* For instance, prices have an extended number specification in order to
|
||||
* add currency symbol to the formatted number.
|
||||
*
|
||||
* @param string formattedNumber
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
performSpecificReplacements(formattedNumber) {
|
||||
if (this.numberSpecification instanceof PriceSpecification) {
|
||||
return formattedNumber
|
||||
.split(CURRENCY_SYMBOL_PLACEHOLDER)
|
||||
.join(this.numberSpecification.getCurrencySymbol());
|
||||
}
|
||||
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
static build(specifications) {
|
||||
let symbol;
|
||||
|
||||
if (undefined !== specifications.numberSymbols) {
|
||||
symbol = new NumberSymbol(...specifications.numberSymbols);
|
||||
} else {
|
||||
symbol = new NumberSymbol(...specifications.symbol);
|
||||
}
|
||||
|
||||
let specification;
|
||||
|
||||
if (specifications.currencySymbol) {
|
||||
specification = new PriceSpecification(
|
||||
specifications.positivePattern,
|
||||
specifications.negativePattern,
|
||||
symbol,
|
||||
parseInt(specifications.maxFractionDigits, 10),
|
||||
parseInt(specifications.minFractionDigits, 10),
|
||||
specifications.groupingUsed,
|
||||
specifications.primaryGroupSize,
|
||||
specifications.secondaryGroupSize,
|
||||
specifications.currencySymbol,
|
||||
specifications.currencyCode,
|
||||
);
|
||||
} else {
|
||||
specification = new NumberSpecification(
|
||||
specifications.positivePattern,
|
||||
specifications.negativePattern,
|
||||
symbol,
|
||||
parseInt(specifications.maxFractionDigits, 10),
|
||||
parseInt(specifications.minFractionDigits, 10),
|
||||
specifications.groupingUsed,
|
||||
specifications.primaryGroupSize,
|
||||
specifications.secondaryGroupSize,
|
||||
);
|
||||
}
|
||||
|
||||
return new NumberFormatter(specification);
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberFormatter;
|
||||
222
modules/ps_facetedsearch/_dev/cldr/number-symbol.js
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import LocalizationException from './exception/localization';
|
||||
|
||||
class NumberSymbol {
|
||||
/**
|
||||
* NumberSymbolList constructor.
|
||||
*
|
||||
* @param string decimal Decimal separator character
|
||||
* @param string group Digits group separator character
|
||||
* @param string list List elements separator character
|
||||
* @param string percentSign Percent sign character
|
||||
* @param string minusSign Minus sign character
|
||||
* @param string plusSign Plus sign character
|
||||
* @param string exponential Exponential character
|
||||
* @param string superscriptingExponent Superscripting exponent character
|
||||
* @param string perMille Permille sign character
|
||||
* @param string infinity The infinity sign. Corresponds to the IEEE infinity bit pattern.
|
||||
* @param string nan The NaN (Not A Number) sign. Corresponds to the IEEE NaN bit pattern.
|
||||
*
|
||||
* @throws LocalizationException
|
||||
*/
|
||||
constructor(
|
||||
decimal,
|
||||
group,
|
||||
list,
|
||||
percentSign,
|
||||
minusSign,
|
||||
plusSign,
|
||||
exponential,
|
||||
superscriptingExponent,
|
||||
perMille,
|
||||
infinity,
|
||||
nan,
|
||||
) {
|
||||
this.decimal = decimal;
|
||||
this.group = group;
|
||||
this.list = list;
|
||||
this.percentSign = percentSign;
|
||||
this.minusSign = minusSign;
|
||||
this.plusSign = plusSign;
|
||||
this.exponential = exponential;
|
||||
this.superscriptingExponent = superscriptingExponent;
|
||||
this.perMille = perMille;
|
||||
this.infinity = infinity;
|
||||
this.nan = nan;
|
||||
|
||||
this.validateData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decimal separator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getDecimal() {
|
||||
return this.decimal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the digit groups separator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list elements separator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getList() {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percent sign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getPercentSign() {
|
||||
return this.percentSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minus sign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getMinusSign() {
|
||||
return this.minusSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plus sign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getPlusSign() {
|
||||
return this.plusSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exponential character.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getExponential() {
|
||||
return this.exponential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exponent character.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getSuperscriptingExponent() {
|
||||
return this.superscriptingExponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gert the per mille symbol (often "‰").
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/Per_mille
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getPerMille() {
|
||||
return this.perMille;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the infinity symbol (often "∞").
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/Infinity_symbol
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getInfinity() {
|
||||
return this.infinity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NaN (not a number) sign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getNan() {
|
||||
return this.nan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbols list validation.
|
||||
*
|
||||
* @throws LocalizationException
|
||||
*/
|
||||
validateData() {
|
||||
if (!this.decimal || typeof this.decimal !== 'string') {
|
||||
throw new LocalizationException('Invalid decimal');
|
||||
}
|
||||
|
||||
if (!this.group || typeof this.group !== 'string') {
|
||||
throw new LocalizationException('Invalid group');
|
||||
}
|
||||
|
||||
if (!this.list || typeof this.list !== 'string') {
|
||||
throw new LocalizationException('Invalid symbol list');
|
||||
}
|
||||
|
||||
if (!this.percentSign || typeof this.percentSign !== 'string') {
|
||||
throw new LocalizationException('Invalid percentSign');
|
||||
}
|
||||
|
||||
if (!this.minusSign || typeof this.minusSign !== 'string') {
|
||||
throw new LocalizationException('Invalid minusSign');
|
||||
}
|
||||
|
||||
if (!this.plusSign || typeof this.plusSign !== 'string') {
|
||||
throw new LocalizationException('Invalid plusSign');
|
||||
}
|
||||
|
||||
if (!this.exponential || typeof this.exponential !== 'string') {
|
||||
throw new LocalizationException('Invalid exponential');
|
||||
}
|
||||
|
||||
if (!this.superscriptingExponent || typeof this.superscriptingExponent !== 'string') {
|
||||
throw new LocalizationException('Invalid superscriptingExponent');
|
||||
}
|
||||
|
||||
if (!this.perMille || typeof this.perMille !== 'string') {
|
||||
throw new LocalizationException('Invalid perMille');
|
||||
}
|
||||
|
||||
if (!this.infinity || typeof this.infinity !== 'string') {
|
||||
throw new LocalizationException('Invalid infinity');
|
||||
}
|
||||
|
||||
if (!this.nan || typeof this.nan !== 'string') {
|
||||
throw new LocalizationException('Invalid nan');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberSymbol;
|
||||
170
modules/ps_facetedsearch/_dev/cldr/specifications/number.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import LocalizationException from '../exception/localization';
|
||||
import NumberSymbol from '../number-symbol';
|
||||
|
||||
class NumberSpecification {
|
||||
/**
|
||||
* Number specification constructor.
|
||||
*
|
||||
* @param string positivePattern CLDR formatting pattern for positive amounts
|
||||
* @param string negativePattern CLDR formatting pattern for negative amounts
|
||||
* @param NumberSymbol symbol Number symbol
|
||||
* @param int maxFractionDigits Maximum number of digits after decimal separator
|
||||
* @param int minFractionDigits Minimum number of digits after decimal separator
|
||||
* @param bool groupingUsed Is digits grouping used ?
|
||||
* @param int primaryGroupSize Size of primary digits group in the number
|
||||
* @param int secondaryGroupSize Size of secondary digits group in the number
|
||||
*
|
||||
* @throws LocalizationException
|
||||
*/
|
||||
constructor(
|
||||
positivePattern,
|
||||
negativePattern,
|
||||
symbol,
|
||||
maxFractionDigits,
|
||||
minFractionDigits,
|
||||
groupingUsed,
|
||||
primaryGroupSize,
|
||||
secondaryGroupSize,
|
||||
) {
|
||||
this.positivePattern = positivePattern;
|
||||
this.negativePattern = negativePattern;
|
||||
this.symbol = symbol;
|
||||
|
||||
this.maxFractionDigits = maxFractionDigits;
|
||||
// eslint-disable-next-line
|
||||
this.minFractionDigits = maxFractionDigits < minFractionDigits ? maxFractionDigits : minFractionDigits;
|
||||
|
||||
this.groupingUsed = groupingUsed;
|
||||
this.primaryGroupSize = primaryGroupSize;
|
||||
this.secondaryGroupSize = secondaryGroupSize;
|
||||
|
||||
if (!this.positivePattern || typeof this.positivePattern !== 'string') {
|
||||
throw new LocalizationException('Invalid positivePattern');
|
||||
}
|
||||
|
||||
if (!this.negativePattern || typeof this.negativePattern !== 'string') {
|
||||
throw new LocalizationException('Invalid negativePattern');
|
||||
}
|
||||
|
||||
if (!this.symbol || !(this.symbol instanceof NumberSymbol)) {
|
||||
throw new LocalizationException('Invalid symbol');
|
||||
}
|
||||
|
||||
if (typeof this.maxFractionDigits !== 'number') {
|
||||
throw new LocalizationException('Invalid maxFractionDigits');
|
||||
}
|
||||
|
||||
if (typeof this.minFractionDigits !== 'number') {
|
||||
throw new LocalizationException('Invalid minFractionDigits');
|
||||
}
|
||||
|
||||
if (typeof this.groupingUsed !== 'boolean') {
|
||||
throw new LocalizationException('Invalid groupingUsed');
|
||||
}
|
||||
|
||||
if (typeof this.primaryGroupSize !== 'number') {
|
||||
throw new LocalizationException('Invalid primaryGroupSize');
|
||||
}
|
||||
|
||||
if (typeof this.secondaryGroupSize !== 'number') {
|
||||
throw new LocalizationException('Invalid secondaryGroupSize');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get symbol.
|
||||
*
|
||||
* @return NumberSymbol
|
||||
*/
|
||||
getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatting rules for this number (when positive).
|
||||
*
|
||||
* This pattern uses the Unicode CLDR number pattern syntax
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getPositivePattern() {
|
||||
return this.positivePattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatting rules for this number (when negative).
|
||||
*
|
||||
* This pattern uses the Unicode CLDR number pattern syntax
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getNegativePattern() {
|
||||
return this.negativePattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of digits after decimal separator (rounding if needed).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
getMaxFractionDigits() {
|
||||
return this.maxFractionDigits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum number of digits after decimal separator (fill with "0" if needed).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
getMinFractionDigits() {
|
||||
return this.minFractionDigits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "grouping" flag. This flag defines if digits
|
||||
* grouping should be used when formatting this number.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
isGroupingUsed() {
|
||||
return this.groupingUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of primary digits group in the number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
getPrimaryGroupSize() {
|
||||
return this.primaryGroupSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of secondary digits groups in the number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
getSecondaryGroupSize() {
|
||||
return this.secondaryGroupSize;
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberSpecification;
|
||||
108
modules/ps_facetedsearch/_dev/cldr/specifications/price.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import LocalizationException from '../exception/localization';
|
||||
import NumberSpecification from './number';
|
||||
|
||||
/**
|
||||
* Currency display option: symbol notation.
|
||||
*/
|
||||
const CURRENCY_DISPLAY_SYMBOL = 'symbol';
|
||||
|
||||
class PriceSpecification extends NumberSpecification {
|
||||
/**
|
||||
* Price specification constructor.
|
||||
*
|
||||
* @param string positivePattern CLDR formatting pattern for positive amounts
|
||||
* @param string negativePattern CLDR formatting pattern for negative amounts
|
||||
* @param NumberSymbol symbol Number symbol
|
||||
* @param int maxFractionDigits Maximum number of digits after decimal separator
|
||||
* @param int minFractionDigits Minimum number of digits after decimal separator
|
||||
* @param bool groupingUsed Is digits grouping used ?
|
||||
* @param int primaryGroupSize Size of primary digits group in the number
|
||||
* @param int secondaryGroupSize Size of secondary digits group in the number
|
||||
* @param string currencySymbol Currency symbol of this price (eg. : €)
|
||||
* @param currencyCode Currency code of this price (e.g.: EUR)
|
||||
*
|
||||
* @throws LocalizationException
|
||||
*/
|
||||
constructor(
|
||||
positivePattern,
|
||||
negativePattern,
|
||||
symbol,
|
||||
maxFractionDigits,
|
||||
minFractionDigits,
|
||||
groupingUsed,
|
||||
primaryGroupSize,
|
||||
secondaryGroupSize,
|
||||
currencySymbol,
|
||||
currencyCode,
|
||||
) {
|
||||
super(
|
||||
positivePattern,
|
||||
negativePattern,
|
||||
symbol,
|
||||
maxFractionDigits,
|
||||
minFractionDigits,
|
||||
groupingUsed,
|
||||
primaryGroupSize,
|
||||
secondaryGroupSize,
|
||||
);
|
||||
this.currencySymbol = currencySymbol;
|
||||
this.currencyCode = currencyCode;
|
||||
|
||||
if (!this.currencySymbol || typeof this.currencySymbol !== 'string') {
|
||||
throw new LocalizationException('Invalid currencySymbol');
|
||||
}
|
||||
|
||||
if (!this.currencyCode || typeof this.currencyCode !== 'string') {
|
||||
throw new LocalizationException('Invalid currencyCode');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type of display for currency symbol.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static getCurrencyDisplay() {
|
||||
return CURRENCY_DISPLAY_SYMBOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currency symbol
|
||||
* e.g.: €.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getCurrencySymbol() {
|
||||
return this.currencySymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currency ISO code
|
||||
* e.g.: EUR.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
getCurrencyCode() {
|
||||
return this.currencyCode;
|
||||
}
|
||||
}
|
||||
|
||||
export default PriceSpecification;
|
||||
33
modules/ps_facetedsearch/_dev/front/events.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import refreshSliders from './slider';
|
||||
import {showOverlay, hideOverlay} from './overlay';
|
||||
|
||||
$(document).ready(() => {
|
||||
prestashop.on('updateProductList', () => {
|
||||
hideOverlay();
|
||||
refreshSliders();
|
||||
});
|
||||
|
||||
refreshSliders();
|
||||
|
||||
prestashop.on('updateFacets', () => {
|
||||
showOverlay();
|
||||
});
|
||||
});
|
||||
48
modules/ps_facetedsearch/_dev/front/facet.css
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
#search_filters .facet .title {
|
||||
display: flex;
|
||||
}
|
||||
#search_filters .facet .title .collapse-icons {
|
||||
margin-left: auto;
|
||||
}
|
||||
#search_filters .facet .facet-title {
|
||||
width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#search_filters .facet .facet-label {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
#search_filters .facet .facet-label .custom-checkbox,
|
||||
#search_filters .facet .facet-label .custom-radio {
|
||||
top: -7px;
|
||||
margin-right: 0;
|
||||
}
|
||||
#search_filters .facet .facet-label .color {
|
||||
margin-left: 0;
|
||||
}
|
||||
#search_filters .facet .facet-label a {
|
||||
width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}/*# sourceMappingURL=facet.css.map */
|
||||
1
modules/ps_facetedsearch/_dev/front/facet.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["facet.scss","facet.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;EAAA;AA2BI;EACE,aAAA;ACRN;ADSM;EACE,iBAAA;ACPR;ADWI;EAfF,wBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ACOF;ADSI;EACE,WAAA;EACA,gBAAA;ACPN;ADQM;;EAEE,SAAA;EACA,eAAA;ACNR;ADQM;EACE,cAAA;ACNR;ADSM;EA/BJ,wBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ACyBF","file":"facet.css"}
|
||||
56
modules/ps_facetedsearch/_dev/front/facet.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
@mixin text-ellipsis() {
|
||||
width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#search_filters {
|
||||
.facet {
|
||||
.title {
|
||||
display: flex;
|
||||
.collapse-icons {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.facet-title {
|
||||
@include text-ellipsis();
|
||||
}
|
||||
|
||||
.facet-label {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
.custom-checkbox,
|
||||
.custom-radio {
|
||||
top: -7px;
|
||||
margin-right: 0;
|
||||
}
|
||||
.color {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@include text-ellipsis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
modules/ps_facetedsearch/_dev/front/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
import 'jquery-ui-touch-punch';
|
||||
import './events';
|
||||
import './slider.scss';
|
||||
import './facet.scss';
|
||||
57
modules/ps_facetedsearch/_dev/front/overlay.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
.faceted-overlay {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
background-color: rgba(25, 25, 25, 0.5);
|
||||
z-index: 100;
|
||||
}
|
||||
.faceted-overlay .overlay__inner {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
.faceted-overlay .overlay__content {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.faceted-overlay .spinner {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
display: inline-block;
|
||||
border-width: 2px;
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s infinite linear;
|
||||
border-radius: 100%;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}/*# sourceMappingURL=overlay.css.map */
|
||||
1
modules/ps_facetedsearch/_dev/front/overlay.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["overlay.scss","overlay.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;EAAA;AAkBA;EACE,OAAA;EACA,MAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;EACA,uCAAA;EACA,YAAA;ACCF;ADCE;EACE,OAAA;EACA,MAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;ACCJ;ADEE;EACE,SAAA;EACA,kBAAA;EACA,QAAA;EACA,gCAAA;ACAJ;ADGE;EACE,WAAA;EACA,YAAA;EACA,qBAAA;EACA,iBAAA;EACA,uCAAA;EACA,sBAAA;EACA,kCAAA;EACA,mBAAA;EACA,mBAAA;ACDJ;;ADKA;EACE;IACE,yBAAA;ECFF;AACF","file":"overlay.css"}
|
||||
43
modules/ps_facetedsearch/_dev/front/overlay.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
import './overlay.scss';
|
||||
|
||||
const template = `<div class="faceted-overlay">
|
||||
<div class="overlay__inner">
|
||||
<div class="overlay__content"><span class="spinner"></span></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
function show() {
|
||||
if ($('.faceted-overlay').length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').append(template);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$('.faceted-overlay').remove();
|
||||
}
|
||||
|
||||
export {
|
||||
show as showOverlay,
|
||||
hide as hideOverlay,
|
||||
};
|
||||
60
modules/ps_facetedsearch/_dev/front/overlay.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
.faceted-overlay {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
background-color: rgba(25, 25, 25, 0.5);
|
||||
z-index: 100;
|
||||
|
||||
.overlay__inner {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.overlay__content {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
display: inline-block;
|
||||
border-width: 2px;
|
||||
border-color: rgba(255, 255, 255, 0.05);
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s infinite linear;
|
||||
border-radius: 100%;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
34
modules/ps_facetedsearch/_dev/front/slider.css
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
#search_filters .ui-slider-horizontal .ui-slider-handle {
|
||||
margin-left: -1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#search_filters .ui-widget-header {
|
||||
background: #555;
|
||||
}
|
||||
#search_filters .ui-slider .ui-slider-handle {
|
||||
top: -0.45em;
|
||||
width: 0.4em;
|
||||
background: #fff;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
#search_filters .ui-slider-horizontal {
|
||||
height: 0.4em;
|
||||
}/*# sourceMappingURL=slider.css.map */
|
||||
1
modules/ps_facetedsearch/_dev/front/slider.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["slider.scss","slider.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;EAAA;AAoBI;EACE,iBAAA;EACA,eAAA;ACDN;ADIE;EACE,gBAAA;ACFJ;ADKI;EACE,YAAA;EACA,YAAA;EACA,gBAAA;EACA,sBAAA;ACHN;ADME;EACE,aAAA;ACJJ","file":"slider.css"}
|
||||
129
modules/ps_facetedsearch/_dev/front/slider.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
import getQueryParameters from './urlparser';
|
||||
import NumberFormatter from '../cldr/number-formatter';
|
||||
|
||||
const formatters = {};
|
||||
|
||||
const displayLabelBlock = (formatterId, displayBlock, min, max) => {
|
||||
if (formatters[formatterId] === undefined) {
|
||||
displayBlock.text(
|
||||
displayBlock.text().replace(
|
||||
/([^\d]*)(?:[\d\s.,]+)([^\d]+)(?:[\d\s.,]+)(.*)/,
|
||||
`$1${min}$2${max}$3`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
displayBlock.text(
|
||||
`${formatters[formatterId].format(min)} - ${formatters[formatterId].format(max)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh facets sliders
|
||||
*/
|
||||
const refreshSliders = () => {
|
||||
$('.faceted-slider').each(function initializeSliders() {
|
||||
const $el = $(this);
|
||||
const values = $el.data('slider-values');
|
||||
const specifications = $el.data('slider-specifications');
|
||||
|
||||
if (specifications !== null && specifications !== undefined) {
|
||||
formatters[$el.data('slider-id')] = NumberFormatter.build(specifications);
|
||||
}
|
||||
|
||||
displayLabelBlock(
|
||||
$el.data('slider-id'),
|
||||
$(`#facet_label_${$el.data('slider-id')}`),
|
||||
values === null ? $el.data('slider-min') : values[0],
|
||||
values === null ? $el.data('slider-max') : values[1],
|
||||
);
|
||||
|
||||
$(`#slider-range_${$el.data('slider-id')}`).slider({
|
||||
range: true,
|
||||
min: $el.data('slider-min'),
|
||||
max: $el.data('slider-max'),
|
||||
values: [
|
||||
values === null ? $el.data('slider-min') : values[0],
|
||||
values === null ? $el.data('slider-max') : values[1],
|
||||
],
|
||||
stop(event, ui) {
|
||||
const nextEncodedFacetsURL = $el.data('slider-encoded-url');
|
||||
const urlsSplitted = nextEncodedFacetsURL.split('?');
|
||||
let queryParams = [];
|
||||
|
||||
// Retrieve parameters if exists
|
||||
if (urlsSplitted.length > 1) {
|
||||
queryParams = getQueryParameters(urlsSplitted[1]);
|
||||
}
|
||||
|
||||
let found = false;
|
||||
queryParams.forEach((query) => {
|
||||
if (query.name === 'q') {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
queryParams.push({name: 'q', value: ''});
|
||||
}
|
||||
|
||||
// Update query parameter
|
||||
queryParams.forEach((query) => {
|
||||
if (query.name === 'q') {
|
||||
// eslint-disable-next-line
|
||||
query.value += [
|
||||
query.value.length > 0 ? '/' : '',
|
||||
$el.data('slider-label'),
|
||||
'-',
|
||||
$el.data('slider-unit'),
|
||||
'-',
|
||||
ui.values[0],
|
||||
'-',
|
||||
ui.values[1],
|
||||
].join('');
|
||||
}
|
||||
});
|
||||
|
||||
const requestUrl = [
|
||||
urlsSplitted[0],
|
||||
'?',
|
||||
$.param(queryParams),
|
||||
].join('');
|
||||
|
||||
prestashop.emit(
|
||||
'updateFacets',
|
||||
requestUrl,
|
||||
);
|
||||
},
|
||||
slide(event, ui) {
|
||||
displayLabelBlock(
|
||||
$el.data('slider-id'),
|
||||
$(`#facet_label_${$el.data('slider-id')}`),
|
||||
ui.values[0],
|
||||
ui.values[1],
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshSliders;
|
||||
40
modules/ps_facetedsearch/_dev/front/slider.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
#search_filters {
|
||||
.ui-slider-horizontal {
|
||||
.ui-slider-handle {
|
||||
margin-left: -1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ui-widget-header {
|
||||
background: #555;
|
||||
}
|
||||
.ui-slider {
|
||||
.ui-slider-handle {
|
||||
top: -.45em;
|
||||
width: 0.4em;
|
||||
background: #fff;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
}
|
||||
.ui-slider-horizontal {
|
||||
height: .4em;
|
||||
}
|
||||
}
|
||||
29
modules/ps_facetedsearch/_dev/front/urlparser.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
const getQueryParameters = (params) => params.split('&').map((str) => {
|
||||
const [key, val] = str.split('=');
|
||||
|
||||
return {
|
||||
name: key,
|
||||
value: decodeURIComponent(val).replace(/\+/g, ' '),
|
||||
};
|
||||
});
|
||||
|
||||
export default getQueryParameters;
|
||||
28
modules/ps_facetedsearch/_dev/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../../');
|
||||
exit;
|
||||
3104
modules/ps_facetedsearch/composer.lock
generated
Normal file
12
modules/ps_facetedsearch/config.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<module>
|
||||
<name>ps_facetedsearch</name>
|
||||
<displayName><![CDATA[Faceted search]]></displayName>
|
||||
<version><![CDATA[3.8.0]]></version>
|
||||
<description><![CDATA[Displays a block allowing multiple filters.]]></description>
|
||||
<author><![CDATA[PrestaShop]]></author>
|
||||
<tab><![CDATA[front_office_features]]></tab>
|
||||
<is_configurable>1</is_configurable>
|
||||
<need_instance>0</need_instance>
|
||||
<limited_countries></limited_countries>
|
||||
</module>
|
||||
28
modules/ps_facetedsearch/config/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
10
modules/ps_facetedsearch/config/services.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
_defaults:
|
||||
public: true
|
||||
|
||||
prestashop.module.ps_facetedsearch.constraint.url_segment_validator:
|
||||
class: PrestaShop\Module\FacetedSearch\Constraint\UrlSegmentValidator
|
||||
arguments:
|
||||
- '@prestashop.adapter.tools'
|
||||
tags:
|
||||
- { name: validator.constraint_validator }
|
||||
12
modules/ps_facetedsearch/config_pl.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<module>
|
||||
<name>ps_facetedsearch</name>
|
||||
<displayName><![CDATA[Nawigacja fasetowa]]></displayName>
|
||||
<version><![CDATA[3.8.0]]></version>
|
||||
<description><![CDATA[Filter your catalog to help visitors picture the category tree and browse your store easily.]]></description>
|
||||
<author><![CDATA[PrestaShop]]></author>
|
||||
<tab><![CDATA[front_office_features]]></tab>
|
||||
<is_configurable>1</is_configurable>
|
||||
<need_instance>0</need_instance>
|
||||
<limited_countries></limited_countries>
|
||||
</module>
|
||||
BIN
modules/ps_facetedsearch/delete.gif
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
modules/ps_facetedsearch/img/cogs.gif
Normal file
|
After Width: | Height: | Size: 280 B |
BIN
modules/ps_facetedsearch/img/cross.png
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
modules/ps_facetedsearch/img/edit.gif
Normal file
|
After Width: | Height: | Size: 573 B |
28
modules/ps_facetedsearch/img/icon/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
BIN
modules/ps_facetedsearch/img/icon/less.gif
Normal file
|
After Width: | Height: | Size: 58 B |
BIN
modules/ps_facetedsearch/img/icon/more.gif
Normal file
|
After Width: | Height: | Size: 58 B |
28
modules/ps_facetedsearch/img/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
28
modules/ps_facetedsearch/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
BIN
modules/ps_facetedsearch/logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
23925
modules/ps_facetedsearch/package-lock.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/config.inc.php';
|
||||
require_once __DIR__ . '/ps_facetedsearch.php';
|
||||
|
||||
if (substr(Tools::hash('ps_facetedsearch/index'), 0, 10) != Tools::getValue('token') || !Module::isInstalled('ps_facetedsearch')) {
|
||||
exit('Bad token');
|
||||
}
|
||||
|
||||
Shop::setContext(Shop::CONTEXT_ALL);
|
||||
|
||||
$psFacetedsearch = new Ps_Facetedsearch();
|
||||
$psFacetedsearch->indexAttributes();
|
||||
$psFacetedsearch->indexFeatures();
|
||||
$psFacetedsearch->indexAttributeGroup();
|
||||
|
||||
echo 1;
|
||||
28
modules/ps_facetedsearch/ps_facetedsearch-clear-cache.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/config.inc.php';
|
||||
require_once __DIR__ . '/ps_facetedsearch.php';
|
||||
|
||||
if (substr(Tools::hash('ps_facetedsearch/index'), 0, 10) != Tools::getValue('token') || !Module::isInstalled('ps_facetedsearch')) {
|
||||
exit('Bad token');
|
||||
}
|
||||
|
||||
$psFacetedsearch = new Ps_Facetedsearch();
|
||||
echo $psFacetedsearch->invalidateLayeredFilterBlockCache();
|
||||
34
modules/ps_facetedsearch/ps_facetedsearch-price-indexer.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/config.inc.php';
|
||||
require_once __DIR__ . '/ps_facetedsearch.php';
|
||||
|
||||
if (substr(Tools::hash('ps_facetedsearch/index'), 0, 10) != Tools::getValue('token') || !Module::isInstalled('ps_facetedsearch')) {
|
||||
exit('Bad token');
|
||||
}
|
||||
|
||||
Shop::setContext(Shop::CONTEXT_ALL);
|
||||
|
||||
$module = new Ps_Facetedsearch();
|
||||
if (Tools::getValue('full')) {
|
||||
echo $module->fullPricesIndexProcess((int) Tools::getValue('cursor'), (bool) Tools::getValue('ajax'), true);
|
||||
} else {
|
||||
echo $module->pricesIndexProcess((int) Tools::getValue('cursor'), (bool) Tools::getValue('ajax'));
|
||||
}
|
||||
1506
modules/ps_facetedsearch/ps_facetedsearch.php
Normal file
25
modules/ps_facetedsearch/ps_facetedsearch.tpl
Normal file
@@ -0,0 +1,25 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{if isset($listing.rendered_active_filters)}
|
||||
{$listing.rendered_active_filters nofilter}
|
||||
{/if}
|
||||
|
||||
{if isset($listing.rendered_facets)}
|
||||
{$listing.rendered_facets nofilter}
|
||||
{/if}
|
||||
328
modules/ps_facetedsearch/src/Adapter/AbstractAdapter.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Adapter;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
abstract class AbstractAdapter implements InterfaceAdapter
|
||||
{
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
protected $operationsFilters;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
protected $selectFields;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
protected $groupFields;
|
||||
|
||||
protected $orderField = 'id_product';
|
||||
|
||||
protected $orderDirection = 'DESC';
|
||||
|
||||
protected $limit = 20;
|
||||
|
||||
protected $offset = 0;
|
||||
|
||||
/** @var InterfaceAdapter */
|
||||
protected $initialPopulation = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->groupFields = new ArrayCollection();
|
||||
$this->selectFields = new ArrayCollection();
|
||||
$this->filters = new ArrayCollection();
|
||||
$this->operationsFilters = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->filters = clone $this->filters;
|
||||
$this->operationsFilters = clone $this->operationsFilters;
|
||||
$this->groupFields = clone $this->groupFields;
|
||||
$this->selectFields = clone $this->selectFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInitialPopulation()
|
||||
{
|
||||
return $this->initialPopulation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetFilter($filterName)
|
||||
{
|
||||
if ($this->filters->offsetExists($filterName)) {
|
||||
$this->filters->offsetUnset($filterName);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetOperationsFilter($filterName)
|
||||
{
|
||||
if ($this->operationsFilters->offsetExists($filterName)) {
|
||||
$this->operationsFilters->offsetUnset($filterName);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetOperationsFilters()
|
||||
{
|
||||
$this->operationsFilters = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetAll()
|
||||
{
|
||||
$this->selectFields = new ArrayCollection();
|
||||
$this->groupFields = new ArrayCollection();
|
||||
$this->filters = new ArrayCollection();
|
||||
$this->operationsFilters = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilter($filterName)
|
||||
{
|
||||
if (isset($this->filters[$filterName])) {
|
||||
return $this->filters[$filterName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrderDirection()
|
||||
{
|
||||
return $this->orderDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrderField()
|
||||
{
|
||||
return $this->orderField;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupFields()
|
||||
{
|
||||
return $this->groupFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectFields()
|
||||
{
|
||||
return $this->selectFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOperationsFilters()
|
||||
{
|
||||
return $this->operationsFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function copyFilters(InterfaceAdapter $adapter)
|
||||
{
|
||||
$this->filters = clone $adapter->getFilters();
|
||||
$this->operationsFilters = clone $adapter->getOperationsFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addFilter($filterName, $values, $operator = '=')
|
||||
{
|
||||
$filters = $this->filters->get($filterName);
|
||||
if (!isset($filters[$operator])) {
|
||||
$filters[$operator] = [];
|
||||
}
|
||||
|
||||
$filters[$operator][] = $values;
|
||||
$this->filters->set($filterName, $filters);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addOperationsFilter($filterName, array $operations = [])
|
||||
{
|
||||
$this->operationsFilters->set($filterName, $operations);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSelectField($fieldName)
|
||||
{
|
||||
if (!$this->selectFields->contains($fieldName)) {
|
||||
$this->selectFields->add($fieldName);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSelectFields($selectFields)
|
||||
{
|
||||
$this->selectFields = new ArrayCollection($selectFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetSelectField()
|
||||
{
|
||||
$this->selectFields = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addGroupBy($groupField)
|
||||
{
|
||||
$this->groupFields->add($groupField);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setGroupFields($groupFields)
|
||||
{
|
||||
$this->groupFields = new ArrayCollection($groupFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetGroupBy()
|
||||
{
|
||||
$this->groupFields = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFilter($filterName, $value)
|
||||
{
|
||||
if ($value !== null) {
|
||||
$this->filters->set($filterName, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOrderField($fieldName)
|
||||
{
|
||||
$this->orderField = $fieldName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOrderDirection($direction)
|
||||
{
|
||||
$this->orderDirection = $direction === 'desc' ? 'desc' : 'asc';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLimit($limit, $offset = 0)
|
||||
{
|
||||
$this->limit = $limit ? (int) $limit : null;
|
||||
$this->offset = (int) $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
294
modules/ps_facetedsearch/src/Adapter/InterfaceAdapter.php
Normal file
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Adapter;
|
||||
|
||||
interface InterfaceAdapter
|
||||
{
|
||||
/**
|
||||
* Set order by field
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setOrderField($fieldName);
|
||||
|
||||
/**
|
||||
* Set the order by direction for the given field
|
||||
*
|
||||
* @param string $direction
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setOrderDirection($direction);
|
||||
|
||||
/**
|
||||
* Set the limit and offset associated with the current search
|
||||
*
|
||||
* @param int|null $limit
|
||||
* @param int $offset
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLimit($limit, $offset = 0);
|
||||
|
||||
/**
|
||||
* Execute the search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute();
|
||||
|
||||
/**
|
||||
* Get the current query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQuery();
|
||||
|
||||
/**
|
||||
* Get the min & max value of the field filedName associated with the current search
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMinMaxValue($fieldName);
|
||||
|
||||
/**
|
||||
* Get the min & max value of the price associated with the current search
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMinMaxPriceValue();
|
||||
|
||||
/**
|
||||
* Return order direction associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOrderDirection();
|
||||
|
||||
/**
|
||||
* Return order field associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOrderField();
|
||||
|
||||
/**
|
||||
* Return all group fields associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getGroupFields();
|
||||
|
||||
/**
|
||||
* Return all selected fields associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSelectFields();
|
||||
|
||||
/**
|
||||
* Return all the filters associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFilters();
|
||||
|
||||
/**
|
||||
* Return all the operations filters associated with the current search
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOperationsFilters();
|
||||
|
||||
/**
|
||||
* Return the number of results associated for the current search
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count();
|
||||
|
||||
/**
|
||||
* Move the current search into the "initialPopulation"
|
||||
* This initialPopulation will be used to generate the first derived table 'FROM (SELECT ...)' in the final query
|
||||
* e.g. : SELECT ... FROM (initialPopulation) p JOIN ....
|
||||
*/
|
||||
public function useFiltersAsInitialPopulation();
|
||||
|
||||
/**
|
||||
* Create a new SearchAdapter, keeping the initialPopulation of the current Search
|
||||
*
|
||||
* @param string $resetFilter reset this filter inside the initialPopulation
|
||||
* @param bool $skipInitialPopulation if enable, do not copy the initialPopulation filter
|
||||
*
|
||||
* @return InterfaceAdapter
|
||||
*/
|
||||
public function getFilteredSearchAdapter($resetFilter = null, $skipInitialPopulation = false);
|
||||
|
||||
/**
|
||||
* Add a new filter with filterName, operator & values to the current search
|
||||
* If several values are provided with the = operator, it's converted automatically to a IN () in the final query
|
||||
*
|
||||
* @param string $filterName
|
||||
* @param array $values
|
||||
* @param string $operator
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addFilter($filterName, $values, $operator = '=');
|
||||
|
||||
/**
|
||||
* Add a stack of operations with filterName. Operations must contains filterName, values and to the current search
|
||||
*
|
||||
* @param string $filterName
|
||||
* @param array $operations
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addOperationsFilter($filterName, array $operations);
|
||||
|
||||
/**
|
||||
* Add fieldName in the current search result
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addSelectField($fieldName);
|
||||
|
||||
/**
|
||||
* Returns the number of distinct products, group by fieldName values
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function valueCount($fieldName = null);
|
||||
|
||||
/**
|
||||
* Reset the operations filters
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetOperationsFilters();
|
||||
|
||||
/**
|
||||
* Reset the operations filter for the given filterName
|
||||
*
|
||||
* @param string $filterName
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetOperationsFilter($filterName);
|
||||
|
||||
/**
|
||||
* Reset the filter for the given filterName
|
||||
*
|
||||
* @param string $filterName
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetFilter($filterName);
|
||||
|
||||
/**
|
||||
* Return the filter associated with filterName
|
||||
*
|
||||
* @param string $filterName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFilter($filterName);
|
||||
|
||||
/**
|
||||
* Set the filterName to the given array value
|
||||
*
|
||||
* @param string $filterName
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setFilter($filterName, $value);
|
||||
|
||||
/**
|
||||
* Return the current initialPopulation
|
||||
*
|
||||
* @return self|null
|
||||
*/
|
||||
public function getInitialPopulation();
|
||||
|
||||
/**
|
||||
* Return all the filters / groupFields / selectFields
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetAll();
|
||||
|
||||
/**
|
||||
* Copy all the filters & operationsFilters from adapter to the current search
|
||||
*
|
||||
* @param InterfaceAdapter $adapter
|
||||
*/
|
||||
public function copyFilters(InterfaceAdapter $adapter);
|
||||
|
||||
/**
|
||||
* Set all the select fields
|
||||
*
|
||||
* @param array $selectFields
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSelectFields($selectFields);
|
||||
|
||||
/**
|
||||
* Reset all the select fields
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetSelectField();
|
||||
|
||||
/**
|
||||
* Add a group by field
|
||||
*
|
||||
* @param string $groupField
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addGroupBy($groupField);
|
||||
|
||||
/**
|
||||
* Set the group by fields
|
||||
*
|
||||
* @param array $groupFields
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setGroupFields($groupFields);
|
||||
|
||||
/**
|
||||
* Reset the group by conditions
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetGroupBy();
|
||||
}
|
||||
815
modules/ps_facetedsearch/src/Adapter/MySQL.php
Normal file
@@ -0,0 +1,815 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Adapter;
|
||||
|
||||
use Configuration;
|
||||
use Context;
|
||||
use Db;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Product;
|
||||
use StockAvailable;
|
||||
|
||||
class MySQL extends AbstractAdapter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const TYPE = 'MySQL';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const LEFT_JOIN = 'LEFT JOIN';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const INNER_JOIN = 'INNER JOIN';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinMaxPriceValue()
|
||||
{
|
||||
$mysqlAdapter = $this->getFilteredSearchAdapter();
|
||||
$mysqlAdapter->copyFilters($this);
|
||||
$mysqlAdapter->setSelectFields(['price_min', 'MIN(price_min) as min, MAX(price_max) as max']);
|
||||
$mysqlAdapter->setLimit(null);
|
||||
$mysqlAdapter->setOrderField('');
|
||||
|
||||
$result = $mysqlAdapter->execute();
|
||||
|
||||
return [floor((float) $result[0]['min']), ceil((float) $result[0]['max'])];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilteredSearchAdapter($resetFilter = null, $skipInitialPopulation = false)
|
||||
{
|
||||
$mysqlAdapter = new self();
|
||||
if ($this->getInitialPopulation() !== null && !$skipInitialPopulation) {
|
||||
$mysqlAdapter->initialPopulation = clone $this->getInitialPopulation();
|
||||
if ($resetFilter) {
|
||||
// Try to reset filter & operations filter
|
||||
$mysqlAdapter->initialPopulation->resetFilter($resetFilter);
|
||||
$mysqlAdapter->initialPopulation->resetOperationsFilter($resetFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return $mysqlAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
return $this->getDatabase()->executeS($this->getQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the final sql query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
$filterToTableMapping = $this->getFieldMapping();
|
||||
$orderField = $this->computeOrderByField($filterToTableMapping);
|
||||
|
||||
if ($this->getInitialPopulation() === null) {
|
||||
$referenceTable = _DB_PREFIX_ . 'product';
|
||||
} else {
|
||||
$referenceTable = '(' . $this->getInitialPopulation()->getQuery() . ')';
|
||||
}
|
||||
|
||||
$query = 'SELECT ';
|
||||
|
||||
$selectFields = $this->computeSelectFields($filterToTableMapping);
|
||||
$whereConditions = $this->computeWhereConditions($filterToTableMapping);
|
||||
$joinConditions = $this->computeJoinConditions($filterToTableMapping);
|
||||
$groupFields = $this->computeGroupByFields($filterToTableMapping);
|
||||
|
||||
$query .= implode(', ', $selectFields) . ' FROM ' . $referenceTable . ' p';
|
||||
|
||||
foreach ($joinConditions as $joinAliasInfos) {
|
||||
foreach ($joinAliasInfos as $tableAlias => $joinInfos) {
|
||||
$query .= ' ' . $joinInfos['joinType'] . ' ' . _DB_PREFIX_ . $joinInfos['tableName'] . ' ' .
|
||||
$tableAlias . ' ON ' . $joinInfos['joinCondition'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($whereConditions)) {
|
||||
$query .= ' WHERE ' . implode(' AND ', $whereConditions);
|
||||
}
|
||||
|
||||
if ($groupFields) {
|
||||
$query .= ' GROUP BY ' . implode(', ', $groupFields);
|
||||
}
|
||||
|
||||
if ($orderField) {
|
||||
$query .= ' ORDER BY ' . $orderField . ' ' . strtoupper($this->getOrderDirection());
|
||||
if ($orderField !== 'p.id_product') {
|
||||
$query .= ', p.id_product DESC';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->limit !== null) {
|
||||
$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the mapping between fields and tables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldMapping()
|
||||
{
|
||||
$stockCondition = StockAvailable::addSqlShopRestriction(
|
||||
null,
|
||||
null,
|
||||
'sa'
|
||||
);
|
||||
|
||||
$filterToTableMapping = [
|
||||
'id_product_attribute' => [
|
||||
'tableName' => 'product_attribute',
|
||||
'tableAlias' => 'pa',
|
||||
'joinCondition' => '(p.id_product = pa.id_product)',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
],
|
||||
'id_attribute' => [
|
||||
'tableName' => 'product_attribute_combination',
|
||||
'tableAlias' => 'pac',
|
||||
'joinCondition' => '(pa.id_product_attribute = pac.id_product_attribute)',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
'dependencyField' => 'id_product_attribute',
|
||||
],
|
||||
'id_attribute_group' => [
|
||||
'tableName' => 'attribute',
|
||||
'tableAlias' => 'a',
|
||||
'joinCondition' => '(a.id_attribute = pac.id_attribute)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
'dependencyField' => 'id_attribute',
|
||||
],
|
||||
'id_feature' => [
|
||||
'tableName' => 'feature_product',
|
||||
'tableAlias' => 'fp',
|
||||
'joinCondition' => '(p.id_product = fp.id_product)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'id_shop' => [
|
||||
'tableName' => 'product_shop',
|
||||
'tableAlias' => 'ps',
|
||||
'joinCondition' => '(p.id_product = ps.id_product AND ps.id_shop = ' .
|
||||
$this->getContext()->shop->id . ' AND ps.active = TRUE)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'id_feature_value' => [
|
||||
'tableName' => 'feature_product',
|
||||
'tableAlias' => 'fp',
|
||||
'joinCondition' => '(p.id_product = fp.id_product)',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
],
|
||||
'id_category' => [
|
||||
'tableName' => 'category_product',
|
||||
'tableAlias' => 'cp',
|
||||
'joinCondition' => '(p.id_product = cp.id_product)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'position' => [
|
||||
'tableName' => 'category_product',
|
||||
'tableAlias' => 'cp',
|
||||
'joinCondition' => '(p.id_product = cp.id_product)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'manufacturer_name' => [
|
||||
'tableName' => 'manufacturer',
|
||||
'tableAlias' => 'm',
|
||||
'fieldName' => 'name',
|
||||
'joinCondition' => '(p.id_manufacturer = m.id_manufacturer)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'name' => [
|
||||
'tableName' => 'product_lang',
|
||||
'tableAlias' => 'pl',
|
||||
'joinCondition' => '(p.id_product = pl.id_product AND pl.id_shop = ' .
|
||||
$this->getContext()->shop->id . ' AND pl.id_lang = ' . $this->getContext()->language->id . ')',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'nleft' => [
|
||||
'tableName' => 'category',
|
||||
'tableAlias' => 'c',
|
||||
'joinCondition' => '(cp.id_category = c.id_category AND c.active=1)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
'dependencyField' => 'id_category',
|
||||
],
|
||||
'nright' => [
|
||||
'tableName' => 'category',
|
||||
'tableAlias' => 'c',
|
||||
'joinCondition' => '(cp.id_category = c.id_category AND c.active=1)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
'dependencyField' => 'id_category',
|
||||
],
|
||||
'level_depth' => [
|
||||
'tableName' => 'category',
|
||||
'tableAlias' => 'c',
|
||||
'joinCondition' => '(cp.id_category = c.id_category AND c.active=1)',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
'dependencyField' => 'id_category',
|
||||
],
|
||||
'out_of_stock' => [
|
||||
'tableName' => 'stock_available',
|
||||
'tableAlias' => 'sa',
|
||||
'joinCondition' => '(p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute' .
|
||||
$stockCondition . ')',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
'dependencyField' => 'id_attribute',
|
||||
],
|
||||
'quantity' => [
|
||||
'tableName' => 'stock_available',
|
||||
'tableAlias' => 'sa',
|
||||
'joinCondition' => '(p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute' .
|
||||
$stockCondition . ')',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
'dependencyField' => 'id_attribute',
|
||||
'aggregateFunction' => 'SUM',
|
||||
'aggregateFieldName' => 'quantity',
|
||||
],
|
||||
'price_min' => [
|
||||
'tableName' => 'layered_price_index',
|
||||
'tableAlias' => 'psi',
|
||||
'joinCondition' => '(psi.id_product = p.id_product AND psi.id_shop = ' . $this->getContext()->shop->id . ' AND psi.id_currency = ' .
|
||||
$this->getContext()->currency->id . ' AND psi.id_country = ' . $this->getContext()->country->id . ')',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'price_max' => [
|
||||
'tableName' => 'layered_price_index',
|
||||
'tableAlias' => 'psi',
|
||||
'joinCondition' => '(psi.id_product = p.id_product AND psi.id_shop = ' . $this->getContext()->shop->id . ' AND psi.id_currency = ' .
|
||||
$this->getContext()->currency->id . ' AND psi.id_country = ' . $this->getContext()->country->id . ')',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'range_start' => [
|
||||
'tableName' => 'layered_price_index',
|
||||
'tableAlias' => 'psi',
|
||||
'joinCondition' => '(psi.id_product = p.id_product AND psi.id_shop = ' . $this->getContext()->shop->id . ' AND psi.id_currency = ' .
|
||||
$this->getContext()->currency->id . ' AND psi.id_country = ' . $this->getContext()->country->id . ')',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'range_end' => [
|
||||
'tableName' => 'layered_price_index',
|
||||
'tableAlias' => 'psi',
|
||||
'joinCondition' => '(psi.id_product = p.id_product AND psi.id_shop = ' . $this->getContext()->shop->id . ' AND psi.id_currency = ' .
|
||||
$this->getContext()->currency->id . ' AND psi.id_country = ' . $this->getContext()->country->id . ')',
|
||||
'joinType' => self::INNER_JOIN,
|
||||
],
|
||||
'id_group' => [
|
||||
'tableName' => 'category_group',
|
||||
'tableAlias' => 'cg',
|
||||
'joinCondition' => '(cg.id_category = c.id_category)',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
'dependencyField' => 'nleft',
|
||||
],
|
||||
'sales' => [
|
||||
'tableName' => 'product_sale',
|
||||
'tableAlias' => 'psales',
|
||||
'fieldName' => 'quantity',
|
||||
'fieldAlias' => 'sales',
|
||||
'joinCondition' => '(psales.id_product = p.id_product)',
|
||||
'joinType' => self::LEFT_JOIN,
|
||||
],
|
||||
];
|
||||
|
||||
return $filterToTableMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the joined and escaped value from an multi-dimensional array
|
||||
*
|
||||
* @param string $separator
|
||||
* @param array $values
|
||||
*
|
||||
* @return string Escaped string value
|
||||
*/
|
||||
protected function getJoinedEscapedValue($separator, array $values)
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$values[$key] = $this->getJoinedEscapedValue($separator, $value);
|
||||
} elseif (is_numeric($value)) {
|
||||
$values[$key] = pSQL($value);
|
||||
} else {
|
||||
$values[$key] = "'" . pSQL($value) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
return implode($separator, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the orderby fields, adding the proper alias that will be added to the final query
|
||||
*
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function computeOrderByField(array $filterToTableMapping)
|
||||
{
|
||||
$orderField = $this->getOrderField();
|
||||
|
||||
if ($this->getInitialPopulation() !== null && !empty($orderField)) {
|
||||
$this->getInitialPopulation()->addSelectField($orderField);
|
||||
}
|
||||
|
||||
// do not try to process the orderField if it already has an alias, or if it's a group function
|
||||
if (empty($orderField) || strpos($orderField, '.') !== false
|
||||
|| strpos($orderField, '(') !== false) {
|
||||
return $orderField;
|
||||
}
|
||||
|
||||
if ($orderField === 'price') {
|
||||
$orderField = $this->getOrderDirection() === 'asc' ? 'price_min' : 'price_max';
|
||||
}
|
||||
|
||||
$orderField = $this->computeFieldName($orderField, $filterToTableMapping, true);
|
||||
|
||||
// put some products at the end of the list
|
||||
$orderField = $this->computeShowLast($orderField, $filterToTableMapping);
|
||||
|
||||
return $orderField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort product list: InStock, OOPS with qty 0, OutOfStock
|
||||
*
|
||||
* @param string $orderField
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function computeShowLast($orderField, $filterToTableMapping)
|
||||
{
|
||||
// allow only if feature is enabled & it is main product list query
|
||||
if ($this->getInitialPopulation() === null
|
||||
|| empty($orderField)
|
||||
|| !Configuration::get('PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST')
|
||||
) {
|
||||
return $orderField;
|
||||
}
|
||||
|
||||
$this->addSelectField('out_of_stock');
|
||||
|
||||
// order by out-of-stock last
|
||||
$computedQuantityField = $this->computeFieldName('quantity', $filterToTableMapping);
|
||||
$byOutOfStockLast = 'IFNULL(' . $computedQuantityField . ', 0) <= 0';
|
||||
|
||||
/**
|
||||
* Default behaviour when out of stock
|
||||
* 0 - when deny orders
|
||||
* 1 - when allow orders
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
$isAvailableWhenOutOfStock = (int) Product::isAvailableWhenOutOfStock(2);
|
||||
|
||||
// computing values for order by 'allow to order last'
|
||||
$computedField = $this->computeFieldName('out_of_stock', $filterToTableMapping);
|
||||
$computedValue = $isAvailableWhenOutOfStock ? 0 : 1;
|
||||
$computedDirection = $isAvailableWhenOutOfStock ? 'ASC' : 'DESC';
|
||||
|
||||
// query: products with zero or less quantity and not available to order go to the end
|
||||
$byOOPS = str_replace(
|
||||
[':byOutOfStockLast', ':field', ':value', ':direction'],
|
||||
[$byOutOfStockLast, $computedField, $computedValue, $computedDirection],
|
||||
':byOutOfStockLast AND FIELD(:field, :value) :direction'
|
||||
);
|
||||
|
||||
$orderField = $byOutOfStockLast . ', '
|
||||
. $byOOPS . ', '
|
||||
. $orderField;
|
||||
|
||||
return $orderField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add alias to table field name
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return string Table Field name with an alias
|
||||
*/
|
||||
protected function computeFieldName($fieldName, $filterToTableMapping, $sortByField = false)
|
||||
{
|
||||
if (array_key_exists($fieldName, $filterToTableMapping)
|
||||
&& (
|
||||
// If the requested order field is in the result, no need to change tableAlias
|
||||
// unless a fieldName key exists
|
||||
isset($filterToTableMapping[$fieldName]['fieldName'])
|
||||
|| $this->getInitialPopulation() === null
|
||||
|| !$this->getInitialPopulation()->getSelectFields()->contains($fieldName)
|
||||
)
|
||||
) {
|
||||
$joinMapping = $filterToTableMapping[$fieldName];
|
||||
$fieldName = $joinMapping['tableAlias'] . '.' . (isset($joinMapping['fieldName']) ? $joinMapping['fieldName'] : $fieldName);
|
||||
if ($sortByField === false) {
|
||||
$fieldName .= isset($joinMapping['fieldAlias']) ? ' as ' . $joinMapping['fieldAlias'] : '';
|
||||
}
|
||||
|
||||
if (isset($joinMapping['aggregateFunction'], $joinMapping['aggregateFieldName'])) {
|
||||
$fieldName = $joinMapping['aggregateFunction'] . '(' . $fieldName . ') as ' . $joinMapping['aggregateFieldName'];
|
||||
}
|
||||
} else {
|
||||
if (strpos($fieldName, '(') === false) {
|
||||
$fieldName = 'p.' . $fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
return $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the select fields, adding the proper alias that will be added to the final query
|
||||
*
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function computeSelectFields(array $filterToTableMapping)
|
||||
{
|
||||
$selectFields = [];
|
||||
foreach ($this->getSelectFields() as $key => $selectField) {
|
||||
$selectFields[] = $this->computeFieldName($selectField, $filterToTableMapping);
|
||||
}
|
||||
|
||||
return $selectFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computer the where conditions that will be added to the final query
|
||||
*
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function computeWhereConditions(array $filterToTableMapping)
|
||||
{
|
||||
$whereConditions = [];
|
||||
$operationIdx = 0;
|
||||
foreach ($this->getOperationsFilters() as $filterName => $filterOperations) {
|
||||
$operationsConditions = [];
|
||||
foreach ($filterOperations as $operations) {
|
||||
$conditions = [];
|
||||
foreach ($operations as $idx => $operation) {
|
||||
$selectAlias = 'p';
|
||||
$values = $operation[1];
|
||||
if (array_key_exists($operation[0], $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$operation[0]];
|
||||
// If index is not the first, append to the table alias for
|
||||
// multi join
|
||||
$selectAlias = $joinMapping['tableAlias'] .
|
||||
($operationIdx === 0 ? '' : '_' . $operationIdx) .
|
||||
($idx === 0 ? '' : '_' . $idx);
|
||||
$operation[0] = isset($joinMapping['fieldName']) ? $joinMapping['fieldName'] : $operation[0];
|
||||
}
|
||||
|
||||
if (count($values) === 1) {
|
||||
$operator = !empty($operation[2]) ? $operation[2] : '=';
|
||||
$conditions[] = $selectAlias . '.' . $operation[0] . $operator . current($values);
|
||||
} else {
|
||||
$conditions[] = $selectAlias . '.' . $operation[0] . ' IN (' . $this->getJoinedEscapedValue(', ', $values) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$operationsConditions[] = '(' . implode(' AND ', $conditions) . ')';
|
||||
}
|
||||
|
||||
++$operationIdx;
|
||||
if (!empty($operationsConditions)) {
|
||||
$whereConditions[] = '(' . implode(' OR ', $operationsConditions) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getFilters() as $filterName => $filterContent) {
|
||||
$selectAlias = 'p';
|
||||
if (array_key_exists($filterName, $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$filterName];
|
||||
$selectAlias = $joinMapping['tableAlias'];
|
||||
$filterName = isset($joinMapping['fieldName']) ? $joinMapping['fieldName'] : $filterName;
|
||||
}
|
||||
|
||||
foreach ($filterContent as $operator => $values) {
|
||||
if (count($values) == 1) {
|
||||
$values = current($values);
|
||||
|
||||
if ($operator === '=') {
|
||||
if (count($values) == 1) {
|
||||
$whereConditions[] =
|
||||
$selectAlias . '.' . $filterName . $operator . "'" . current($values) . "'";
|
||||
} else {
|
||||
$whereConditions[] =
|
||||
$selectAlias . '.' . $filterName . ' IN (' . $this->getJoinedEscapedValue(', ', $values) . ')';
|
||||
}
|
||||
} else {
|
||||
$orConditions = [];
|
||||
foreach ($values as $value) {
|
||||
$orConditions[] = $selectAlias . '.' . $filterName . $operator . $value;
|
||||
}
|
||||
$whereConditions[] = implode(' OR ', $orConditions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have several "groups" of the same filter, we need to use the intersect of the matching products
|
||||
// e.g. : mix of id_feature like Composition & Styles
|
||||
$idFilteredProducts = null;
|
||||
foreach ($this->getFilters() as $filterName => $filterContent) {
|
||||
foreach ($filterContent as $operator => $filterValues) {
|
||||
if (count($filterValues) <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$idTmpFilteredProducts = [];
|
||||
$mysqlAdapter = $this->getFilteredSearchAdapter();
|
||||
$mysqlAdapter->addSelectField('id_product');
|
||||
$mysqlAdapter->setLimit(null);
|
||||
$mysqlAdapter->setOrderField('');
|
||||
$mysqlAdapter->addFilter($filterName, $filterValues, $operator);
|
||||
$idProducts = $mysqlAdapter->execute();
|
||||
foreach ($idProducts as $idProduct) {
|
||||
$idTmpFilteredProducts[] = $idProduct['id_product'];
|
||||
}
|
||||
|
||||
if ($idFilteredProducts === null) {
|
||||
$idFilteredProducts = $idTmpFilteredProducts;
|
||||
} else {
|
||||
$idFilteredProducts += array_intersect($idFilteredProducts, $idTmpFilteredProducts);
|
||||
}
|
||||
|
||||
if (empty($idFilteredProducts)) {
|
||||
// set it to 0 to make sure no result will be returned
|
||||
$idFilteredProducts[] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$whereConditions[] = 'p.id_product IN (' . implode(', ', $idFilteredProducts) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $whereConditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the joinConditions needed depending on the fields required in select, where, groupby & orderby fields
|
||||
*
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
protected function computeJoinConditions(array $filterToTableMapping)
|
||||
{
|
||||
$joinList = new ArrayCollection();
|
||||
|
||||
$this->addJoinList($joinList, $this->getSelectFields(), $filterToTableMapping);
|
||||
$this->addJoinList($joinList, $this->getFilters()->getKeys(), $filterToTableMapping);
|
||||
|
||||
$operationIdx = 0;
|
||||
foreach ($this->getOperationsFilters() as $filterOperations) {
|
||||
foreach ($filterOperations as $operations) {
|
||||
foreach ($operations as $idx => $operation) {
|
||||
if (array_key_exists($operation[0], $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$operation[0]];
|
||||
if ($idx !== 0 || $operationIdx !== 0) {
|
||||
// Index is not the first, append index to tableAlias on joinCondition
|
||||
$joinMapping['joinCondition'] = preg_replace(
|
||||
'~([\(\s=]' . $joinMapping['tableAlias'] . ')\.~',
|
||||
'${1}' .
|
||||
($operationIdx === 0 ? '' : '_' . $operationIdx) .
|
||||
($idx === 0 ? '' : '_' . $idx) .
|
||||
'.',
|
||||
$joinMapping['joinCondition']
|
||||
);
|
||||
$joinMapping['tableAlias'] .= ($operationIdx === 0 ? '' : '_' . $operationIdx) .
|
||||
($idx === 0 ? '' : '_' . $idx);
|
||||
}
|
||||
|
||||
$this->addJoinConditions($joinList, $joinMapping, $filterToTableMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
++$operationIdx;
|
||||
}
|
||||
|
||||
$this->addJoinList($joinList, $this->getGroupFields()->getKeys(), $filterToTableMapping);
|
||||
|
||||
if (array_key_exists($this->getOrderField(), $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$this->getOrderField()];
|
||||
$this->addJoinConditions($joinList, $joinMapping, $filterToTableMapping);
|
||||
}
|
||||
|
||||
return $joinList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to add tables infos to the join list.
|
||||
*
|
||||
* @param ArrayCollection $joinList
|
||||
* @param array|ArrayCollection $list
|
||||
* @param array $filterToTableMapping
|
||||
*/
|
||||
private function addJoinList(ArrayCollection $joinList, $list, array $filterToTableMapping)
|
||||
{
|
||||
foreach ($list as $field) {
|
||||
if (array_key_exists($field, $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$field];
|
||||
$this->addJoinConditions($joinList, $joinMapping, $filterToTableMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the required table infos to the join list, taking care of the dependent tables
|
||||
*
|
||||
* @param ArrayCollection $joinList
|
||||
* @param array $joinMapping
|
||||
* @param array $filterToTableMapping
|
||||
*/
|
||||
private function addJoinConditions(ArrayCollection $joinList, array $joinMapping, array $filterToTableMapping)
|
||||
{
|
||||
if (array_key_exists('dependencyField', $joinMapping)) {
|
||||
$dependencyJoinMapping = $filterToTableMapping[$joinMapping['dependencyField']];
|
||||
$this->addJoinConditions($joinList, $dependencyJoinMapping, $filterToTableMapping);
|
||||
}
|
||||
$joinInfos[$joinMapping['tableAlias']] = [
|
||||
'tableName' => $joinMapping['tableName'],
|
||||
'joinCondition' => $joinMapping['joinCondition'],
|
||||
'joinType' => $joinMapping['joinType'],
|
||||
];
|
||||
|
||||
$joinList->set($joinMapping['tableAlias'] . '_' . $joinMapping['tableName'], $joinInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the groupby condition, adding the proper alias that will be added to the final query
|
||||
*
|
||||
* @param array $filterToTableMapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function computeGroupByFields(array $filterToTableMapping)
|
||||
{
|
||||
$groupFields = [];
|
||||
if ($this->getGroupFields()->isEmpty()) {
|
||||
return $groupFields;
|
||||
}
|
||||
|
||||
foreach ($this->getGroupFields() as $key => $values) {
|
||||
if (strpos($values, '.') !== false
|
||||
|| strpos($values, '(') !== false) {
|
||||
$groupFields[$key] = $values;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($values, $filterToTableMapping)) {
|
||||
$joinMapping = $filterToTableMapping[$values];
|
||||
$groupFields[$key] = $joinMapping['tableAlias'] . '.' . $values;
|
||||
} else {
|
||||
$groupFields[$key] = 'p.' . $values;
|
||||
}
|
||||
}
|
||||
|
||||
return $groupFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinMaxValue($fieldName)
|
||||
{
|
||||
$mysqlAdapter = $this->getFilteredSearchAdapter();
|
||||
$mysqlAdapter->copyFilters($this);
|
||||
$mysqlAdapter->setSelectFields(['MIN(' . $fieldName . ') as min, MAX(' . $fieldName . ') as max']);
|
||||
$mysqlAdapter->setLimit(null);
|
||||
$mysqlAdapter->setOrderField('');
|
||||
|
||||
$result = $mysqlAdapter->execute();
|
||||
|
||||
return [(float) $result[0]['min'], (float) $result[0]['max']];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
$mysqlAdapter = $this->getFilteredSearchAdapter();
|
||||
$mysqlAdapter->copyFilters($this);
|
||||
|
||||
$result = $mysqlAdapter->valueCount();
|
||||
|
||||
return isset($result[0]['c']) ? (int) $result[0]['c'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valueCount($fieldName = null)
|
||||
{
|
||||
$this->resetGroupBy();
|
||||
if ($fieldName !== null) {
|
||||
$this->addGroupBy($fieldName);
|
||||
$this->addSelectField($fieldName);
|
||||
}
|
||||
|
||||
$this->addSelectField('COUNT(DISTINCT p.id_product) c');
|
||||
$this->setLimit(null);
|
||||
$this->setOrderField('');
|
||||
|
||||
$this->copyOperationsFilters();
|
||||
|
||||
return $this->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function useFiltersAsInitialPopulation()
|
||||
{
|
||||
$this->setLimit(null);
|
||||
$this->setOrderField('');
|
||||
$this->setSelectFields(
|
||||
[
|
||||
'id_product',
|
||||
'id_manufacturer',
|
||||
'quantity',
|
||||
'condition',
|
||||
'weight',
|
||||
'price',
|
||||
'sales',
|
||||
]
|
||||
);
|
||||
$this->initialPopulation = clone $this;
|
||||
$this->resetAll();
|
||||
$this->addSelectField('id_product');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Context
|
||||
*/
|
||||
protected function getContext()
|
||||
{
|
||||
return Context::getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Db
|
||||
*/
|
||||
protected function getDatabase()
|
||||
{
|
||||
return Db::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy stock management operation filters
|
||||
* to make sure quantity is also used
|
||||
*/
|
||||
protected function copyOperationsFilters()
|
||||
{
|
||||
$initialPopulation = $this->getInitialPopulation();
|
||||
if (null === $initialPopulation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$operationsFilters = clone $initialPopulation->getOperationsFilters();
|
||||
foreach ($operationsFilters as $operationName => $operations) {
|
||||
$this->addOperationsFilter(
|
||||
$operationName,
|
||||
$operations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
modules/ps_facetedsearch/src/Constraint/UrlSegment.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class UrlSegment extends Constraint
|
||||
{
|
||||
public $message = '%s is invalid.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatedBy()
|
||||
{
|
||||
return UrlSegmentValidator::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Constraint;
|
||||
|
||||
use PrestaShop\PrestaShop\Adapter\Tools;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Class UrlSegmentValidator responsible for validating an URL segment.
|
||||
*/
|
||||
class UrlSegmentValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var Tools
|
||||
*/
|
||||
private $tools;
|
||||
|
||||
/**
|
||||
* @param Tools $tools
|
||||
*/
|
||||
public function __construct(Tools $tools)
|
||||
{
|
||||
$this->tools = $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof UrlSegment) {
|
||||
throw new UnexpectedTypeException($constraint, UrlSegment::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strtolower($value) !== $this->tools->linkRewrite($value)) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setTranslationDomain('Admin.Notifications.Error')
|
||||
->setParameter('%s', $this->formatValue($value))
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
982
modules/ps_facetedsearch/src/Filters/Block.php
Normal file
@@ -0,0 +1,982 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Filters;
|
||||
|
||||
use Category;
|
||||
use Configuration;
|
||||
use Context;
|
||||
use Db;
|
||||
use Feature;
|
||||
use Group;
|
||||
use Manufacturer;
|
||||
use PrestaShop\Module\FacetedSearch\Adapter\InterfaceAdapter;
|
||||
use PrestaShop\Module\FacetedSearch\Product\Search;
|
||||
use PrestaShop\PrestaShop\Core\Localization\Locale;
|
||||
use PrestaShop\PrestaShop\Core\Localization\Specification\NumberSymbolList;
|
||||
use PrestaShopDatabaseException;
|
||||
use Tools;
|
||||
|
||||
/**
|
||||
* Display filters block on navigation
|
||||
*/
|
||||
class Block
|
||||
{
|
||||
/**
|
||||
* @var InterfaceAdapter
|
||||
*/
|
||||
private $searchAdapter;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $psStockManagement;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $psOrderOutOfStock;
|
||||
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var Db
|
||||
*/
|
||||
private $database;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributesGroup;
|
||||
|
||||
/**
|
||||
* @var DataAccessor
|
||||
*/
|
||||
private $dataAccessor;
|
||||
|
||||
public function __construct(InterfaceAdapter $searchAdapter, Context $context, Db $database, DataAccessor $dataAccessor)
|
||||
{
|
||||
$this->searchAdapter = $searchAdapter;
|
||||
$this->context = $context;
|
||||
$this->database = $database;
|
||||
$this->dataAccessor = $dataAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $nbProducts
|
||||
* @param array $selectedFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilterBlock(
|
||||
$nbProducts,
|
||||
$selectedFilters
|
||||
) {
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$idShop = (int) $this->context->shop->id;
|
||||
$idParent = (int) Tools::getValue(
|
||||
'id_category',
|
||||
Tools::getValue('id_category_layered', Configuration::get('PS_HOME_CATEGORY'))
|
||||
);
|
||||
|
||||
/* Get the filters for the current category */
|
||||
$filters = $this->database->executeS(
|
||||
'SELECT type, id_value, filter_show_limit, filter_type ' .
|
||||
'FROM ' . _DB_PREFIX_ . 'layered_category ' .
|
||||
'WHERE id_category = ' . $idParent . ' ' .
|
||||
'AND id_shop = ' . $idShop . ' ' .
|
||||
'GROUP BY `type`, id_value ORDER BY position ASC'
|
||||
);
|
||||
|
||||
$filterBlocks = [];
|
||||
// iterate through each filter, and the get corresponding filter block
|
||||
foreach ($filters as $filter) {
|
||||
switch ($filter['type']) {
|
||||
case 'price':
|
||||
$filterBlocks[] = $this->getPriceRangeBlock($filter, $selectedFilters, $nbProducts);
|
||||
break;
|
||||
case 'weight':
|
||||
$filterBlocks[] = $this->getWeightRangeBlock($filter, $selectedFilters, $nbProducts);
|
||||
break;
|
||||
case 'condition':
|
||||
$filterBlocks[] = $this->getConditionsBlock($filter, $selectedFilters);
|
||||
break;
|
||||
case 'quantity':
|
||||
$filterBlocks[] = $this->getQuantitiesBlock($filter, $selectedFilters);
|
||||
break;
|
||||
case 'manufacturer':
|
||||
$filterBlocks[] = $this->getManufacturersBlock($filter, $selectedFilters, $idLang);
|
||||
break;
|
||||
case 'id_attribute_group':
|
||||
$filterBlocks =
|
||||
array_merge($filterBlocks, $this->getAttributesBlock($filter, $selectedFilters, $idLang));
|
||||
break;
|
||||
case 'id_feature':
|
||||
$filterBlocks =
|
||||
array_merge($filterBlocks, $this->getFeaturesBlock($filter, $selectedFilters, $idLang));
|
||||
break;
|
||||
case 'category':
|
||||
$parent = new Category($idParent, $idLang);
|
||||
$filterBlocks[] = $this->getCategoriesBlock($filter, $selectedFilters, $idLang, $parent);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'filters' => $filterBlocks,
|
||||
];
|
||||
}
|
||||
|
||||
protected function showPriceFilter()
|
||||
{
|
||||
return Group::getCurrent()->show_prices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter block from the cache table
|
||||
*
|
||||
* @param string $filterHash
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getFromCache($filterHash)
|
||||
{
|
||||
if (!Configuration::get('PS_LAYERED_CACHE_ENABLED')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $this->database->getRow(
|
||||
'SELECT data FROM ' . _DB_PREFIX_ . 'layered_filter_block WHERE hash="' . pSQL($filterHash) . '"'
|
||||
);
|
||||
|
||||
if (!empty($row)) {
|
||||
return unserialize(current($row));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the filter block into the cache table
|
||||
*
|
||||
* @param string $filterHash
|
||||
* @param array $data
|
||||
*/
|
||||
public function insertIntoCache($filterHash, $data)
|
||||
{
|
||||
if (!Configuration::get('PS_LAYERED_CACHE_ENABLED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->database->execute(
|
||||
'REPLACE INTO ' . _DB_PREFIX_ . 'layered_filter_block (hash, data) ' .
|
||||
'VALUES ("' . $filterHash . '", "' . pSQL(serialize($data)) . '")'
|
||||
);
|
||||
} catch (PrestaShopDatabaseException $e) {
|
||||
// Don't worry if the cache have invalid or duplicate hash
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $nbProducts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getPriceRangeBlock($filter, $selectedFilters, $nbProducts)
|
||||
{
|
||||
if (!$this->showPriceFilter()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$priceSpecifications = $this->preparePriceSpecifications();
|
||||
$priceBlock = [
|
||||
'type_lite' => 'price',
|
||||
'type' => 'price',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Price', [], 'Modules.Facetedsearch.Shop'),
|
||||
'max' => '0',
|
||||
'min' => null,
|
||||
'unit' => $this->context->currency->sign,
|
||||
'specifications' => $priceSpecifications,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => Converter::WIDGET_TYPE_SLIDER,
|
||||
'nbr' => $nbProducts,
|
||||
];
|
||||
|
||||
list($priceMinFilter, $priceMaxFilter, $weightFilter) = $this->ignorePriceAndWeightFilters(
|
||||
$this->searchAdapter->getInitialPopulation()
|
||||
);
|
||||
|
||||
list($priceBlock['min'], $priceBlock['max']) = $this->searchAdapter->getInitialPopulation()->getMinMaxPriceValue();
|
||||
$priceBlock['value'] = !empty($selectedFilters['price']) ? $selectedFilters['price'] : null;
|
||||
|
||||
$this->restorePriceAndWeightFilters(
|
||||
$this->searchAdapter->getInitialPopulation(),
|
||||
$priceMinFilter,
|
||||
$priceMaxFilter,
|
||||
$weightFilter
|
||||
);
|
||||
|
||||
return $priceBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price / weight filter block should not apply their own filters
|
||||
* otherwise they will always disappear if we filter on price / weight
|
||||
* because only one choice will remain
|
||||
*
|
||||
* @param InterfaceAdapter $filteredSearchAdapter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function ignorePriceAndWeightFilters(InterfaceAdapter $filteredSearchAdapter)
|
||||
{
|
||||
// disable the current price and weight filters to compute ranges
|
||||
$priceMinFilter = $filteredSearchAdapter->getFilter('price_min');
|
||||
$priceMaxFilter = $filteredSearchAdapter->getFilter('price_max');
|
||||
$weightFilter = $filteredSearchAdapter->getFilter('weight');
|
||||
$filteredSearchAdapter->resetFilter('price_min');
|
||||
$filteredSearchAdapter->resetFilter('price_max');
|
||||
$filteredSearchAdapter->resetFilter('weight');
|
||||
|
||||
return [
|
||||
$priceMinFilter,
|
||||
$priceMaxFilter,
|
||||
$weightFilter,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore price and weight filters
|
||||
*
|
||||
* @param InterfaceAdapter $filteredSearchAdapter
|
||||
* @param int $priceMinFilter
|
||||
* @param int $priceMaxFilter
|
||||
* @param int $weightFilter
|
||||
*/
|
||||
private function restorePriceAndWeightFilters(
|
||||
$filteredSearchAdapter,
|
||||
$priceMinFilter,
|
||||
$priceMaxFilter,
|
||||
$weightFilter
|
||||
) {
|
||||
// put back the price and weight filters
|
||||
$filteredSearchAdapter->setFilter('price_min', $priceMinFilter);
|
||||
$filteredSearchAdapter->setFilter('price_max', $priceMaxFilter);
|
||||
$filteredSearchAdapter->setFilter('weight', $weightFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weight filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $nbProducts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getWeightRangeBlock($filter, $selectedFilters, $nbProducts)
|
||||
{
|
||||
$weightBlock = [
|
||||
'type_lite' => 'weight',
|
||||
'type' => 'weight',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Weight', [], 'Modules.Facetedsearch.Shop'),
|
||||
'max' => '0',
|
||||
'min' => null,
|
||||
'unit' => Configuration::get('PS_WEIGHT_UNIT'),
|
||||
'specifications' => null,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => Converter::WIDGET_TYPE_SLIDER,
|
||||
'value' => null,
|
||||
'nbr' => $nbProducts,
|
||||
];
|
||||
|
||||
list($priceMinFilter, $priceMaxFilter, $weightFilter) = $this->ignorePriceAndWeightFilters(
|
||||
$this->searchAdapter->getInitialPopulation()
|
||||
);
|
||||
|
||||
list($weightBlock['min'], $weightBlock['max']) = $this->searchAdapter->getInitialPopulation()->getMinMaxValue('p.weight');
|
||||
if (empty($weightBlock['min']) && empty($weightBlock['max'])) {
|
||||
// We don't need to continue, no filter available
|
||||
return [];
|
||||
}
|
||||
|
||||
$weightBlock['value'] = !empty($selectedFilters['weight']) ? $selectedFilters['weight'] : null;
|
||||
|
||||
$this->restorePriceAndWeightFilters(
|
||||
$this->searchAdapter->getInitialPopulation(),
|
||||
$priceMinFilter,
|
||||
$priceMaxFilter,
|
||||
$weightFilter
|
||||
);
|
||||
|
||||
return $weightBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the condition filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConditionsBlock($filter, $selectedFilters)
|
||||
{
|
||||
$conditionArray = [
|
||||
'new' => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'New',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
'used' => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'Used',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
'refurbished' => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'Refurbished',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
];
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('condition');
|
||||
$results = $filteredSearchAdapter->valueCount('condition');
|
||||
foreach ($results as $key => $values) {
|
||||
$condition = $values['condition'];
|
||||
$count = $values['c'];
|
||||
|
||||
$conditionArray[$condition]['nbr'] = $count;
|
||||
if (isset($selectedFilters['condition'])
|
||||
&& in_array($condition, $selectedFilters['condition'])
|
||||
) {
|
||||
$conditionArray[$condition]['checked'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$conditionBlock = [
|
||||
'type_lite' => 'condition',
|
||||
'type' => 'condition',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Condition', [], 'Modules.Facetedsearch.Shop'),
|
||||
'values' => $conditionArray,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
|
||||
return $conditionBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quantities filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getQuantitiesBlock($filter, $selectedFilters)
|
||||
{
|
||||
if ($this->psStockManagement === null) {
|
||||
$this->psStockManagement = (bool) Configuration::get('PS_STOCK_MANAGEMENT');
|
||||
}
|
||||
|
||||
if ($this->psOrderOutOfStock === null) {
|
||||
$this->psOrderOutOfStock = (bool) Configuration::get('PS_ORDER_OUT_OF_STOCK');
|
||||
}
|
||||
|
||||
// We only initialize the options if stock management is activated
|
||||
$availabilityOptions = [];
|
||||
if ($this->psStockManagement) {
|
||||
$availabilityOptions = [
|
||||
0 => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'Not available',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
1 => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'Available',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
2 => [
|
||||
'name' => $this->context->getTranslator()->trans(
|
||||
'In stock',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
),
|
||||
'nbr' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(Search::STOCK_MANAGEMENT_FILTER);
|
||||
|
||||
// Products without quantity in stock, with out-of-stock ordering disabled
|
||||
$filteredSearchAdapter->addOperationsFilter(
|
||||
Search::STOCK_MANAGEMENT_FILTER,
|
||||
[
|
||||
[
|
||||
['quantity', [0], '<='],
|
||||
['out_of_stock', !$this->psOrderOutOfStock ? [0, 2] : [0], '='],
|
||||
],
|
||||
]
|
||||
);
|
||||
$availabilityOptions[0]['nbr'] = $filteredSearchAdapter->count();
|
||||
|
||||
// Products in stock, or with out-of-stock ordering enabled
|
||||
$filteredSearchAdapter->addOperationsFilter(
|
||||
Search::STOCK_MANAGEMENT_FILTER,
|
||||
[
|
||||
[
|
||||
['out_of_stock', $this->psOrderOutOfStock ? [1, 2] : [1], '='],
|
||||
],
|
||||
[
|
||||
['quantity', [0], '>'],
|
||||
],
|
||||
]
|
||||
);
|
||||
$availabilityOptions[1]['nbr'] = $filteredSearchAdapter->count();
|
||||
|
||||
// Products in stock
|
||||
$filteredSearchAdapter->addOperationsFilter(
|
||||
Search::STOCK_MANAGEMENT_FILTER,
|
||||
[
|
||||
[
|
||||
['quantity', [0], '>'],
|
||||
],
|
||||
]
|
||||
);
|
||||
$availabilityOptions[2]['nbr'] = $filteredSearchAdapter->count();
|
||||
|
||||
// If some filter was selected, we want to show only this single filter, it does not make sense to show others
|
||||
if (isset($selectedFilters['quantity'])) {
|
||||
// We loop through selected filters and assign it to our options and remove the rest
|
||||
foreach ($availabilityOptions as $key => $values) {
|
||||
if (in_array($key, $selectedFilters['quantity'], true)) {
|
||||
$availabilityOptions[$key]['checked'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$quantityBlock = [
|
||||
'type_lite' => 'quantity',
|
||||
'type' => 'quantity',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Availability', [], 'Modules.Facetedsearch.Shop'),
|
||||
'values' => $availabilityOptions,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
|
||||
return $quantityBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the manufacturers filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getManufacturersBlock($filter, $selectedFilters, $idLang)
|
||||
{
|
||||
$manufacturersArray = $manufacturers = [];
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('id_manufacturer');
|
||||
|
||||
$tempManufacturers = Manufacturer::getManufacturers(false, $idLang);
|
||||
if (empty($tempManufacturers)) {
|
||||
return $manufacturersArray;
|
||||
}
|
||||
|
||||
foreach ($tempManufacturers as $key => $manufacturer) {
|
||||
$manufacturers[$manufacturer['id_manufacturer']] = $manufacturer;
|
||||
}
|
||||
|
||||
$results = $filteredSearchAdapter->valueCount('id_manufacturer');
|
||||
foreach ($results as $key => $values) {
|
||||
if (!isset($values['id_manufacturer'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id_manufacturer = $values['id_manufacturer'];
|
||||
if (empty($manufacturers[$id_manufacturer]['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = $values['c'];
|
||||
$manufacturersArray[$id_manufacturer] = [
|
||||
'name' => $manufacturers[$id_manufacturer]['name'],
|
||||
'nbr' => $count,
|
||||
];
|
||||
|
||||
if (isset($selectedFilters['manufacturer'])
|
||||
&& in_array($id_manufacturer, $selectedFilters['manufacturer'])
|
||||
) {
|
||||
$manufacturersArray[$id_manufacturer]['checked'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$manufacturerBlock = [
|
||||
'type_lite' => 'manufacturer',
|
||||
'type' => 'manufacturer',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Brand', [], 'Modules.Facetedsearch.Shop'),
|
||||
'values' => $manufacturersArray,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
|
||||
return $manufacturerBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAttributesBlock($filter, $selectedFilters, $idLang)
|
||||
{
|
||||
$attributesBlock = [];
|
||||
$filteredSearchAdapter = null;
|
||||
$idAttributeGroup = $filter['id_value'];
|
||||
|
||||
if (!empty($selectedFilters['id_attribute_group'])) {
|
||||
foreach ($selectedFilters['id_attribute_group'] as $key => $selectedFilter) {
|
||||
if ($key == $idAttributeGroup) {
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('with_attributes_' . $idAttributeGroup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$filteredSearchAdapter) {
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter();
|
||||
}
|
||||
|
||||
$attributesGroup = $this->dataAccessor->getAttributesGroups($idLang);
|
||||
if ($attributesGroup === []) {
|
||||
return $attributesBlock;
|
||||
}
|
||||
|
||||
$attributes = $this->dataAccessor->getAttributes($idLang, $idAttributeGroup);
|
||||
|
||||
$filteredSearchAdapter->addOperationsFilter(
|
||||
'id_attribute_group_' . $idAttributeGroup,
|
||||
[[['id_attribute_group', [(int) $idAttributeGroup]]]]
|
||||
);
|
||||
$results = $filteredSearchAdapter->valueCount('id_attribute');
|
||||
foreach ($results as $key => $values) {
|
||||
$idAttribute = $values['id_attribute'];
|
||||
if (!isset($attributes[$idAttribute])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = $values['c'];
|
||||
$attribute = $attributes[$idAttribute];
|
||||
$idAttributeGroup = $attribute['id_attribute_group'];
|
||||
if (!isset($attributesBlock[$idAttributeGroup])) {
|
||||
$attributeGroup = $attributesGroup[$idAttributeGroup];
|
||||
|
||||
$attributesBlock[$idAttributeGroup] = [
|
||||
'type_lite' => 'id_attribute_group',
|
||||
'type' => 'id_attribute_group',
|
||||
'id_key' => $idAttributeGroup,
|
||||
'name' => $attributeGroup['attribute_group_name'],
|
||||
'is_color_group' => (bool) $attributeGroup['is_color_group'],
|
||||
'values' => [],
|
||||
'url_name' => $attributeGroup['url_name'],
|
||||
'meta_title' => $attributeGroup['meta_title'],
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
}
|
||||
|
||||
$attributesBlock[$idAttributeGroup]['values'][$idAttribute] = [
|
||||
'name' => $attribute['name'],
|
||||
'nbr' => $count,
|
||||
'url_name' => $attribute['url_name'],
|
||||
'meta_title' => $attribute['meta_title'],
|
||||
];
|
||||
|
||||
if ($attributesBlock[$idAttributeGroup]['is_color_group'] !== false) {
|
||||
$attributesBlock[$idAttributeGroup]['values'][$idAttribute]['color'] = $attribute['color'];
|
||||
}
|
||||
|
||||
if (array_key_exists('id_attribute_group', $selectedFilters)) {
|
||||
foreach ($selectedFilters['id_attribute_group'] as $selectedAttribute) {
|
||||
if (in_array($idAttribute, $selectedAttribute)) {
|
||||
$attributesBlock[$idAttributeGroup]['values'][$idAttribute]['checked'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attributesBlock as $idAttributeGroup => $value) {
|
||||
$attributesBlock[$idAttributeGroup]['values'] = $this->sortByKey($attributes, $value['values']);
|
||||
}
|
||||
|
||||
$attributesBlock = $this->sortByKey($attributesGroup, $attributesBlock);
|
||||
|
||||
return $attributesBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array using the same key order than the sortedReferenceArray
|
||||
*
|
||||
* @param array $sortedReferenceArray
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function sortByKey(array $sortedReferenceArray, $array)
|
||||
{
|
||||
$sortedArray = [];
|
||||
|
||||
// iterate in the original order
|
||||
foreach ($sortedReferenceArray as $key => $value) {
|
||||
if (array_key_exists($key, $array)) {
|
||||
$sortedArray[$key] = $array[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $sortedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the features filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getFeaturesBlock($filter, $selectedFilters, $idLang)
|
||||
{
|
||||
$features = $featureBlock = [];
|
||||
$idFeature = $filter['id_value'];
|
||||
$filteredSearchAdapter = null;
|
||||
|
||||
if (!empty($selectedFilters['id_feature'])) {
|
||||
foreach ($selectedFilters['id_feature'] as $key => $selectedFilter) {
|
||||
if ($key == $idFeature) {
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('with_features_' . $idFeature);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$filteredSearchAdapter) {
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter();
|
||||
}
|
||||
|
||||
$tempFeatures = $this->dataAccessor->getFeatures($idLang);
|
||||
if (empty($tempFeatures)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($tempFeatures as $key => $feature) {
|
||||
$features[$feature['id_feature']] = $feature;
|
||||
}
|
||||
|
||||
$filteredSearchAdapter->addOperationsFilter(
|
||||
'id_feature_' . $idFeature,
|
||||
[[['id_feature', [(int) $idFeature]]]]
|
||||
);
|
||||
|
||||
$filteredSearchAdapter->addSelectField('id_feature');
|
||||
$results = $filteredSearchAdapter->valueCount('id_feature_value');
|
||||
foreach ($results as $key => $values) {
|
||||
$idFeatureValue = $values['id_feature_value'];
|
||||
$idFeature = $values['id_feature'];
|
||||
$count = $values['c'];
|
||||
|
||||
$feature = $features[$idFeature];
|
||||
|
||||
if (!isset($featureBlock[$idFeature])) {
|
||||
$tempFeatureValues = $this->dataAccessor->getFeatureValues($idFeature, $idLang);
|
||||
foreach ($tempFeatureValues as $featureValueKey => $featureValue) {
|
||||
$features[$idFeature]['featureValues'][$featureValue['id_feature_value']] = $featureValue;
|
||||
}
|
||||
|
||||
$featureBlock[$idFeature] = [
|
||||
'type_lite' => 'id_feature',
|
||||
'type' => 'id_feature',
|
||||
'id_key' => $idFeature,
|
||||
'values' => [],
|
||||
'name' => $feature['name'],
|
||||
'url_name' => $feature['url_name'],
|
||||
'meta_title' => $feature['meta_title'],
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
}
|
||||
|
||||
$featureValues = $features[$idFeature]['featureValues'];
|
||||
if (!isset($featureValues[$idFeatureValue]['value'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$featureBlock[$idFeature]['values'][$idFeatureValue] = [
|
||||
'nbr' => $count,
|
||||
'name' => $featureValues[$idFeatureValue]['value'],
|
||||
'url_name' => $featureValues[$idFeatureValue]['url_name'],
|
||||
'meta_title' => $featureValues[$idFeatureValue]['meta_title'],
|
||||
];
|
||||
|
||||
if (array_key_exists('id_feature', $selectedFilters)) {
|
||||
foreach ($selectedFilters['id_feature'] as $selectedFeature) {
|
||||
if (in_array($idFeatureValue, $selectedFeature)) {
|
||||
$featureBlock[$feature['id_feature']]['values'][$idFeatureValue]['checked'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$featureBlock = $this->sortFeatureBlock($featureBlock);
|
||||
|
||||
return $featureBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural sort multi-dimensional feature array
|
||||
*
|
||||
* @param array $featureBlock
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function sortFeatureBlock($featureBlock)
|
||||
{
|
||||
//Natural sort
|
||||
foreach ($featureBlock as $key => $value) {
|
||||
$temp = [];
|
||||
foreach ($featureBlock[$key]['values'] as $idFeatureValue => $featureValueInfos) {
|
||||
$temp[$idFeatureValue] = $featureValueInfos['name'];
|
||||
}
|
||||
|
||||
natcasesort($temp);
|
||||
$temp2 = [];
|
||||
|
||||
foreach ($temp as $keytemp => $valuetemp) {
|
||||
$temp2[$keytemp] = $featureBlock[$key]['values'][$keytemp];
|
||||
}
|
||||
|
||||
$featureBlock[$key]['values'] = $temp2;
|
||||
}
|
||||
|
||||
return $featureBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the categories filter condition based on the parent and config variables
|
||||
*
|
||||
* @param InterfaceAdapter $filteredSearchAdapter
|
||||
* @param Category $parent
|
||||
*/
|
||||
private function addCategoriesBlockFilters(InterfaceAdapter $filteredSearchAdapter, $parent)
|
||||
{
|
||||
if (Group::isFeatureActive()) {
|
||||
$userGroups = ($this->context->customer->isLogged() ? $this->context->customer->getGroups() : [
|
||||
Configuration::get(
|
||||
'PS_UNIDENTIFIED_GROUP'
|
||||
),
|
||||
]);
|
||||
|
||||
$filteredSearchAdapter->addFilter('id_group', $userGroups);
|
||||
}
|
||||
|
||||
$depth = (int) Configuration::get('PS_LAYERED_FILTER_CATEGORY_DEPTH', null, null, null, 1);
|
||||
|
||||
if ($depth) {
|
||||
$levelDepth = $parent->level_depth;
|
||||
$filteredSearchAdapter->addFilter('level_depth', [$depth + $levelDepth], '<=');
|
||||
}
|
||||
|
||||
$filteredSearchAdapter->addFilter('nleft', [$parent->nleft], '>');
|
||||
$filteredSearchAdapter->addFilter('nright', [$parent->nright], '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the categories filter block
|
||||
*
|
||||
* @param array $filter
|
||||
* @param array $selectedFilters
|
||||
* @param int $idLang
|
||||
* @param Category $parent
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCategoriesBlock($filter, $selectedFilters, $idLang, $parent)
|
||||
{
|
||||
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('id_category');
|
||||
$this->addCategoriesBlockFilters($filteredSearchAdapter, $parent);
|
||||
|
||||
$categoryArray = [];
|
||||
$categories = Category::getAllCategoriesName(
|
||||
null,
|
||||
$idLang,
|
||||
true,
|
||||
null,
|
||||
true,
|
||||
'',
|
||||
'ORDER BY c.nleft, c.position'
|
||||
);
|
||||
foreach ($categories as $key => $value) {
|
||||
$categories[$value['id_category']] = $value;
|
||||
}
|
||||
|
||||
$results = $filteredSearchAdapter->valueCount('id_category');
|
||||
|
||||
foreach ($results as $key => $values) {
|
||||
$idCategory = $values['id_category'];
|
||||
if (!isset($categories[$idCategory])) {
|
||||
// Category can sometimes not be found in case of multistore
|
||||
// plus waiting for indexation
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = $values['c'];
|
||||
$categoryArray[$idCategory] = [
|
||||
'name' => $categories[$idCategory]['name'],
|
||||
'nbr' => $count,
|
||||
];
|
||||
|
||||
if (isset($selectedFilters['category']) && in_array($idCategory, $selectedFilters['category'])) {
|
||||
$categoryArray[$idCategory]['checked'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$categoryBlock = [
|
||||
'type_lite' => 'category',
|
||||
'type' => 'category',
|
||||
'id_key' => 0,
|
||||
'name' => $this->context->getTranslator()->trans('Categories', [], 'Modules.Facetedsearch.Shop'),
|
||||
'values' => $categoryArray,
|
||||
'filter_show_limit' => (int) $filter['filter_show_limit'],
|
||||
'filter_type' => $filter['filter_type'],
|
||||
];
|
||||
|
||||
return $categoryBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare price specifications to display cldr prices.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function preparePriceSpecifications()
|
||||
{
|
||||
/* @var Currency */
|
||||
$currency = $this->context->currency;
|
||||
// New method since PS 1.7.6
|
||||
if (isset($this->context->currentLocale) && method_exists($this->context->currentLocale, 'getPriceSpecification')) {
|
||||
/* @var PriceSpecification */
|
||||
$priceSpecification = $this->context->currentLocale->getPriceSpecification($currency->iso_code);
|
||||
/* @var NumberSymbolList */
|
||||
$symbolList = $priceSpecification->getSymbolsByNumberingSystem(Locale::NUMBERING_SYSTEM_LATIN);
|
||||
|
||||
$symbol = [
|
||||
$symbolList->getDecimal(),
|
||||
$symbolList->getGroup(),
|
||||
$symbolList->getList(),
|
||||
$symbolList->getPercentSign(),
|
||||
$symbolList->getMinusSign(),
|
||||
$symbolList->getPlusSign(),
|
||||
$symbolList->getExponential(),
|
||||
$symbolList->getSuperscriptingExponent(),
|
||||
$symbolList->getPerMille(),
|
||||
$symbolList->getInfinity(),
|
||||
$symbolList->getNaN(),
|
||||
];
|
||||
|
||||
return array_merge(
|
||||
['symbol' => $symbol],
|
||||
$priceSpecification->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
// Default symbol configuration
|
||||
$symbol = [
|
||||
'.',
|
||||
',',
|
||||
';',
|
||||
'%',
|
||||
'-',
|
||||
'+',
|
||||
'E',
|
||||
'×',
|
||||
'‰',
|
||||
'∞',
|
||||
'NaN',
|
||||
];
|
||||
// The property `$precision` exists only from PS 1.7.6. On previous versions, all prices have 2 decimals
|
||||
$precision = isset($currency->precision) ? $currency->precision : 2;
|
||||
$formats = explode(';', $currency->format);
|
||||
if (count($formats) > 1) {
|
||||
$positivePattern = $formats[0];
|
||||
$negativePattern = $formats[1];
|
||||
} else {
|
||||
$positivePattern = $currency->format;
|
||||
$negativePattern = $currency->format;
|
||||
}
|
||||
|
||||
return [
|
||||
'positivePattern' => $positivePattern,
|
||||
'negativePattern' => $negativePattern,
|
||||
'symbol' => $symbol,
|
||||
'maxFractionDigits' => $precision,
|
||||
'minFractionDigits' => $precision,
|
||||
'groupingUsed' => true,
|
||||
'primaryGroupSize' => 3,
|
||||
'secondaryGroupSize' => 3,
|
||||
'currencyCode' => $currency->iso_code,
|
||||
'currencySymbol' => $currency->sign,
|
||||
];
|
||||
}
|
||||
}
|
||||
517
modules/ps_facetedsearch/src/Filters/Converter.php
Normal file
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Filters;
|
||||
|
||||
use Category;
|
||||
use Configuration;
|
||||
use Context;
|
||||
use Db;
|
||||
use Manufacturer;
|
||||
use PrestaShop\Module\FacetedSearch\Filters;
|
||||
use PrestaShop\Module\FacetedSearch\URLSerializer;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
|
||||
use Tools;
|
||||
|
||||
class Converter
|
||||
{
|
||||
const WIDGET_TYPE_CHECKBOX = 0;
|
||||
const WIDGET_TYPE_RADIO = 1;
|
||||
const WIDGET_TYPE_DROPDOWN = 2;
|
||||
const WIDGET_TYPE_SLIDER = 3;
|
||||
|
||||
const TYPE_ATTRIBUTE_GROUP = 'id_attribute_group';
|
||||
const TYPE_AVAILABILITY = 'availability';
|
||||
const TYPE_CATEGORY = 'category';
|
||||
const TYPE_CONDITION = 'condition';
|
||||
const TYPE_FEATURE = 'id_feature';
|
||||
const TYPE_QUANTITY = 'quantity';
|
||||
const TYPE_MANUFACTURER = 'manufacturer';
|
||||
const TYPE_PRICE = 'price';
|
||||
const TYPE_WEIGHT = 'weight';
|
||||
|
||||
const PROPERTY_URL_NAME = 'url_name';
|
||||
const PROPERTY_COLOR = 'color';
|
||||
const PROPERTY_TEXTURE = 'texture';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
const RANGE_FILTERS = [self::TYPE_PRICE, self::TYPE_WEIGHT];
|
||||
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var Db
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* @var URLSerializer
|
||||
*/
|
||||
protected $urlSerializer;
|
||||
|
||||
/**
|
||||
* @var Filters\DataAccessor
|
||||
*/
|
||||
private $dataAccessor;
|
||||
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Db $database,
|
||||
URLSerializer $urlSerializer,
|
||||
Filters\DataAccessor $dataAccessor
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->database = $database;
|
||||
$this->urlSerializer = $urlSerializer;
|
||||
$this->dataAccessor = $dataAccessor;
|
||||
}
|
||||
|
||||
public function getFacetsFromFilterBlocks(array $filterBlocks)
|
||||
{
|
||||
$facets = [];
|
||||
|
||||
foreach ($filterBlocks as $filterBlock) {
|
||||
if (empty($filterBlock)) {
|
||||
// Empty filter, let's continue
|
||||
continue;
|
||||
}
|
||||
|
||||
$facet = new Facet();
|
||||
$facet
|
||||
->setLabel($filterBlock['name'])
|
||||
->setProperty('filter_show_limit', $filterBlock['filter_show_limit'])
|
||||
->setMultipleSelectionAllowed(true);
|
||||
|
||||
switch ($filterBlock['type']) {
|
||||
case self::TYPE_CATEGORY:
|
||||
case self::TYPE_CONDITION:
|
||||
case self::TYPE_MANUFACTURER:
|
||||
case self::TYPE_QUANTITY:
|
||||
case self::TYPE_ATTRIBUTE_GROUP:
|
||||
case self::TYPE_FEATURE:
|
||||
$type = $filterBlock['type'];
|
||||
if ($filterBlock['type'] === self::TYPE_QUANTITY) {
|
||||
$type = 'availability';
|
||||
} elseif ($filterBlock['type'] == self::TYPE_ATTRIBUTE_GROUP) {
|
||||
$type = 'attribute_group';
|
||||
$facet->setProperty(self::TYPE_ATTRIBUTE_GROUP, $filterBlock['id_key']);
|
||||
if (isset($filterBlock['url_name'])) {
|
||||
$facet->setProperty(self::PROPERTY_URL_NAME, $filterBlock['url_name']);
|
||||
}
|
||||
} elseif ($filterBlock['type'] == self::TYPE_FEATURE) {
|
||||
$type = 'feature';
|
||||
$facet->setProperty(self::TYPE_FEATURE, $filterBlock['id_key']);
|
||||
if (isset($filterBlock['url_name'])) {
|
||||
$facet->setProperty(self::PROPERTY_URL_NAME, $filterBlock['url_name']);
|
||||
}
|
||||
}
|
||||
|
||||
$facet->setType($type);
|
||||
$filters = [];
|
||||
foreach ($filterBlock['values'] as $id => $filterArray) {
|
||||
$filter = new Filter();
|
||||
$filter
|
||||
->setType($type)
|
||||
->setLabel($filterArray['name'])
|
||||
->setMagnitude($filterArray['nbr'])
|
||||
->setValue($id);
|
||||
|
||||
if (isset($filterArray['url_name'])) {
|
||||
$filter->setProperty(self::PROPERTY_URL_NAME, $filterArray['url_name']);
|
||||
}
|
||||
|
||||
if (array_key_exists('checked', $filterArray)) {
|
||||
$filter->setActive($filterArray['checked']);
|
||||
}
|
||||
|
||||
if (isset($filterArray['color'])) {
|
||||
if ($filterArray['color'] != '') {
|
||||
$filter->setProperty(self::PROPERTY_COLOR, $filterArray['color']);
|
||||
} elseif (file_exists(_PS_COL_IMG_DIR_ . $id . '.jpg')) {
|
||||
$filter->setProperty(self::PROPERTY_TEXTURE, _THEME_COL_DIR_ . $id . '.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
if ((int) $filterBlock['filter_show_limit'] !== 0) {
|
||||
usort($filters, [$this, 'sortFiltersByMagnitude']);
|
||||
}
|
||||
|
||||
$this->hideZeroValuesAndShowLimit($filters, (int) $filterBlock['filter_show_limit']);
|
||||
|
||||
if ((int) $filterBlock['filter_show_limit'] !== 0 || $filterBlock['type'] !== self::TYPE_ATTRIBUTE_GROUP) {
|
||||
usort($filters, [$this, 'sortFiltersByLabel']);
|
||||
}
|
||||
|
||||
// No method available to add all filters
|
||||
foreach ($filters as $filter) {
|
||||
$facet->addFilter($filter);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_WEIGHT:
|
||||
case self::TYPE_PRICE:
|
||||
$facet
|
||||
->setType($filterBlock['type'])
|
||||
->setProperty('min', $filterBlock['min'])
|
||||
->setProperty('max', $filterBlock['max'])
|
||||
->setProperty('unit', $filterBlock['unit'])
|
||||
->setProperty('specifications', $filterBlock['specifications'])
|
||||
->setMultipleSelectionAllowed(false)
|
||||
->setProperty('range', true);
|
||||
|
||||
$filter = new Filter();
|
||||
$filter
|
||||
->setActive($filterBlock['value'] !== null)
|
||||
->setType($filterBlock['type'])
|
||||
->setMagnitude($filterBlock['nbr'])
|
||||
->setProperty('symbol', $filterBlock['unit'])
|
||||
->setValue($filterBlock['value']);
|
||||
|
||||
$facet->addFilter($filter);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch ((int) $filterBlock['filter_type']) {
|
||||
case self::WIDGET_TYPE_CHECKBOX:
|
||||
$facet->setMultipleSelectionAllowed(true);
|
||||
$facet->setWidgetType('checkbox');
|
||||
break;
|
||||
case self::WIDGET_TYPE_RADIO:
|
||||
$facet->setMultipleSelectionAllowed(false);
|
||||
$facet->setWidgetType('radio');
|
||||
break;
|
||||
case self::WIDGET_TYPE_DROPDOWN:
|
||||
$facet->setMultipleSelectionAllowed(false);
|
||||
$facet->setWidgetType('dropdown');
|
||||
break;
|
||||
case self::WIDGET_TYPE_SLIDER:
|
||||
$facet->setMultipleSelectionAllowed(false);
|
||||
$facet->setWidgetType('slider');
|
||||
break;
|
||||
}
|
||||
|
||||
$facets[] = $facet;
|
||||
}
|
||||
|
||||
return $facets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProductSearchQuery $query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function createFacetedSearchFiltersFromQuery(ProductSearchQuery $query)
|
||||
{
|
||||
$idShop = (int) $this->context->shop->id;
|
||||
$idLang = (int) $this->context->language->id;
|
||||
|
||||
$idParent = $query->getIdCategory();
|
||||
if (empty($idParent)) {
|
||||
$idParent = (int) Tools::getValue('id_category_layered', Configuration::get('PS_HOME_CATEGORY'));
|
||||
}
|
||||
|
||||
$searchFilters = [];
|
||||
|
||||
/* Get the filters for the current category */
|
||||
$filters = $this->database->executeS(
|
||||
'SELECT type, id_value, filter_show_limit, filter_type FROM ' . _DB_PREFIX_ . 'layered_category
|
||||
WHERE id_category = ' . (int) $idParent . '
|
||||
AND id_shop = ' . (int) $idShop . '
|
||||
GROUP BY `type`, id_value ORDER BY position ASC'
|
||||
);
|
||||
|
||||
$facetAndFiltersLabels = $this->urlSerializer->unserialize($query->getEncodedFacets());
|
||||
foreach ($filters as $filter) {
|
||||
$filterLabel = $this->convertFilterTypeToLabel($filter['type']);
|
||||
|
||||
switch ($filter['type']) {
|
||||
case self::TYPE_MANUFACTURER:
|
||||
if (!isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
// No need to filter if no information
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$manufacturers = Manufacturer::getManufacturers(false, $idLang);
|
||||
$searchFilters[$filter['type']] = [];
|
||||
foreach ($manufacturers as $manufacturer) {
|
||||
if (in_array($manufacturer['name'], $facetAndFiltersLabels[$filterLabel])) {
|
||||
$searchFilters[$filter['type']][$manufacturer['name']] = $manufacturer['id_manufacturer'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_QUANTITY:
|
||||
if (!isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
// No need to filter if no information
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$quantityArray = [
|
||||
$this->context->getTranslator()->trans(
|
||||
'Not available',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 0,
|
||||
$this->context->getTranslator()->trans(
|
||||
'Available',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 1,
|
||||
$this->context->getTranslator()->trans(
|
||||
'In stock',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 2,
|
||||
];
|
||||
|
||||
$searchFilters[$filter['type']] = [];
|
||||
foreach ($quantityArray as $quantityName => $quantityId) {
|
||||
if (isset($facetAndFiltersLabels[$filterLabel]) && in_array($quantityName, $facetAndFiltersLabels[$filterLabel])) {
|
||||
$searchFilters[$filter['type']][] = $quantityId;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_CONDITION:
|
||||
if (!isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
// No need to filter if no information
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$conditionArray = [
|
||||
$this->context->getTranslator()->trans(
|
||||
'New',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 'new',
|
||||
$this->context->getTranslator()->trans(
|
||||
'Used',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 'used',
|
||||
$this->context->getTranslator()->trans(
|
||||
'Refurbished',
|
||||
[],
|
||||
'Modules.Facetedsearch.Shop'
|
||||
) => 'refurbished',
|
||||
];
|
||||
|
||||
$searchFilters[$filter['type']] = [];
|
||||
foreach ($conditionArray as $conditionName => $conditionId) {
|
||||
if (isset($facetAndFiltersLabels[$filterLabel]) && in_array($conditionName, $facetAndFiltersLabels[$filterLabel])) {
|
||||
$searchFilters[$filter['type']][] = $conditionId;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_FEATURE:
|
||||
$features = $this->dataAccessor->getFeatures($idLang);
|
||||
foreach ($features as $feature) {
|
||||
if ($filter['id_value'] != $feature['id_feature']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($facetAndFiltersLabels[$feature['url_name']])) {
|
||||
$featureValueLabels = $facetAndFiltersLabels[$feature['url_name']];
|
||||
} elseif (isset($facetAndFiltersLabels[$feature['name']])) {
|
||||
$featureValueLabels = $facetAndFiltersLabels[$feature['name']];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$featureValues = $this->dataAccessor->getFeatureValues($feature['id_feature'], $idLang);
|
||||
foreach ($featureValues as $featureValue) {
|
||||
if (in_array($featureValue['url_name'], $featureValueLabels)
|
||||
|| in_array($featureValue['value'], $featureValueLabels)
|
||||
) {
|
||||
$searchFilters['id_feature'][$feature['id_feature']][] = $featureValue['id_feature_value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_ATTRIBUTE_GROUP:
|
||||
$attributesGroup = $this->dataAccessor->getAttributesGroups($idLang);
|
||||
foreach ($attributesGroup as $attributeGroup) {
|
||||
if ($filter['id_value'] != $attributeGroup['id_attribute_group']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($facetAndFiltersLabels[$attributeGroup['url_name']])) {
|
||||
$attributeLabels = $facetAndFiltersLabels[$attributeGroup['url_name']];
|
||||
} elseif (isset($facetAndFiltersLabels[$attributeGroup['attribute_group_name']])) {
|
||||
$attributeLabels = $facetAndFiltersLabels[$attributeGroup['attribute_group_name']];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = $this->dataAccessor->getAttributes($idLang, $attributeGroup['id_attribute_group']);
|
||||
foreach ($attributes as $attribute) {
|
||||
if (in_array($attribute['url_name'], $attributeLabels)
|
||||
|| in_array($attribute['name'], $attributeLabels)
|
||||
) {
|
||||
$searchFilters['id_attribute_group'][$attributeGroup['id_attribute_group']][] = $attribute['id_attribute'];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_PRICE:
|
||||
case self::TYPE_WEIGHT:
|
||||
if (isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
$filters = $facetAndFiltersLabels[$filterLabel];
|
||||
if (isset($filters[1]) && isset($filters[2])) {
|
||||
$from = $filters[1];
|
||||
$to = $filters[2];
|
||||
$searchFilters[$filter['type']][0] = $from;
|
||||
$searchFilters[$filter['type']][1] = $to;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::TYPE_CATEGORY:
|
||||
if (isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
foreach ($facetAndFiltersLabels[$filterLabel] as $queryFilter) {
|
||||
$categories = Category::searchByNameAndParentCategoryId($idLang, $queryFilter, (int) $query->getIdCategory());
|
||||
if ($categories) {
|
||||
$searchFilters[$filter['type']][] = $categories['id_category'];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isset($facetAndFiltersLabels[$filterLabel])) {
|
||||
foreach ($facetAndFiltersLabels[$filterLabel] as $queryFilter) {
|
||||
$searchFilters[$filter['type']][] = $queryFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all empty selected filters
|
||||
foreach ($searchFilters as $key => $value) {
|
||||
switch ($key) {
|
||||
case self::TYPE_PRICE:
|
||||
case self::TYPE_WEIGHT:
|
||||
if ($value[0] === '' && $value[1] === '') {
|
||||
unset($searchFilters[$key]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ($value == '' || $value == []) {
|
||||
unset($searchFilters[$key]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $searchFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert filter type to label
|
||||
*
|
||||
* @param string $filterType
|
||||
*/
|
||||
private function convertFilterTypeToLabel($filterType)
|
||||
{
|
||||
switch ($filterType) {
|
||||
case self::TYPE_PRICE:
|
||||
return $this->context->getTranslator()->trans('Price', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_WEIGHT:
|
||||
return $this->context->getTranslator()->trans('Weight', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_CONDITION:
|
||||
return $this->context->getTranslator()->trans('Condition', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_QUANTITY:
|
||||
return $this->context->getTranslator()->trans('Availability', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_MANUFACTURER:
|
||||
return $this->context->getTranslator()->trans('Brand', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_CATEGORY:
|
||||
return $this->context->getTranslator()->trans('Categories', [], 'Modules.Facetedsearch.Shop');
|
||||
case self::TYPE_FEATURE:
|
||||
case self::TYPE_ATTRIBUTE_GROUP:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide entries with 0 results
|
||||
* Hide depending of show limit parameter
|
||||
*
|
||||
* @param array $filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function hideZeroValuesAndShowLimit(array $filters, $showLimit)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter->getMagnitude() === 0
|
||||
|| ($showLimit > 0 && $count >= $showLimit)
|
||||
) {
|
||||
$filter->setDisplayed(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
++$count;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort filters by magnitude
|
||||
*
|
||||
* @param Filter $a
|
||||
* @param Filter $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function sortFiltersByMagnitude(Filter $a, Filter $b)
|
||||
{
|
||||
$aMagnitude = $a->getMagnitude();
|
||||
$bMagnitude = $b->getMagnitude();
|
||||
if ($aMagnitude == $bMagnitude) {
|
||||
// Same magnitude, sort by label
|
||||
return $this->sortFiltersByLabel($a, $b);
|
||||
}
|
||||
|
||||
return $aMagnitude > $bMagnitude ? -1 : +1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort filters by label
|
||||
*
|
||||
* @param Filter $a
|
||||
* @param Filter $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function sortFiltersByLabel(Filter $a, Filter $b)
|
||||
{
|
||||
return strnatcasecmp($a->getLabel(), $b->getLabel());
|
||||
}
|
||||
}
|
||||
184
modules/ps_facetedsearch/src/Filters/DataAccessor.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Filters;
|
||||
|
||||
use Combination;
|
||||
use Db;
|
||||
use Shop;
|
||||
|
||||
/**
|
||||
* Data accessor for features and attributes
|
||||
*/
|
||||
class DataAccessor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributesGroup;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* @var Db
|
||||
*/
|
||||
private $database;
|
||||
|
||||
public function __construct(Db $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array|false|\PDOStatement|resource|null
|
||||
*/
|
||||
public function getAttributes($idLang, $idAttributeGroup)
|
||||
{
|
||||
if (!Combination::isFeatureActive()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isset($this->attributes[$idLang][$idAttributeGroup])) {
|
||||
$this->attributes[$idLang] = [$idAttributeGroup => []];
|
||||
$tempAttributes = $this->database->executeS(
|
||||
'SELECT DISTINCT a.`id_attribute`, ' .
|
||||
'a.`color`, ' .
|
||||
'al.`name`, ' .
|
||||
'agl.`id_attribute_group`, ' .
|
||||
'IF(lialv.`url_name` IS NULL OR lialv.`url_name` = "", NULL, lialv.`url_name`) AS url_name, ' .
|
||||
'IF(lialv.`meta_title` IS NULL OR lialv.`meta_title` = "", NULL, lialv.`meta_title`) AS meta_title ' .
|
||||
'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) $idLang . ') ' .
|
||||
'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) $idLang . ')' .
|
||||
Shop::addSqlAssociation('attribute_group', 'ag') . ' ' .
|
||||
Shop::addSqlAssociation('attribute', 'a') . ' ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value` lialv ' .
|
||||
'ON (a.`id_attribute` = lialv.`id_attribute` AND lialv.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'WHERE ag.id_attribute_group = ' . (int) $idAttributeGroup . ' ' .
|
||||
'ORDER BY agl.`name` ASC, a.`position` ASC'
|
||||
);
|
||||
|
||||
foreach ($tempAttributes as $key => $attribute) {
|
||||
$this->attributes[$idLang][$idAttributeGroup][$attribute['id_attribute']] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->attributes[$idLang][$idAttributeGroup];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attributes groups for a given language
|
||||
*
|
||||
* @param int $idLang Language id
|
||||
*
|
||||
* @return array Attributes groups
|
||||
*/
|
||||
public function getAttributesGroups($idLang)
|
||||
{
|
||||
if (!Combination::isFeatureActive()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isset($this->attributesGroup[$idLang])) {
|
||||
$this->attributesGroup[$idLang] = [];
|
||||
$tempAttributesGroup = $this->database->executeS(
|
||||
'SELECT ag.id_attribute_group, ' .
|
||||
'agl.public_name as attribute_group_name, ' .
|
||||
'is_color_group, ' .
|
||||
'IF(liaglv.`url_name` IS NULL OR liaglv.`url_name` = "", NULL, liaglv.`url_name`) AS url_name, ' .
|
||||
'IF(liaglv.`meta_title` IS NULL OR liaglv.`meta_title` = "", NULL, liaglv.`meta_title`) AS meta_title, ' .
|
||||
'IFNULL(liag.indexable, TRUE) AS indexable ' .
|
||||
'FROM `' . _DB_PREFIX_ . 'attribute_group` ag ' .
|
||||
Shop::addSqlAssociation('attribute_group', 'ag') . ' ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'attribute_group_lang` agl ' .
|
||||
'ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_attribute_group` liag ' .
|
||||
'ON (ag.`id_attribute_group` = liag.`id_attribute_group`) ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value` AS liaglv ' .
|
||||
'ON (ag.`id_attribute_group` = liaglv.`id_attribute_group` AND agl.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'GROUP BY ag.id_attribute_group ORDER BY ag.`position` ASC'
|
||||
);
|
||||
|
||||
foreach ($tempAttributesGroup as $key => $attributeGroup) {
|
||||
$this->attributesGroup[$idLang][$attributeGroup['id_attribute_group']] = $attributeGroup;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->attributesGroup[$idLang];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get features with their associated layered information
|
||||
*
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array|false|\PDOStatement|resource|null
|
||||
*/
|
||||
public function getFeatures($idLang)
|
||||
{
|
||||
return $this->database->executeS(
|
||||
'SELECT DISTINCT f.id_feature, f.*, fl.*, ' .
|
||||
'IF(liflv.`url_name` IS NULL OR liflv.`url_name` = "", NULL, liflv.`url_name`) AS url_name, ' .
|
||||
'IF(liflv.`meta_title` IS NULL OR liflv.`meta_title` = "", NULL, liflv.`meta_title`) AS meta_title, ' .
|
||||
'lif.indexable ' .
|
||||
'FROM `' . _DB_PREFIX_ . 'feature` f ' .
|
||||
'' . Shop::addSqlAssociation('feature', 'f') . ' ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'feature_lang` fl ON (f.`id_feature` = fl.`id_feature` AND fl.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_feature` lif ' .
|
||||
'ON (f.`id_feature` = lif.`id_feature`) ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value` liflv ' .
|
||||
'ON (f.`id_feature` = liflv.`id_feature` AND liflv.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'ORDER BY f.`position` ASC'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feature values with their associated layered information
|
||||
*
|
||||
* @param int $idFeature
|
||||
* @param int $idLang
|
||||
*
|
||||
* @return array|false|\PDOStatement|resource|null
|
||||
*/
|
||||
public function getFeatureValues($idFeature, $idLang)
|
||||
{
|
||||
return $this->database->executeS(
|
||||
'SELECT v.*, vl.*, ' .
|
||||
'IF(lifvlv.`url_name` IS NULL OR lifvlv.`url_name` = "", NULL, lifvlv.`url_name`) AS url_name, ' .
|
||||
'IF(lifvlv.`meta_title` IS NULL OR lifvlv.`meta_title` = "", NULL, lifvlv.`meta_title`) AS meta_title ' .
|
||||
'FROM `' . _DB_PREFIX_ . 'feature_value` v ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'feature_value_lang` vl ' .
|
||||
'ON (v.`id_feature_value` = vl.`id_feature_value` AND vl.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value` lifvlv ' .
|
||||
'ON (v.`id_feature_value` = lifvlv.`id_feature_value` AND lifvlv.`id_lang` = ' . (int) $idLang . ') ' .
|
||||
'WHERE v.`id_feature` = ' . (int) $idFeature . ' ' .
|
||||
'ORDER BY vl.`value` ASC'
|
||||
);
|
||||
}
|
||||
}
|
||||
168
modules/ps_facetedsearch/src/Filters/Products.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Filters;
|
||||
|
||||
use Configuration;
|
||||
use PrestaShop\Module\FacetedSearch\Adapter\AbstractAdapter;
|
||||
use PrestaShop\Module\FacetedSearch\Product\Search;
|
||||
use Product;
|
||||
use Validate;
|
||||
|
||||
class Products
|
||||
{
|
||||
/**
|
||||
* Use price tax filter
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $psLayeredFilterPriceUsetax;
|
||||
|
||||
/**
|
||||
* Use price rounding
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $psLayeredFilterPriceRounding;
|
||||
|
||||
/**
|
||||
* @var AbstractAdapter
|
||||
*/
|
||||
private $searchAdapter;
|
||||
|
||||
public function __construct(Search $productSearch)
|
||||
{
|
||||
$this->searchAdapter = $productSearch->getSearchAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the products associated with the current filters
|
||||
*
|
||||
* @param int $productsPerPage
|
||||
* @param int $page
|
||||
* @param string $orderBy
|
||||
* @param string $orderWay
|
||||
* @param array $selectedFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProductByFilters(
|
||||
$productsPerPage,
|
||||
$page,
|
||||
$orderBy,
|
||||
$orderWay,
|
||||
$selectedFilters = []
|
||||
) {
|
||||
$orderWay = Validate::isOrderWay($orderWay) ? $orderWay : 'ASC';
|
||||
$orderBy = Validate::isOrderBy($orderBy) ? $orderBy : 'position';
|
||||
|
||||
$this->searchAdapter->setLimit((int) $productsPerPage, ((int) $page - 1) * $productsPerPage);
|
||||
$this->searchAdapter->setOrderField($orderBy);
|
||||
$this->searchAdapter->setOrderDirection($orderWay);
|
||||
|
||||
$this->searchAdapter->addGroupBy('id_product');
|
||||
if (isset($selectedFilters['price']) || $orderBy === 'price') {
|
||||
$this->searchAdapter->addSelectField('id_product');
|
||||
$this->searchAdapter->addSelectField('price');
|
||||
$this->searchAdapter->addSelectField('price_min');
|
||||
$this->searchAdapter->addSelectField('price_max');
|
||||
}
|
||||
|
||||
$matchingProductList = $this->searchAdapter->execute();
|
||||
|
||||
$this->pricePostFiltering($matchingProductList, $selectedFilters);
|
||||
|
||||
$nbrProducts = $this->searchAdapter->count();
|
||||
|
||||
if (empty($nbrProducts)) {
|
||||
$matchingProductList = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'products' => $matchingProductList,
|
||||
'count' => $nbrProducts,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Post filter product depending on the price and a few extra config variables
|
||||
*
|
||||
* @param array $matchingProductList
|
||||
* @param array $selectedFilters
|
||||
*/
|
||||
private function pricePostFiltering(&$matchingProductList, $selectedFilters)
|
||||
{
|
||||
if (!isset($selectedFilters['price'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$priceFilter['min'] = (float) ($selectedFilters['price'][0]);
|
||||
$priceFilter['max'] = (float) ($selectedFilters['price'][1]);
|
||||
|
||||
if ($this->psLayeredFilterPriceUsetax === null) {
|
||||
$this->psLayeredFilterPriceUsetax = (bool) Configuration::get('PS_LAYERED_FILTER_PRICE_USETAX');
|
||||
}
|
||||
|
||||
if ($this->psLayeredFilterPriceRounding === null) {
|
||||
$this->psLayeredFilterPriceRounding = (bool) Configuration::get('PS_LAYERED_FILTER_PRICE_ROUNDING');
|
||||
}
|
||||
|
||||
if ($this->psLayeredFilterPriceUsetax || $this->psLayeredFilterPriceRounding) {
|
||||
$this->filterPrice(
|
||||
$matchingProductList,
|
||||
$this->psLayeredFilterPriceUsetax,
|
||||
$this->psLayeredFilterPriceRounding,
|
||||
$priceFilter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove products from the product list in case of price postFiltering
|
||||
*
|
||||
* @param array $matchingProductList
|
||||
* @param bool $psLayeredFilterPriceUsetax
|
||||
* @param bool $psLayeredFilterPriceRounding
|
||||
* @param array $priceFilter
|
||||
*/
|
||||
private function filterPrice(
|
||||
&$matchingProductList,
|
||||
$psLayeredFilterPriceUsetax,
|
||||
$psLayeredFilterPriceRounding,
|
||||
$priceFilter
|
||||
) {
|
||||
/* for this case, price could be out of range, so we need to compute the real price */
|
||||
foreach ($matchingProductList as $key => $product) {
|
||||
if (($product['price_min'] < (int) $priceFilter['min'] && $product['price_max'] > (int) $priceFilter['min'])
|
||||
|| ($product['price_max'] > (int) $priceFilter['max'] && $product['price_min'] < (int) $priceFilter['max'])
|
||||
) {
|
||||
$price = Product::getPriceStatic($product['id_product'], $psLayeredFilterPriceUsetax);
|
||||
if ($psLayeredFilterPriceRounding) {
|
||||
$price = (int) $price;
|
||||
}
|
||||
|
||||
if ($price < $priceFilter['min'] || $price > $priceFilter['max']) {
|
||||
// out of range price, exclude the product
|
||||
unset($matchingProductList[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Form\Feature;
|
||||
|
||||
use Db;
|
||||
use PrestaShopDatabaseException;
|
||||
|
||||
/**
|
||||
* Provides form data
|
||||
*/
|
||||
class FormDataProvider
|
||||
{
|
||||
/**
|
||||
* @var Db
|
||||
*/
|
||||
private $database;
|
||||
|
||||
public function __construct(Db $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills form data
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws PrestaShopDatabaseException
|
||||
*/
|
||||
public function getData(array $params)
|
||||
{
|
||||
$defaultUrl = [];
|
||||
$defaultMetaTitle = [];
|
||||
$isIndexable = false;
|
||||
|
||||
// if params contains id, gets data for edit form
|
||||
if (!empty($params['id'])) {
|
||||
$featureId = (int) $params['id'];
|
||||
|
||||
// returns false if request failed.
|
||||
$queryIndexable = $this->database->getValue(
|
||||
'SELECT `indexable` ' .
|
||||
'FROM ' . _DB_PREFIX_ . 'layered_indexable_feature ' .
|
||||
'WHERE `id_feature` = ' . $featureId
|
||||
);
|
||||
|
||||
$isIndexable = (bool) $queryIndexable;
|
||||
$result = $this->database->executeS(
|
||||
'SELECT `url_name`, `meta_title`, `id_lang` ' .
|
||||
'FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value ' .
|
||||
'WHERE `id_feature` = ' . $featureId
|
||||
);
|
||||
|
||||
if (!empty($result) && is_array($result)) {
|
||||
foreach ($result as $data) {
|
||||
$defaultUrl[$data['id_lang']] = $data['url_name'];
|
||||
$defaultMetaTitle[$data['id_lang']] = $data['meta_title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $defaultUrl,
|
||||
'meta_title' => $defaultMetaTitle,
|
||||
'is_indexable' => $isIndexable,
|
||||
];
|
||||
}
|
||||
}
|
||||
117
modules/ps_facetedsearch/src/Form/Feature/FormModifier.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Form\Feature;
|
||||
|
||||
use Context;
|
||||
use PrestaShop\Module\FacetedSearch\Constraint\UrlSegment;
|
||||
use PrestaShopBundle\Form\Admin\Type\SwitchType;
|
||||
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
|
||||
use PrestaShopBundle\Translation\TranslatorComponent;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Adds module specific fields to BO form
|
||||
*/
|
||||
class FormModifier
|
||||
{
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
public function __construct(Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function modify(
|
||||
FormBuilderInterface $formBuilder,
|
||||
array $data
|
||||
) {
|
||||
/**
|
||||
* @var TranslatorComponent
|
||||
*/
|
||||
$translator = $this->context->getTranslator();
|
||||
$invalidCharsHint = $translator->trans(
|
||||
'Invalid characters: <>;=#{}_',
|
||||
[],
|
||||
'Modules.Facetedsearch.Admin'
|
||||
);
|
||||
|
||||
$urlTip = $translator->trans(
|
||||
'When the Faceted Search module is enabled, you can get more detailed URLs by choosing ' .
|
||||
'the word that best represents this feature. By default, PrestaShop uses the ' .
|
||||
'feature\'s name, but you can change that setting using this field.',
|
||||
[],
|
||||
'Modules.Facetedsearch.Admin'
|
||||
);
|
||||
$metaTitleTip = $translator->trans(
|
||||
'When the Faceted Search module is enabled, you can get more detailed page titles by ' .
|
||||
'choosing the word that best represents this feature. By default, PrestaShop uses the ' .
|
||||
'feature\'s name, but you can change that setting using this field.',
|
||||
[],
|
||||
'Modules.Facetedsearch.Admin'
|
||||
);
|
||||
|
||||
$formBuilder
|
||||
->add(
|
||||
'url_name',
|
||||
TranslatableType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $translator->trans('URL', [], 'Modules.Facetedsearch.Admin'),
|
||||
'help' => $urlTip . ' ' . $invalidCharsHint,
|
||||
'options' => [
|
||||
'constraints' => [
|
||||
new UrlSegment([
|
||||
'message' => $translator->trans('%s is invalid.', [], 'Admin.Notifications.Error'),
|
||||
]),
|
||||
],
|
||||
],
|
||||
'data' => $data['url'],
|
||||
]
|
||||
)
|
||||
->add(
|
||||
'meta_title',
|
||||
TranslatableType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $translator->trans('Meta title', [], 'Modules.Facetedsearch.Admin'),
|
||||
'help' => $metaTitleTip,
|
||||
'data' => $data['meta_title'],
|
||||
]
|
||||
)
|
||||
->add(
|
||||
'layered_indexable',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $translator->trans('Indexable', [], 'Modules.Facetedsearch.Admin'),
|
||||
'help' => $translator->trans(
|
||||
'Use this attribute in URL generated by the Faceted Search module.',
|
||||
[],
|
||||
'Modules.Facetedsearch.Admin'
|
||||
),
|
||||
'data' => $data['is_indexable'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
60
modules/ps_facetedsearch/src/Hook/AbstractHook.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Context;
|
||||
use Db;
|
||||
use Ps_Facetedsearch;
|
||||
|
||||
abstract class AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [];
|
||||
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var Ps_Facetedsearch
|
||||
*/
|
||||
protected $module;
|
||||
|
||||
/**
|
||||
* @var Db
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
public function __construct(Ps_Facetedsearch $module)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->context = $module->getContext();
|
||||
$this->database = $module->getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableHooks()
|
||||
{
|
||||
return static::AVAILABLE_HOOKS;
|
||||
}
|
||||
}
|
||||
125
modules/ps_facetedsearch/src/Hook/Attribute.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Language;
|
||||
use Tools;
|
||||
|
||||
class Attribute extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionAttributeGroupDelete',
|
||||
'actionAttributeSave',
|
||||
'displayAttributeForm',
|
||||
'actionAttributePostProcess',
|
||||
];
|
||||
|
||||
/**
|
||||
* After save attribute
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAttributeSave(array $params)
|
||||
{
|
||||
if (empty($params['id_attribute'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value
|
||||
WHERE `id_attribute` = ' . (int) $params['id_attribute']
|
||||
);
|
||||
|
||||
foreach (Language::getLanguages(false) as $language) {
|
||||
$seoUrl = Tools::getValue('url_name_' . (int) $language['id_lang']);
|
||||
$metaTitle = Tools::getValue('meta_title_' . (int) $language['id_lang']);
|
||||
if (empty($seoUrl) && empty($metaTitle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value
|
||||
(`id_attribute`, `id_lang`, `url_name`, `meta_title`)
|
||||
VALUES (
|
||||
' . (int) $params['id_attribute'] . ', ' . (int) $language['id_lang'] . ',
|
||||
\'' . pSQL(Tools::link_rewrite($seoUrl)) . '\',
|
||||
\'' . pSQL($metaTitle, true) . '\')'
|
||||
);
|
||||
}
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* After delete attribute
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAttributeGroupDelete(array $params)
|
||||
{
|
||||
if (empty($params['id_attribute'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value
|
||||
WHERE `id_attribute` = ' . (int) $params['id_attribute']
|
||||
);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process attribute
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAttributePostProcess(array $params)
|
||||
{
|
||||
$this->module->checkLinksRewrite($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute form
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayAttributeForm(array $params)
|
||||
{
|
||||
$values = [];
|
||||
|
||||
if ($result = $this->database->executeS(
|
||||
'SELECT `url_name`, `meta_title`, `id_lang`
|
||||
FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value
|
||||
WHERE `id_attribute` = ' . (int) $params['id_attribute']
|
||||
)) {
|
||||
foreach ($result as $data) {
|
||||
$values[$data['id_lang']] = ['url_name' => $data['url_name'], 'meta_title' => $data['meta_title']];
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'languages' => Language::getLanguages(false),
|
||||
'default_form_language' => (int) $this->context->controller->default_form_language,
|
||||
'values' => $values,
|
||||
]);
|
||||
|
||||
return $this->module->render('attribute_form.tpl');
|
||||
}
|
||||
}
|
||||
145
modules/ps_facetedsearch/src/Hook/AttributeGroup.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Language;
|
||||
use Tools;
|
||||
|
||||
class AttributeGroup extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionAttributeGroupDelete',
|
||||
'actionAttributeGroupSave',
|
||||
'displayAttributeGroupForm',
|
||||
'displayAttributeGroupPostProcess',
|
||||
];
|
||||
|
||||
/**
|
||||
* After save Attributes group
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAttributeGroupSave(array $params)
|
||||
{
|
||||
if (empty($params['id_attribute_group']) || Tools::getValue('layered_indexable') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
);
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
);
|
||||
|
||||
$this->database->execute(
|
||||
'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_attribute_group (`id_attribute_group`, `indexable`)
|
||||
VALUES (' . (int) $params['id_attribute_group'] . ', ' . (int) Tools::getValue('layered_indexable') . ')'
|
||||
);
|
||||
|
||||
foreach (Language::getLanguages(false) as $language) {
|
||||
$seoUrl = Tools::getValue('url_name_' . (int) $language['id_lang']);
|
||||
$metaTitle = Tools::getValue('meta_title_' . (int) $language['id_lang']);
|
||||
if (empty($seoUrl) && empty($metaTitle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value
|
||||
(`id_attribute_group`, `id_lang`, `url_name`, `meta_title`)
|
||||
VALUES (
|
||||
' . (int) $params['id_attribute_group'] . ', ' . (int) $language['id_lang'] . ',
|
||||
\'' . pSQL(Tools::link_rewrite($seoUrl)) . '\',
|
||||
\'' . pSQL($metaTitle, true) . '\')'
|
||||
);
|
||||
}
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* After delete attribute group
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAttributeGroupDelete(array $params)
|
||||
{
|
||||
if (empty($params['id_attribute_group'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
);
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process attribute group
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayAttributeGroupPostProcess(array $params)
|
||||
{
|
||||
$this->module->checkLinksRewrite($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute group form
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function displayAttributeGroupForm(array $params)
|
||||
{
|
||||
$values = [];
|
||||
$isIndexable = $this->database->getValue(
|
||||
'SELECT `indexable`
|
||||
FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
);
|
||||
|
||||
if ($result = $this->database->executeS(
|
||||
'SELECT `url_name`, `meta_title`, `id_lang` FROM ' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value
|
||||
WHERE `id_attribute_group` = ' . (int) $params['id_attribute_group']
|
||||
)) {
|
||||
foreach ($result as $data) {
|
||||
$values[$data['id_lang']] = ['url_name' => $data['url_name'], 'meta_title' => $data['meta_title']];
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'languages' => Language::getLanguages(false),
|
||||
'default_form_language' => (int) $this->context->controller->default_form_language,
|
||||
'values' => $values,
|
||||
'is_indexable' => (bool) $isIndexable,
|
||||
]);
|
||||
|
||||
return $this->module->render('attribute_group_form.tpl');
|
||||
}
|
||||
}
|
||||
97
modules/ps_facetedsearch/src/Hook/Category.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Tools;
|
||||
|
||||
class Category extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionCategoryAdd',
|
||||
'actionCategoryDelete',
|
||||
'actionCategoryUpdate',
|
||||
];
|
||||
|
||||
/**
|
||||
* Category addition
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionCategoryAdd(array $params)
|
||||
{
|
||||
$this->module->rebuildLayeredCache([], [(int) $params['category']->id]);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Category update
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionCategoryUpdate(array $params)
|
||||
{
|
||||
/*
|
||||
* The category status might (active, inactive) have changed,
|
||||
* we have to update the layered cache table structure
|
||||
*/
|
||||
if (isset($params['category']) && !$params['category']->active) {
|
||||
$this->cleanAndRebuildCategoryFilters($params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Category deletion
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionCategoryDelete(array $params)
|
||||
{
|
||||
$this->cleanAndRebuildCategoryFilters($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean and rebuild category filters
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
private function cleanAndRebuildCategoryFilters(array $params)
|
||||
{
|
||||
$layeredFilterList = $this->database->executeS(
|
||||
'SELECT * FROM ' . _DB_PREFIX_ . 'layered_filter'
|
||||
);
|
||||
|
||||
foreach ($layeredFilterList as $layeredFilter) {
|
||||
$data = Tools::unSerialize($layeredFilter['filters']);
|
||||
|
||||
if (in_array((int) $params['category']->id, $data['categories'])) {
|
||||
unset($data['categories'][array_search((int) $params['category']->id, $data['categories'])]);
|
||||
$this->database->execute(
|
||||
'UPDATE `' . _DB_PREFIX_ . 'layered_filter`
|
||||
SET `filters` = \'' . pSQL(serialize($data)) . '\'
|
||||
WHERE `id_layered_filter` = ' . (int) $layeredFilter['id_layered_filter']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
$this->module->buildLayeredCategories();
|
||||
}
|
||||
}
|
||||
38
modules/ps_facetedsearch/src/Hook/Configuration.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
class Configuration extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionProductPreferencesPageStockSave',
|
||||
];
|
||||
|
||||
/**
|
||||
* After save of product stock preferences form
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionProductPreferencesPageStockSave(array $params)
|
||||
{
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
}
|
||||
40
modules/ps_facetedsearch/src/Hook/Design.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
class Design extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'displayLeftColumn',
|
||||
];
|
||||
|
||||
/**
|
||||
* Force this hook to be called here instance of using WidgetInterface
|
||||
* because Hook::isHookCallableOn before the instanceof function.
|
||||
* Which means is_callable always returns true with a __call usage.
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayLeftColumn(array $params)
|
||||
{
|
||||
return $this->module->fetch('module:ps_facetedsearch/ps_facetedsearch.tpl');
|
||||
}
|
||||
}
|
||||
268
modules/ps_facetedsearch/src/Hook/Feature.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Configuration;
|
||||
use Language;
|
||||
use PrestaShop\Module\FacetedSearch\Form\Feature\FormDataProvider;
|
||||
use PrestaShop\Module\FacetedSearch\Form\Feature\FormModifier;
|
||||
use PrestaShopDatabaseException;
|
||||
use Ps_Facetedsearch;
|
||||
use Tools;
|
||||
|
||||
class Feature extends AbstractHook
|
||||
{
|
||||
/**
|
||||
* @var FormModifier
|
||||
*/
|
||||
private $formModifier;
|
||||
|
||||
/**
|
||||
* @var FormDataProvider
|
||||
*/
|
||||
private $dataProvider;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isMigratedPage = false;
|
||||
|
||||
public function __construct(Ps_Facetedsearch $module)
|
||||
{
|
||||
parent::__construct($module);
|
||||
|
||||
$this->formModifier = new FormModifier($module->getContext());
|
||||
$this->dataProvider = new FormDataProvider($module->getDatabase());
|
||||
}
|
||||
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionFeatureSave',
|
||||
'actionFeatureDelete',
|
||||
'displayFeatureForm',
|
||||
'displayFeaturePostProcess',
|
||||
'actionFeatureFormBuilderModifier',
|
||||
'actionAfterCreateFeatureFormHandler',
|
||||
'actionAfterUpdateFeatureFormHandler',
|
||||
];
|
||||
|
||||
/**
|
||||
* Hook for modifying feature form formBuilder
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @throws PrestaShopDatabaseException
|
||||
*/
|
||||
public function actionFeatureFormBuilderModifier(array $params)
|
||||
{
|
||||
$this->isMigratedPage = true;
|
||||
$this->formModifier->modify($params['form_builder'], $this->dataProvider->getData($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook after create feature.
|
||||
*
|
||||
* @since PrestaShop 1.7.8.0
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAfterCreateFeatureFormHandler(array $params)
|
||||
{
|
||||
$this->save($params['id'], $params['form_data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook after update feature.
|
||||
*
|
||||
* @since PrestaShop 1.7.8.0
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAfterUpdateFeatureFormHandler(array $params)
|
||||
{
|
||||
$this->save($params['id'], $params['form_data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook after delete a feature
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionFeatureDelete(array $params)
|
||||
{
|
||||
if (empty($params['id_feature'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_feature
|
||||
WHERE `id_feature` = ' . (int) $params['id_feature']
|
||||
);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook post process feature
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayFeaturePostProcess(array $params)
|
||||
{
|
||||
$this->module->checkLinksRewrite($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook feature form
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayFeatureForm(array $params)
|
||||
{
|
||||
if ($this->isMigratedPage === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$isIndexable = $this->database->getValue(
|
||||
'SELECT `indexable` ' .
|
||||
'FROM ' . _DB_PREFIX_ . 'layered_indexable_feature ' .
|
||||
'WHERE `id_feature` = ' . (int) $params['id_feature']
|
||||
);
|
||||
|
||||
$result = $this->database->executeS(
|
||||
'SELECT `url_name`, `meta_title`, `id_lang` ' .
|
||||
'FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value ' .
|
||||
'WHERE `id_feature` = ' . (int) $params['id_feature']
|
||||
);
|
||||
if ($result) {
|
||||
foreach ($result as $data) {
|
||||
$values[$data['id_lang']] = [
|
||||
'url_name' => $data['url_name'],
|
||||
'meta_title' => $data['meta_title'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'languages' => Language::getLanguages(false),
|
||||
'default_form_language' => (int) $this->context->controller->default_form_language,
|
||||
'values' => $values,
|
||||
'is_indexable' => (bool) $isIndexable,
|
||||
]);
|
||||
|
||||
return $this->module->render('feature_form.tpl');
|
||||
}
|
||||
|
||||
/**
|
||||
* After save feature
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionFeatureSave(array $params)
|
||||
{
|
||||
if (empty($params['id_feature']) || Tools::getValue('layered_indexable') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$featureId = (int) $params['id_feature'];
|
||||
$formData = [
|
||||
'layered_indexable' => Tools::getValue('layered_indexable'),
|
||||
];
|
||||
|
||||
foreach (Language::getLanguages(false) as $language) {
|
||||
$langId = (int) $language['id_lang'];
|
||||
$seoUrl = Tools::getValue('url_name_' . $langId);
|
||||
$metaTitle = Tools::getValue('meta_title_' . $langId);
|
||||
|
||||
if (empty($seoUrl) && empty($metaTitle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$formData['meta_title'][$langId] = $metaTitle;
|
||||
$formData['url_name'][$langId] = $seoUrl;
|
||||
}
|
||||
|
||||
$this->save($featureId, $formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves feature form.
|
||||
*
|
||||
* @param int $featureId
|
||||
* @param array $formData
|
||||
*
|
||||
* @since PrestaShop 1.7.8.0
|
||||
*/
|
||||
private function save($featureId, array $formData)
|
||||
{
|
||||
$this->cleanLayeredIndexableTables($featureId);
|
||||
|
||||
$this->database->execute(
|
||||
'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_feature
|
||||
(`id_feature`, `indexable`)
|
||||
VALUES (' . (int) $featureId . ', ' . (int) $formData['layered_indexable'] . ')'
|
||||
);
|
||||
|
||||
$defaultLangId = (int) Configuration::get('PS_LANG_DEFAULT');
|
||||
$query = 'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value ' .
|
||||
'(`id_feature`, `id_lang`, `url_name`, `meta_title`) ' .
|
||||
'VALUES (%d, %d, \'%s\', \'%s\')';
|
||||
|
||||
foreach (Language::getLanguages(false) as $language) {
|
||||
$langId = (int) $language['id_lang'];
|
||||
$metaTitle = pSQL($formData['meta_title'][$langId]);
|
||||
$seoUrl = $formData['url_name'][$langId];
|
||||
$name = $formData['name'][$langId] ?: $formData['name'][$defaultLangId];
|
||||
|
||||
if (!empty($seoUrl)) {
|
||||
$seoUrl = pSQL(Tools::link_rewrite($seoUrl));
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
sprintf(
|
||||
$query,
|
||||
$featureId,
|
||||
$langId,
|
||||
$seoUrl,
|
||||
$metaTitle
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes from layered_indexable_feature and layered_indexable_feature_lang_value by feature id
|
||||
*
|
||||
* @param int $featureId
|
||||
*/
|
||||
private function cleanLayeredIndexableTables($featureId)
|
||||
{
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_feature
|
||||
WHERE `id_feature` = ' . $featureId
|
||||
);
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value
|
||||
WHERE `id_feature` = ' . $featureId
|
||||
);
|
||||
}
|
||||
}
|
||||
129
modules/ps_facetedsearch/src/Hook/FeatureValue.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use Language;
|
||||
use Tools;
|
||||
|
||||
class FeatureValue extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionFeatureValueSave',
|
||||
'actionFeatureValueDelete',
|
||||
'displayFeatureValueForm',
|
||||
'displayFeatureValuePostProcess',
|
||||
];
|
||||
|
||||
/**
|
||||
* After save feature value
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionFeatureValueSave(array $params)
|
||||
{
|
||||
if (empty($params['id_feature_value'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Removing all indexed language data for this attribute value id
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value
|
||||
WHERE `id_feature_value` = ' . (int) $params['id_feature_value']
|
||||
);
|
||||
|
||||
foreach (Language::getLanguages(false) as $language) {
|
||||
$seoUrl = Tools::getValue('url_name_' . (int) $language['id_lang']);
|
||||
$metaTitle = Tools::getValue('meta_title_' . (int) $language['id_lang']);
|
||||
if (empty($seoUrl) && empty($metaTitle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'INSERT INTO ' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value
|
||||
(`id_feature_value`, `id_lang`, `url_name`, `meta_title`)
|
||||
VALUES (
|
||||
' . (int) $params['id_feature_value'] . ', ' . (int) $language['id_lang'] . ',
|
||||
\'' . pSQL(Tools::link_rewrite($seoUrl)) . '\',
|
||||
\'' . pSQL($metaTitle, true) . '\')'
|
||||
);
|
||||
}
|
||||
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* After delete Feature value
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionFeatureValueDelete(array $params)
|
||||
{
|
||||
if (empty($params['id_feature_value'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->execute(
|
||||
'DELETE FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value
|
||||
WHERE `id_feature_value` = ' . (int) $params['id_feature_value']
|
||||
);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process feature value
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function displayFeatureValuePostProcess(array $params)
|
||||
{
|
||||
$this->module->checkLinksRewrite($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display feature value form
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function displayFeatureValueForm(array $params)
|
||||
{
|
||||
$values = [];
|
||||
|
||||
if ($result = $this->database->executeS(
|
||||
'SELECT `url_name`, `meta_title`, `id_lang`
|
||||
FROM ' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value
|
||||
WHERE `id_feature_value` = ' . (int) $params['id_feature_value']
|
||||
)) {
|
||||
foreach ($result as $data) {
|
||||
$values[$data['id_lang']] = ['url_name' => $data['url_name'], 'meta_title' => $data['meta_title']];
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'languages' => Language::getLanguages(false),
|
||||
'default_form_language' => (int) $this->context->controller->default_form_language,
|
||||
'values' => $values,
|
||||
]);
|
||||
|
||||
return $this->module->render('feature_value_form.tpl');
|
||||
}
|
||||
}
|
||||
44
modules/ps_facetedsearch/src/Hook/Product.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
class Product extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionProductSave',
|
||||
];
|
||||
|
||||
/**
|
||||
* After save product
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionProductSave(array $params)
|
||||
{
|
||||
if (empty($params['id_product'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->module->indexProductPrices((int) $params['id_product']);
|
||||
$this->module->indexAttributes((int) $params['id_product']);
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
}
|
||||
79
modules/ps_facetedsearch/src/Hook/ProductSearch.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
use PrestaShop\Module\FacetedSearch\Filters\Converter;
|
||||
use PrestaShop\Module\FacetedSearch\Filters\DataAccessor;
|
||||
use PrestaShop\Module\FacetedSearch\Product\SearchProvider;
|
||||
use PrestaShop\Module\FacetedSearch\URLSerializer;
|
||||
|
||||
class ProductSearch extends AbstractHook
|
||||
{
|
||||
const AVAILABLE_HOOKS = [
|
||||
'productSearchProvider',
|
||||
];
|
||||
|
||||
/**
|
||||
* Hook project search provider
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return SearchProvider|null
|
||||
*/
|
||||
public function productSearchProvider(array $params)
|
||||
{
|
||||
$query = $params['query'];
|
||||
// do something with query,
|
||||
// e.g. use $query->getIdCategory()
|
||||
// to choose a template for filters.
|
||||
// Query is an instance of:
|
||||
// PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery
|
||||
if ($query->getIdCategory()) {
|
||||
$this->context->controller->addJqueryUi('slider');
|
||||
$this->context->controller->registerStylesheet(
|
||||
'facetedsearch_front',
|
||||
'/modules/ps_facetedsearch/views/dist/front.css'
|
||||
);
|
||||
$this->context->controller->registerJavascript(
|
||||
'facetedsearch_front',
|
||||
'/modules/ps_facetedsearch/views/dist/front.js',
|
||||
['position' => 'bottom', 'priority' => 100]
|
||||
);
|
||||
|
||||
$urlSerializer = new URLSerializer();
|
||||
$dataAccessor = new DataAccessor($this->module->getDatabase());
|
||||
|
||||
return new SearchProvider(
|
||||
$this->module,
|
||||
new Converter(
|
||||
$this->module->getContext(),
|
||||
$this->module->getDatabase(),
|
||||
$urlSerializer,
|
||||
$dataAccessor
|
||||
),
|
||||
$urlSerializer,
|
||||
$dataAccessor
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
72
modules/ps_facetedsearch/src/Hook/SpecificPrice.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Hook;
|
||||
|
||||
class SpecificPrice extends AbstractHook
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $productsBefore = null;
|
||||
|
||||
const AVAILABLE_HOOKS = [
|
||||
'actionObjectSpecificPriceRuleUpdateBefore',
|
||||
'actionAdminSpecificPriceRuleControllerSaveAfter',
|
||||
];
|
||||
|
||||
/**
|
||||
* Before saving a specific price rule
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionObjectSpecificPriceRuleUpdateBefore(array $params)
|
||||
{
|
||||
if (empty($params['object']->id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \SpecificPriceRule */
|
||||
$specificPrice = $params['object'];
|
||||
$this->productsBefore = $specificPrice->getAffectedProducts();
|
||||
}
|
||||
|
||||
/**
|
||||
* After saving a specific price rule
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function actionAdminSpecificPriceRuleControllerSaveAfter(array $params)
|
||||
{
|
||||
if (empty($params['return']->id) || empty($this->productsBefore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \SpecificPriceRule */
|
||||
$specificPrice = $params['return'];
|
||||
$affectedProducts = array_merge($this->productsBefore, $specificPrice->getAffectedProducts());
|
||||
foreach ($affectedProducts as $product) {
|
||||
$this->module->indexProductPrices($product['id_product']);
|
||||
$this->module->indexAttributes($product['id_product']);
|
||||
}
|
||||
|
||||
$this->module->invalidateLayeredFilterBlockCache();
|
||||
}
|
||||
}
|
||||
113
modules/ps_facetedsearch/src/HookDispatcher.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch;
|
||||
|
||||
use Ps_Facetedsearch;
|
||||
|
||||
/**
|
||||
* Class works with Hook\AbstractHook instances in order to reduce ps_facetedsearch.php size.
|
||||
*
|
||||
* The dispatch method is called from the __call method in the module class.
|
||||
*/
|
||||
class HookDispatcher
|
||||
{
|
||||
const CLASSES = [
|
||||
Hook\Attribute::class,
|
||||
Hook\AttributeGroup::class,
|
||||
Hook\Category::class,
|
||||
Hook\Configuration::class,
|
||||
Hook\Design::class,
|
||||
Hook\Feature::class,
|
||||
Hook\FeatureValue::class,
|
||||
Hook\Product::class,
|
||||
Hook\ProductSearch::class,
|
||||
Hook\SpecificPrice::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of available hooks
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $availableHooks = [];
|
||||
|
||||
/**
|
||||
* Hook classes
|
||||
*
|
||||
* @var Hook\AbstractHook[]
|
||||
*/
|
||||
private $hooks = [];
|
||||
|
||||
/**
|
||||
* Module
|
||||
*
|
||||
* @var Ps_Facetedsearch
|
||||
*/
|
||||
private $module;
|
||||
|
||||
/**
|
||||
* Init hooks
|
||||
*
|
||||
* @param Ps_Facetedsearch $module
|
||||
*/
|
||||
public function __construct(Ps_Facetedsearch $module)
|
||||
{
|
||||
$this->module = $module;
|
||||
|
||||
foreach (self::CLASSES as $hookClass) {
|
||||
$hook = new $hookClass($this->module);
|
||||
$this->availableHooks = array_merge($this->availableHooks, $hook->getAvailableHooks());
|
||||
$this->hooks[] = $hook;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available hooks
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAvailableHooks()
|
||||
{
|
||||
return $this->availableHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find hook and dispatch it
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $params
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dispatch($hookName, array $params = [])
|
||||
{
|
||||
$hookName = preg_replace('~^hook~', '', $hookName);
|
||||
|
||||
foreach ($this->hooks as $hook) {
|
||||
if (method_exists($hook, $hookName)) {
|
||||
return call_user_func([$hook, $hookName], $params);
|
||||
}
|
||||
}
|
||||
|
||||
// No hook found, render it as a widget
|
||||
return $this->module->renderWidget($hookName, $params);
|
||||
}
|
||||
}
|
||||
332
modules/ps_facetedsearch/src/Product/Search.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Product;
|
||||
|
||||
use Category;
|
||||
use Configuration;
|
||||
use Context;
|
||||
use FrontController;
|
||||
use Group;
|
||||
use PrestaShop\Module\FacetedSearch\Adapter\AbstractAdapter;
|
||||
use PrestaShop\Module\FacetedSearch\Adapter\MySQL as MySQLAdapter;
|
||||
use Tools;
|
||||
|
||||
class Search
|
||||
{
|
||||
const STOCK_MANAGEMENT_FILTER = 'with_stock_management';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $psStockManagement;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $psOrderOutOfStock;
|
||||
|
||||
/**
|
||||
* @var AbstractAdapter
|
||||
*/
|
||||
protected $searchAdapter;
|
||||
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Search constructor.
|
||||
*
|
||||
* @param Context $context
|
||||
* @param string $adapterType
|
||||
*/
|
||||
public function __construct(Context $context, $adapterType = MySQLAdapter::TYPE)
|
||||
{
|
||||
$this->context = $context;
|
||||
|
||||
switch ($adapterType) {
|
||||
case MySQLAdapter::TYPE:
|
||||
default:
|
||||
$this->searchAdapter = new MySQLAdapter();
|
||||
}
|
||||
|
||||
if ($this->psStockManagement === null) {
|
||||
$this->psStockManagement = (bool) Configuration::get('PS_STOCK_MANAGEMENT');
|
||||
}
|
||||
|
||||
if ($this->psOrderOutOfStock === null) {
|
||||
$this->psOrderOutOfStock = (bool) Configuration::get('PS_ORDER_OUT_OF_STOCK');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractAdapter
|
||||
*/
|
||||
public function getSearchAdapter()
|
||||
{
|
||||
return $this->searchAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the initial population of the search filter
|
||||
*
|
||||
* @param array $selectedFilters
|
||||
*/
|
||||
public function initSearch($selectedFilters)
|
||||
{
|
||||
$homeCategory = Configuration::get('PS_HOME_CATEGORY');
|
||||
/* If the current category isn't defined or if it's homepage, we have nothing to display */
|
||||
$idParent = (int) Tools::getValue(
|
||||
'id_category',
|
||||
Tools::getValue('id_category_layered', $homeCategory)
|
||||
);
|
||||
|
||||
$parent = new Category((int) $idParent);
|
||||
|
||||
$psLayeredFullTree = Configuration::get('PS_LAYERED_FULL_TREE');
|
||||
if (!$psLayeredFullTree) {
|
||||
$this->addFilter('id_category', [$parent->id]);
|
||||
}
|
||||
|
||||
$psLayeredFilterByDefaultCategory = Configuration::get('PS_LAYERED_FILTER_BY_DEFAULT_CATEGORY');
|
||||
if ($psLayeredFilterByDefaultCategory) {
|
||||
$this->addFilter('id_category_default', [$parent->id]);
|
||||
}
|
||||
|
||||
// Visibility of a product must be in catalog or both (search & catalog)
|
||||
$this->addFilter('visibility', ['both', 'catalog']);
|
||||
|
||||
// User must belong to one of the groups that can access the product
|
||||
if (Group::isFeatureActive()) {
|
||||
$groups = FrontController::getCurrentCustomerGroups();
|
||||
|
||||
$this->addFilter('id_group', empty($groups) ? [Group::getCurrent()->id] : $groups);
|
||||
}
|
||||
|
||||
$this->addSearchFilters(
|
||||
$selectedFilters,
|
||||
$psLayeredFullTree ? $parent : null,
|
||||
(int) $this->context->shop->id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $selectedFilters
|
||||
* @param Category $parent
|
||||
* @param int $idShop
|
||||
*/
|
||||
private function addSearchFilters($selectedFilters, $parent, $idShop)
|
||||
{
|
||||
$hasCategory = false;
|
||||
foreach ($selectedFilters as $key => $filterValues) {
|
||||
if (!count($filterValues)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'id_feature':
|
||||
$operationsFilter = [];
|
||||
foreach ($filterValues as $featureId => $filterValue) {
|
||||
$this->getSearchAdapter()->addOperationsFilter(
|
||||
'with_features_' . $featureId,
|
||||
[[['id_feature_value', $filterValue]]]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id_attribute_group':
|
||||
$operationsFilter = [];
|
||||
foreach ($filterValues as $attributeId => $filterValue) {
|
||||
$this->getSearchAdapter()->addOperationsFilter(
|
||||
'with_attributes_' . $attributeId,
|
||||
[[['id_attribute', $filterValue]]]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'category':
|
||||
$this->addFilter('id_category', $filterValues);
|
||||
$this->getSearchAdapter()->resetFilter('id_category_default');
|
||||
$hasCategory = true;
|
||||
break;
|
||||
|
||||
case 'quantity':
|
||||
/*
|
||||
* $filterValues options can have following values:
|
||||
* 0 - Not available - 0 or less quantity and disabled backorders
|
||||
* 1 - Available - Positive quantity or enabled backorders
|
||||
* 2 - In stock - Positive quantity
|
||||
*/
|
||||
|
||||
// If all three values are checked, we show everything
|
||||
if (count($filterValues) == 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If stock management is deactivated, we show everything
|
||||
if (!$this->psStockManagement) {
|
||||
break;
|
||||
}
|
||||
|
||||
$operationsFilter = [];
|
||||
|
||||
// Simple cases with 1 option selected
|
||||
if (count($filterValues) == 1) {
|
||||
// Not available
|
||||
if ($filterValues[0] == 0) {
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '<='],
|
||||
['out_of_stock', $this->psOrderOutOfStock ? [0] : [0, 2], '='],
|
||||
];
|
||||
// Available
|
||||
} elseif ($filterValues[0] == 1) {
|
||||
$operationsFilter[] = [
|
||||
['out_of_stock', $this->psOrderOutOfStock ? [1, 2] : [1], '='],
|
||||
];
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '>'],
|
||||
];
|
||||
// In stock
|
||||
} elseif ($filterValues[0] == 2) {
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '>'],
|
||||
];
|
||||
}
|
||||
// Cases with 2 options selected
|
||||
} elseif (count($filterValues) == 2) {
|
||||
// Not available and available, we show everything
|
||||
if (in_array(0, $filterValues) && in_array(1, $filterValues)) {
|
||||
break;
|
||||
// Not available or in stock
|
||||
} elseif (in_array(0, $filterValues) && in_array(2, $filterValues)) {
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '<='],
|
||||
['out_of_stock', $this->psOrderOutOfStock ? [0] : [0, 2], '='],
|
||||
];
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '>'],
|
||||
];
|
||||
// Available or in stock
|
||||
} elseif (in_array(1, $filterValues) && in_array(2, $filterValues)) {
|
||||
$operationsFilter[] = [
|
||||
['out_of_stock', $this->psOrderOutOfStock ? [1, 2] : [1], '='],
|
||||
];
|
||||
$operationsFilter[] = [
|
||||
['quantity', [0], '>'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getSearchAdapter()->addOperationsFilter(
|
||||
self::STOCK_MANAGEMENT_FILTER,
|
||||
$operationsFilter
|
||||
);
|
||||
break;
|
||||
|
||||
case 'manufacturer':
|
||||
$this->addFilter('id_manufacturer', $filterValues);
|
||||
break;
|
||||
|
||||
case 'condition':
|
||||
if (count($selectedFilters['condition']) == 3) {
|
||||
break;
|
||||
}
|
||||
$this->addFilter('condition', $filterValues);
|
||||
break;
|
||||
|
||||
case 'weight':
|
||||
if (!empty($selectedFilters['weight'][0]) || !empty($selectedFilters['weight'][1])) {
|
||||
$this->getSearchAdapter()->addFilter(
|
||||
'weight',
|
||||
[(float) $selectedFilters['weight'][0]],
|
||||
'>='
|
||||
);
|
||||
$this->getSearchAdapter()->addFilter(
|
||||
'weight',
|
||||
[(float) $selectedFilters['weight'][1]],
|
||||
'<='
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'price':
|
||||
if (isset($selectedFilters['price'])
|
||||
&& (
|
||||
$selectedFilters['price'][0] !== '' || $selectedFilters['price'][1] !== ''
|
||||
)
|
||||
) {
|
||||
$this->addPriceFilter(
|
||||
(float) $selectedFilters['price'][0],
|
||||
(float) $selectedFilters['price'][1]
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasCategory && $parent !== null) {
|
||||
$this->getSearchAdapter()->addFilter('nleft', [$parent->nleft], '>=');
|
||||
$this->getSearchAdapter()->addFilter('nright', [$parent->nright], '<=');
|
||||
}
|
||||
|
||||
$this->getSearchAdapter()->addFilter('id_shop', [$idShop]);
|
||||
$this->getSearchAdapter()->addGroupBy('id_product');
|
||||
|
||||
$this->getSearchAdapter()->useFiltersAsInitialPopulation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter with the filterValues extracted from the selectedFilters
|
||||
*
|
||||
* @param string $filterName
|
||||
* @param array $filterValues
|
||||
*/
|
||||
public function addFilter($filterName, array $filterValues)
|
||||
{
|
||||
$values = [];
|
||||
foreach ($filterValues as $filterValue) {
|
||||
if (is_array($filterValue)) {
|
||||
foreach ($filterValue as $subFilterValue) {
|
||||
$values[] = (int) $subFilterValue;
|
||||
}
|
||||
} else {
|
||||
$values[] = $filterValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($values)) {
|
||||
$this->getSearchAdapter()->addFilter($filterName, $values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a price filter
|
||||
*
|
||||
* @param float $minPrice
|
||||
* @param float $maxPrice
|
||||
*/
|
||||
private function addPriceFilter($minPrice, $maxPrice)
|
||||
{
|
||||
$this->getSearchAdapter()->addFilter('price_min', [$maxPrice], '<=');
|
||||
$this->getSearchAdapter()->addFilter('price_max', [$minPrice], '>=');
|
||||
}
|
||||
}
|
||||
38
modules/ps_facetedsearch/src/Product/SearchFactory.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Product;
|
||||
|
||||
use Context;
|
||||
|
||||
class SearchFactory
|
||||
{
|
||||
/**
|
||||
* Returns an instance of Search for this context
|
||||
*
|
||||
* @param Context $context
|
||||
*
|
||||
* @return Search
|
||||
*/
|
||||
public function build(Context $context)
|
||||
{
|
||||
return new Search($context);
|
||||
}
|
||||
}
|
||||
522
modules/ps_facetedsearch/src/Product/SearchProvider.php
Normal file
@@ -0,0 +1,522 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch\Product;
|
||||
|
||||
use Configuration;
|
||||
use PrestaShop\Module\FacetedSearch\Filters;
|
||||
use PrestaShop\Module\FacetedSearch\URLSerializer;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\FacetCollection;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\FacetsRendererInterface;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchContext;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchProviderInterface;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchResult;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
|
||||
use Ps_Facetedsearch;
|
||||
use Tools;
|
||||
|
||||
class SearchProvider implements FacetsRendererInterface, ProductSearchProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var Ps_Facetedsearch
|
||||
*/
|
||||
private $module;
|
||||
|
||||
/**
|
||||
* @var Filters\Converter
|
||||
*/
|
||||
private $filtersConverter;
|
||||
|
||||
/**
|
||||
* @var Filters\DataAccessor
|
||||
*/
|
||||
private $dataAccessor;
|
||||
|
||||
/**
|
||||
* @var URLSerializer
|
||||
*/
|
||||
private $urlSerializer;
|
||||
|
||||
/**
|
||||
* @var SearchFactory
|
||||
*/
|
||||
private $searchFactory;
|
||||
|
||||
public function __construct(
|
||||
Ps_Facetedsearch $module,
|
||||
Filters\Converter $converter,
|
||||
URLSerializer $serializer,
|
||||
Filters\DataAccessor $dataAccessor,
|
||||
SearchFactory $searchFactory = null
|
||||
) {
|
||||
$this->module = $module;
|
||||
$this->filtersConverter = $converter;
|
||||
$this->urlSerializer = $serializer;
|
||||
$this->dataAccessor = $dataAccessor;
|
||||
$this->searchFactory = $searchFactory === null ? new SearchFactory() : $searchFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getAvailableSortOrders()
|
||||
{
|
||||
$sortSalesDesc = new SortOrder('product', 'sales', 'desc');
|
||||
$sortPosAsc = new SortOrder('product', 'position', 'asc');
|
||||
$sortNameAsc = new SortOrder('product', 'name', 'asc');
|
||||
$sortNameDesc = new SortOrder('product', 'name', 'desc');
|
||||
$sortPriceAsc = new SortOrder('product', 'price', 'asc');
|
||||
$sortPriceDesc = new SortOrder('product', 'price', 'desc');
|
||||
$translator = $this->module->getTranslator();
|
||||
|
||||
return [
|
||||
$sortSalesDesc->setLabel(
|
||||
$translator->trans('Best sellers', [], 'Modules.Facetedsearch.Shop')
|
||||
),
|
||||
$sortPosAsc->setLabel(
|
||||
$translator->trans('Relevance', [], 'Modules.Facetedsearch.Shop')
|
||||
),
|
||||
$sortNameAsc->setLabel(
|
||||
$translator->trans('Name, A to Z', [], 'Shop.Theme.Catalog')
|
||||
),
|
||||
$sortNameDesc->setLabel(
|
||||
$translator->trans('Name, Z to A', [], 'Shop.Theme.Catalog')
|
||||
),
|
||||
$sortPriceAsc->setLabel(
|
||||
$translator->trans('Price, low to high', [], 'Shop.Theme.Catalog')
|
||||
),
|
||||
$sortPriceDesc->setLabel(
|
||||
$translator->trans('Price, high to low', [], 'Shop.Theme.Catalog')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProductSearchContext $context
|
||||
* @param ProductSearchQuery $query
|
||||
*
|
||||
* @return ProductSearchResult
|
||||
*/
|
||||
public function runQuery(
|
||||
ProductSearchContext $context,
|
||||
ProductSearchQuery $query
|
||||
) {
|
||||
$result = new ProductSearchResult();
|
||||
// extract the filter array from the Search query
|
||||
$facetedSearchFilters = $this->filtersConverter->createFacetedSearchFiltersFromQuery($query);
|
||||
|
||||
$context = $this->module->getContext();
|
||||
$facetedSearch = $this->searchFactory->build($context);
|
||||
// init the search with the initial population associated with the current filters
|
||||
$facetedSearch->initSearch($facetedSearchFilters);
|
||||
|
||||
$orderBy = $query->getSortOrder()->toLegacyOrderBy(false);
|
||||
$orderWay = $query->getSortOrder()->toLegacyOrderWay();
|
||||
|
||||
$filterProductSearch = new Filters\Products($facetedSearch);
|
||||
|
||||
// get the product associated with the current filter
|
||||
$productsAndCount = $filterProductSearch->getProductByFilters(
|
||||
$query->getResultsPerPage(),
|
||||
$query->getPage(),
|
||||
$orderBy,
|
||||
$orderWay,
|
||||
$facetedSearchFilters
|
||||
);
|
||||
|
||||
$result
|
||||
->setProducts($productsAndCount['products'])
|
||||
->setTotalProductsCount($productsAndCount['count'])
|
||||
->setAvailableSortOrders($this->getAvailableSortOrders());
|
||||
|
||||
// now get the filter blocks associated with the current search
|
||||
$filterBlockSearch = new Filters\Block(
|
||||
$facetedSearch->getSearchAdapter(),
|
||||
$context,
|
||||
$this->module->getDatabase(),
|
||||
$this->dataAccessor
|
||||
);
|
||||
|
||||
$idShop = (int) $context->shop->id;
|
||||
$idLang = (int) $context->language->id;
|
||||
$idCurrency = (int) $context->currency->id;
|
||||
$idCountry = (int) $context->country->id;
|
||||
$idCategory = (int) $query->getIdCategory();
|
||||
|
||||
$filterHash = md5(
|
||||
sprintf(
|
||||
'%d-%d-%d-%d-%d-%s',
|
||||
$idShop,
|
||||
$idCurrency,
|
||||
$idLang,
|
||||
$idCategory,
|
||||
$idCountry,
|
||||
serialize($facetedSearchFilters)
|
||||
)
|
||||
);
|
||||
|
||||
$filterBlock = $filterBlockSearch->getFromCache($filterHash);
|
||||
if (empty($filterBlock)) {
|
||||
$filterBlock = $filterBlockSearch->getFilterBlock($productsAndCount['count'], $facetedSearchFilters);
|
||||
$filterBlockSearch->insertIntoCache($filterHash, $filterBlock);
|
||||
}
|
||||
|
||||
$facets = $this->filtersConverter->getFacetsFromFilterBlocks(
|
||||
$filterBlock['filters']
|
||||
);
|
||||
|
||||
$this->labelRangeFilters($facets);
|
||||
$this->addEncodedFacetsToFilters($facets);
|
||||
$this->hideUselessFacets($facets, (int) $result->getTotalProductsCount());
|
||||
|
||||
$facetCollection = new FacetCollection();
|
||||
$nextMenu = $facetCollection->setFacets($facets);
|
||||
$result->setFacetCollection($nextMenu);
|
||||
|
||||
$facetFilters = $this->urlSerializer->getActiveFacetFiltersFromFacets($facets);
|
||||
$result->setEncodedFacets($this->urlSerializer->serialize($facetFilters));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an product search result.
|
||||
*
|
||||
* @param ProductSearchContext $context
|
||||
* @param ProductSearchResult $result
|
||||
*
|
||||
* @return string the HTML of the facets
|
||||
*/
|
||||
public function renderFacets(ProductSearchContext $context, ProductSearchResult $result)
|
||||
{
|
||||
list($activeFilters, $displayedFacets, $facetsVar) = $this->prepareActiveFiltersForRender($context, $result);
|
||||
|
||||
// No need to render without facets
|
||||
if (empty($facetsVar)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->module->getContext()->smarty->assign(
|
||||
[
|
||||
'show_quantities' => Configuration::get('PS_LAYERED_SHOW_QTIES'),
|
||||
'facets' => $facetsVar,
|
||||
'js_enabled' => $this->module->isAjax(),
|
||||
'displayedFacets' => $displayedFacets,
|
||||
'activeFilters' => $activeFilters,
|
||||
'sort_order' => $result->getCurrentSortOrder()->toString(),
|
||||
'clear_all_link' => $this->updateQueryString(
|
||||
[
|
||||
'q' => null,
|
||||
'page' => null,
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->module->fetch(
|
||||
'module:ps_facetedsearch/views/templates/front/catalog/facets.tpl'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an product search result of active filters.
|
||||
*
|
||||
* @param ProductSearchContext $context
|
||||
* @param ProductSearchResult $result
|
||||
*
|
||||
* @return string the HTML of the facets
|
||||
*/
|
||||
public function renderActiveFilters(ProductSearchContext $context, ProductSearchResult $result)
|
||||
{
|
||||
list($activeFilters) = $this->prepareActiveFiltersForRender($context, $result);
|
||||
|
||||
$this->module->getContext()->smarty->assign(
|
||||
[
|
||||
'activeFilters' => $activeFilters,
|
||||
'clear_all_link' => $this->updateQueryString(
|
||||
[
|
||||
'q' => null,
|
||||
'page' => null,
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->module->fetch(
|
||||
'module:ps_facetedsearch/views/templates/front/catalog/active-filters.tpl'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare active filters for renderer.
|
||||
*
|
||||
* @param ProductSearchContext $context
|
||||
* @param ProductSearchResult $result
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function prepareActiveFiltersForRender(ProductSearchContext $context, ProductSearchResult $result)
|
||||
{
|
||||
$facetCollection = $result->getFacetCollection();
|
||||
|
||||
// not all search providers generate menus
|
||||
if (empty($facetCollection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$facetsVar = array_map(
|
||||
[$this, 'prepareFacetForTemplate'],
|
||||
$facetCollection->getFacets()
|
||||
);
|
||||
|
||||
$displayedFacets = [];
|
||||
$activeFilters = [];
|
||||
foreach ($facetsVar as $idx => $facet) {
|
||||
// Remove undisplayed facets
|
||||
if (!empty($facet['displayed'])) {
|
||||
$displayedFacets[] = $facet;
|
||||
}
|
||||
|
||||
// Check if a filter is active
|
||||
foreach ($facet['filters'] as $filter) {
|
||||
if ($filter['active']) {
|
||||
$activeFilters[] = $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$activeFilters,
|
||||
$displayedFacets,
|
||||
$facetsVar,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Facet to an array with all necessary
|
||||
* information for templating.
|
||||
*
|
||||
* @param Facet $facet
|
||||
*
|
||||
* @return array ready for templating
|
||||
*/
|
||||
protected function prepareFacetForTemplate(Facet $facet)
|
||||
{
|
||||
$facetsArray = $facet->toArray();
|
||||
foreach ($facetsArray['filters'] as &$filter) {
|
||||
$filter['facetLabel'] = $facet->getLabel();
|
||||
if ($filter['nextEncodedFacets'] || $facet->getWidgetType() === 'slider') {
|
||||
$filter['nextEncodedFacetsURL'] = $this->updateQueryString([
|
||||
'q' => $filter['nextEncodedFacets'],
|
||||
'page' => null,
|
||||
]);
|
||||
} else {
|
||||
$filter['nextEncodedFacetsURL'] = $this->updateQueryString([
|
||||
'q' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
unset($filter);
|
||||
|
||||
return $facetsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a label associated with the facets
|
||||
*
|
||||
* @param array $facets
|
||||
*/
|
||||
private function labelRangeFilters(array $facets)
|
||||
{
|
||||
$context = $this->module->getContext();
|
||||
|
||||
foreach ($facets as $facet) {
|
||||
if (!in_array($facet->getType(), Filters\Converter::RANGE_FILTERS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($facet->getFilters() as $filter) {
|
||||
$filterValue = $filter->getValue();
|
||||
$min = empty($filterValue[0]) ? $facet->getProperty('min') : $filterValue[0];
|
||||
$max = empty($filterValue[1]) ? $facet->getProperty('max') : $filterValue[1];
|
||||
if ($facet->getType() === 'weight') {
|
||||
$unit = Configuration::get('PS_WEIGHT_UNIT');
|
||||
$filter->setLabel(
|
||||
sprintf(
|
||||
'%1$s%2$s - %3$s%4$s',
|
||||
Tools::displayNumber($min),
|
||||
$unit,
|
||||
Tools::displayNumber($max),
|
||||
$unit
|
||||
)
|
||||
);
|
||||
} elseif ($facet->getType() === 'price') {
|
||||
$filter->setLabel(
|
||||
sprintf(
|
||||
'%1$s - %2$s',
|
||||
$context->getCurrentLocale()->formatPrice($min, $context->currency->iso_code),
|
||||
$context->getCurrentLocale()->formatPrice($max, $context->currency->iso_code)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a URL stub for each filter inside the given facets
|
||||
* and assigns this stub to the filters.
|
||||
* The URL stub is called 'nextEncodedFacets' because it is used
|
||||
* to generate the URL of the search once a filter is activated.
|
||||
*/
|
||||
private function addEncodedFacetsToFilters(array $facets)
|
||||
{
|
||||
// first get the currently active facetFilter in an array
|
||||
$originalFacetFilters = $this->urlSerializer->getActiveFacetFiltersFromFacets($facets);
|
||||
|
||||
foreach ($facets as $facet) {
|
||||
$activeFacetFilters = $originalFacetFilters;
|
||||
// If only one filter can be selected, we keep track of
|
||||
// the current active filter to disable it before generating the url stub
|
||||
// and not select two filters in a facet that can have only one active filter.
|
||||
if (!$facet->isMultipleSelectionAllowed() && !$facet->getProperty('range')) {
|
||||
foreach ($facet->getFilters() as $filter) {
|
||||
if ($filter->isActive()) {
|
||||
// we have a currently active filter is the facet, remove it from the facetFilter array
|
||||
$activeFacetFilters = $this->urlSerializer->removeFilterFromFacetFilters(
|
||||
$originalFacetFilters,
|
||||
$filter,
|
||||
$facet
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($facet->getFilters() as $filter) {
|
||||
// toggle the current filter
|
||||
if ($filter->isActive() || $facet->getProperty('range')) {
|
||||
$facetFilters = $this->urlSerializer->removeFilterFromFacetFilters(
|
||||
$activeFacetFilters,
|
||||
$filter,
|
||||
$facet
|
||||
);
|
||||
} else {
|
||||
$facetFilters = $this->urlSerializer->addFilterToFacetFilters(
|
||||
$activeFacetFilters,
|
||||
$filter,
|
||||
$facet
|
||||
);
|
||||
}
|
||||
|
||||
// We've toggled the filter, so the call to serialize
|
||||
// returns the "URL" for the search when user has toggled
|
||||
// the filter.
|
||||
$filter->setNextEncodedFacets(
|
||||
$this->urlSerializer->serialize($facetFilters)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the facet when there's only 1 result.
|
||||
* Keep facet status when it's a slider
|
||||
*
|
||||
* @param array $facets
|
||||
* @param int $totalProducts
|
||||
*/
|
||||
private function hideUselessFacets(array $facets, $totalProducts)
|
||||
{
|
||||
foreach ($facets as $facet) {
|
||||
if ($facet->getWidgetType() === 'slider') {
|
||||
$facet->setDisplayed(
|
||||
$facet->getProperty('min') != $facet->getProperty('max')
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalFacetProducts = 0;
|
||||
$usefulFiltersCount = 0;
|
||||
foreach ($facet->getFilters() as $filter) {
|
||||
if ($filter->getMagnitude() > 0 && $filter->isDisplayed()) {
|
||||
$totalFacetProducts += $filter->getMagnitude();
|
||||
++$usefulFiltersCount;
|
||||
}
|
||||
}
|
||||
|
||||
$facet->setDisplayed(
|
||||
// There are two filters displayed
|
||||
$usefulFiltersCount > 1
|
||||
||
|
||||
/*
|
||||
* There is only one fitler and the
|
||||
* magnitude is different than the
|
||||
* total products
|
||||
*/
|
||||
(
|
||||
count($facet->getFilters()) === 1
|
||||
&& $totalFacetProducts < $totalProducts
|
||||
&& $usefulFiltersCount > 0
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a URL corresponding to the current page but
|
||||
* with the query string altered.
|
||||
*
|
||||
* Params from $extraParams that have a null value are stripped,
|
||||
* and other params are added. Params not in $extraParams are unchanged.
|
||||
*/
|
||||
private function updateQueryString(array $extraParams = [])
|
||||
{
|
||||
$uriWithoutParams = explode('?', $_SERVER['REQUEST_URI'])[0];
|
||||
$url = Tools::getCurrentUrlProtocolPrefix() . $_SERVER['HTTP_HOST'] . $uriWithoutParams;
|
||||
$params = [];
|
||||
$paramsFromUri = '';
|
||||
if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
|
||||
$paramsFromUri = explode('?', $_SERVER['REQUEST_URI'])[1];
|
||||
}
|
||||
parse_str($paramsFromUri, $params);
|
||||
|
||||
foreach ($extraParams as $key => $value) {
|
||||
if (null === $value) {
|
||||
// Force clear param if null value is passed
|
||||
unset($params[$key]);
|
||||
} else {
|
||||
$params[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($params as $key => $param) {
|
||||
if (null === $param || '' === $param) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$queryString = str_replace('%2F', '/', http_build_query($params, '', '&'));
|
||||
|
||||
return $url . ($queryString ? "?$queryString" : '');
|
||||
}
|
||||
}
|
||||
245
modules/ps_facetedsearch/src/URLSerializer.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
|
||||
namespace PrestaShop\Module\FacetedSearch;
|
||||
|
||||
use PrestaShop\Module\FacetedSearch\Filters\Converter;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
|
||||
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
|
||||
|
||||
class URLSerializer
|
||||
{
|
||||
/**
|
||||
* Add filter
|
||||
*
|
||||
* @param array $facetFilters
|
||||
* @param Filter $facetFilter
|
||||
* @param Facet $facet
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function addFilterToFacetFilters(array $facetFilters, Filter $facetFilter, Facet $facet)
|
||||
{
|
||||
$facetLabel = $this->getFacetLabel($facet);
|
||||
$filterLabel = $this->getFilterLabel($facetFilter);
|
||||
|
||||
if ($facet->getProperty('range')) {
|
||||
$facetValue = $facet->getProperty('values');
|
||||
$facetFilters[$facetLabel] = [
|
||||
$facetFilter->getProperty('symbol'),
|
||||
isset($facetValue[0]) ? $facetValue[0] : $facet->getProperty('min'),
|
||||
isset($facetValue[1]) ? $facetValue[1] : $facet->getProperty('max'),
|
||||
];
|
||||
} else {
|
||||
$facetFilters[$facetLabel][$filterLabel] = $filterLabel;
|
||||
}
|
||||
|
||||
return $facetFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove filter
|
||||
*
|
||||
* @param array $facetFilters
|
||||
* @param Filter $facetFilter
|
||||
* @param Facet $facet
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function removeFilterFromFacetFilters(array $facetFilters, Filter $facetFilter, $facet)
|
||||
{
|
||||
$facetLabel = $this->getFacetLabel($facet);
|
||||
|
||||
if ($facet->getProperty('range')) {
|
||||
unset($facetFilters[$facetLabel]);
|
||||
} else {
|
||||
$filterLabel = $this->getFilterLabel($facetFilter);
|
||||
unset($facetFilters[$facetLabel][$filterLabel]);
|
||||
if (empty($facetFilters[$facetLabel])) {
|
||||
unset($facetFilters[$facetLabel]);
|
||||
}
|
||||
}
|
||||
|
||||
return $facetFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active facet filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getActiveFacetFiltersFromFacets(array $facets)
|
||||
{
|
||||
$facetFilters = [];
|
||||
foreach ($facets as $facet) {
|
||||
foreach ($facet->getFilters() as $facetFilter) {
|
||||
if (!$facetFilter->isActive()) {
|
||||
// Filter is not active
|
||||
continue;
|
||||
}
|
||||
|
||||
$facetLabel = $this->getFacetLabel($facet);
|
||||
$filterLabel = $this->getFilterLabel($facetFilter);
|
||||
if (!$facet->getProperty('range')) {
|
||||
$facetFilters[$facetLabel][$filterLabel] = $filterLabel;
|
||||
continue;
|
||||
}
|
||||
|
||||
$facetValue = $facetFilter->getValue();
|
||||
$facetFilters[$facetLabel] = [
|
||||
$facetFilter->getProperty('symbol'),
|
||||
$facetValue[0],
|
||||
$facetValue[1],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $facetFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Facet label
|
||||
*
|
||||
* @param Facet $facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFacetLabel(Facet $facet)
|
||||
{
|
||||
if ($facet->getProperty(Converter::PROPERTY_URL_NAME) !== null) {
|
||||
return $facet->getProperty(Converter::PROPERTY_URL_NAME);
|
||||
}
|
||||
|
||||
return $facet->getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Facet Filter label
|
||||
*
|
||||
* @param Filter $facetFilter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFilterLabel(Filter $facetFilter)
|
||||
{
|
||||
if ($facetFilter->getProperty(Converter::PROPERTY_URL_NAME) !== null) {
|
||||
return $facetFilter->getProperty(Converter::PROPERTY_URL_NAME);
|
||||
}
|
||||
|
||||
return $facetFilter->getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fragment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(array $fragment)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($fragment as $key => $values) {
|
||||
array_unshift($values, $key);
|
||||
$parts[] = $this->serializeListOfStrings($values, '-');
|
||||
}
|
||||
|
||||
return $this->serializeListOfStrings($parts, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function unserialize($string)
|
||||
{
|
||||
$fragment = [];
|
||||
$parts = $this->unserializeListOfStrings($string, '/');
|
||||
foreach ($parts as $part) {
|
||||
$values = $this->unserializeListOfStrings($part, '-');
|
||||
$key = array_shift($values);
|
||||
$fragment[$key] = $values;
|
||||
}
|
||||
|
||||
return $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $separator the string separator
|
||||
* @param string $escape the string escape
|
||||
* @param array $list
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function serializeListOfStrings($list, $separator, $escape = '\\')
|
||||
{
|
||||
return implode($separator, array_map(function ($item) use ($separator, $escape) {
|
||||
return strtr(
|
||||
$item,
|
||||
[
|
||||
$separator => $escape . $separator,
|
||||
]
|
||||
);
|
||||
}, $list));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $separator the string separator
|
||||
* @param string $escape the string escape
|
||||
* @param string $string the UTF8 string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function unserializeListOfStrings($string, $separator, $escape = '\\')
|
||||
{
|
||||
$list = [];
|
||||
$currentString = '';
|
||||
$escaping = false;
|
||||
|
||||
// get UTF-8 chars, inspired from http://stackoverflow.com/questions/9438158/split-utf8-string-into-array-of-chars
|
||||
$arrayOfCharacters = [];
|
||||
preg_match_all('/./u', $string, $arrayOfCharacters);
|
||||
$characters = $arrayOfCharacters[0];
|
||||
|
||||
foreach ($characters as $index => $character) {
|
||||
if ($character === $escape
|
||||
&& isset($characters[$index + 1])
|
||||
&& $characters[$index + 1] === $separator
|
||||
) {
|
||||
$escaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($character === $separator && $escaping === false) {
|
||||
$list[] = $currentString;
|
||||
$currentString = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentString .= $character;
|
||||
$escaping = false;
|
||||
}
|
||||
|
||||
if ('' !== $currentString) {
|
||||
$list[] = $currentString;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
0
modules/ps_facetedsearch/translations/pl.php
Normal file
64
modules/ps_facetedsearch/upgrade/upgrade-3.0.0.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_0_0(Ps_Facetedsearch $module)
|
||||
{
|
||||
// Clear legacy hook names
|
||||
$oldHooks = [
|
||||
'categoryAddition',
|
||||
'categoryUpdate',
|
||||
'attributeGroupForm',
|
||||
'afterSaveAttributeGroup',
|
||||
'afterDeleteAttributeGroup',
|
||||
'featureForm',
|
||||
'afterDeleteFeature',
|
||||
'afterSaveFeature',
|
||||
'categoryDeletion',
|
||||
'afterSaveProduct',
|
||||
'postProcessAttributeGroup',
|
||||
'postProcessFeature',
|
||||
'featureValueForm',
|
||||
'postProcessFeatureValue',
|
||||
'afterDeleteFeatureValue',
|
||||
'afterSaveFeatureValue',
|
||||
'attributeForm',
|
||||
'postProcessAttribute',
|
||||
'afterDeleteAttribute',
|
||||
'afterSaveAttribute',
|
||||
'productSearchProvider',
|
||||
'displayLeftColumn',
|
||||
];
|
||||
|
||||
foreach ($oldHooks as $hookName) {
|
||||
$module->unregisterHook($hookName);
|
||||
}
|
||||
|
||||
// These methods have no return value
|
||||
// If something failed an exception will be raised and
|
||||
// the upgrade will stop
|
||||
$module->rebuildLayeredStructure();
|
||||
$module->rebuildPriceIndexTable();
|
||||
$module->invalidateLayeredFilterBlockCache();
|
||||
|
||||
return $module->registerHook($module->getHookDispatcher()->getAvailableHooks());
|
||||
}
|
||||
43
modules/ps_facetedsearch/upgrade/upgrade-3.0.3.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_0_3(Ps_Facetedsearch $module)
|
||||
{
|
||||
// Clear legacy hook names
|
||||
$oldHooks = [
|
||||
'afterDeleteFeatureValue',
|
||||
'afterSaveFeatureValue',
|
||||
'postProcessFeatureValue',
|
||||
];
|
||||
foreach ($oldHooks as $hookName) {
|
||||
$module->unregisterHook($hookName);
|
||||
}
|
||||
|
||||
$newHooks = [
|
||||
'actionFeatureSave',
|
||||
'actionFeatureValueDelete',
|
||||
'displayFeatureValuePostProcess',
|
||||
];
|
||||
|
||||
return $module->registerHook($newHooks);
|
||||
}
|
||||
33
modules/ps_facetedsearch/upgrade/upgrade-3.3.0.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_3_0(Ps_Facetedsearch $module)
|
||||
{
|
||||
// These methods have no return value
|
||||
// If something failed an exception will be raised and
|
||||
// the upgrade will stop
|
||||
$module->rebuildPriceIndexTable();
|
||||
$module->invalidateLayeredFilterBlockCache();
|
||||
|
||||
return $module->registerHook($module->getHookDispatcher()->getAvailableHooks());
|
||||
}
|
||||
33
modules/ps_facetedsearch/upgrade/upgrade-3.4.0.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_4_0(Ps_Facetedsearch $module)
|
||||
{
|
||||
$newHooks = [
|
||||
'actionFeatureFormBuilderModifier',
|
||||
'actionAfterCreateFeatureFormHandler',
|
||||
'actionAfterUpdateFeatureFormHandler',
|
||||
];
|
||||
|
||||
return $module->registerHook($newHooks);
|
||||
}
|
||||
66
modules/ps_facetedsearch/upgrade/upgrade-3.4.1.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes files or directories.
|
||||
*
|
||||
* @param array $files An array of files to remove
|
||||
*
|
||||
* @return true|string True if everything goes fine, error details otherwise
|
||||
*/
|
||||
function removeFromFsDuringUpgrade(array $files)
|
||||
{
|
||||
$files = array_reverse($files);
|
||||
foreach ($files as $file) {
|
||||
if (is_dir($file)) {
|
||||
$iterator = new FilesystemIterator($file, FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS);
|
||||
removeFromFsDuringUpgrade(iterator_to_array($iterator));
|
||||
if (!rmdir($file) && file_exists($file)) {
|
||||
return 'Deletion of directory ' . $file . 'failed';
|
||||
}
|
||||
} elseif (!unlink($file) && file_exists($file)) {
|
||||
return 'Deletion of file ' . $file . 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* This upgrade file removes the folder vendor/phpunit, when added from a previous release installed on the shop.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function upgrade_module_3_4_1($module)
|
||||
{
|
||||
$path = __DIR__ . '/../vendor/phpunit';
|
||||
if (file_exists($path)) {
|
||||
$result = removeFromFsDuringUpgrade([$path]);
|
||||
if ($result !== true) {
|
||||
PrestaShopLogger::addLog('Could not delete PHPUnit from module. ' . $result, 3);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
29
modules/ps_facetedsearch/upgrade/upgrade-3.6.0.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_6_0($module)
|
||||
{
|
||||
Configuration::updateValue('PS_LAYERED_CACHE_ENABLED', 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
32
modules/ps_facetedsearch/upgrade/upgrade-3.8.0.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_3_8_0($module)
|
||||
{
|
||||
$module->registerHook('actionProductPreferencesPageStockSave');
|
||||
|
||||
return Db::getInstance()->execute(
|
||||
'ALTER TABLE `' . _DB_PREFIX_ . 'layered_price_index`
|
||||
CHANGE `price_min` `price_min` decimal(20,6) NOT NULL,
|
||||
CHANGE `price_max` `price_max` decimal(20,6) NOT NULL;');
|
||||
}
|
||||
4
modules/ps_facetedsearch/views/dist/back.css
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.bootstrap .filter_list .filter_list_item{display:table;width:100%;padding:5px 0;margin-bottom:4px;background-color:white;-webkit-box-shadow:rgba(0,0,0,0.3) 0 0 3px,rgba(0,0,0,0.1) 0 -2px 0 inset;box-shadow:rgba(0,0,0,0.3) 0 0 3px,rgba(0,0,0,0.1) 0 -2px 0 inset;-webkit-border-radius:3px;-moz-border-radius:3px;-ms-border-radius:3px;-o-border-radius:3px;border-radius:3px;cursor:pointer}.bootstrap .filter_panel{min-height:20px;padding:7px 7px 0px 7px;margin-bottom:20px;background-color:#ebebeb;border:1px solid #d9d9d9;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.bootstrap .filter_panel header{margin-bottom:7px}.bootstrap .prestashop-switch span{display:none}
|
||||
|
||||
|
||||
/*# sourceMappingURL=back.css.map*/
|
||||
2
modules/ps_facetedsearch/views/dist/back.js
vendored
Normal file
8
modules/ps_facetedsearch/views/dist/front.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.faceted-overlay{left:0;top:0;width:100%;height:100%;position:fixed;background-color:rgba(25,25,25,0.5);z-index:100}.faceted-overlay .overlay__inner{left:0;top:0;width:100%;height:100%;position:absolute}.faceted-overlay .overlay__content{left:50%;position:absolute;top:50%;transform:translate(-50%, -50%)}.faceted-overlay .spinner{width:75px;height:75px;display:inline-block;border-width:2px;border-color:rgba(255,255,255,0.05);border-top-color:#fff;animation:spin 1s infinite linear;border-radius:100%;border-style:solid}@keyframes spin{100%{transform:rotate(360deg)}}
|
||||
|
||||
#search_filters .ui-slider-horizontal .ui-slider-handle{margin-left:-1px;cursor:pointer}#search_filters .ui-widget-header{background:#555}#search_filters .ui-slider .ui-slider-handle{top:-.45em;width:0.4em;background:#fff;border:1px solid #555}#search_filters .ui-slider-horizontal{height:.4em}
|
||||
|
||||
#search_filters .facet .title{display:flex}#search_filters .facet .title .collapse-icons{margin-left:auto}#search_filters .facet .facet-title{width:calc(100% - 30px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#search_filters .facet .facet-label{width:100%;text-align:left}#search_filters .facet .facet-label .custom-checkbox,#search_filters .facet .facet-label .custom-radio{top:-7px;margin-right:0}#search_filters .facet .facet-label .color{margin-left:0}#search_filters .facet .facet-label a{width:calc(100% - 30px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
|
||||
|
||||
/*# sourceMappingURL=front.css.map*/
|
||||
3
modules/ps_facetedsearch/views/dist/front.js
vendored
Normal file
29
modules/ps_facetedsearch/views/dist/front.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
28
modules/ps_facetedsearch/views/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../../');
|
||||
exit;
|
||||
@@ -0,0 +1,28 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
|
||||
{function get_limit_select element=''}
|
||||
{assign var="name" value="{$element}_filter_show_limit"}
|
||||
<select name="{$name}">
|
||||
<option value="0">{l s='No limit' d='Modules.Facetedsearch.Admin'}</option>
|
||||
{for $index=2 to 20}
|
||||
<option value="{$index}">{$index}</option>
|
||||
{/for}
|
||||
</select>
|
||||
{/function}
|
||||
@@ -0,0 +1,30 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='Categories used for this template:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
{if trim($categories_tree) != ''}
|
||||
{$categories_tree}
|
||||
{else}
|
||||
<div class="alert alert-warning">
|
||||
{l s='Categories selection is disabled because you have no categories or you are in a "all shops" context.' d='Modules.Facetedsearch.Admin'}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
<div class="panel-footer" id="toolbar-footer">
|
||||
<button class="btn btn-default pull-right" id="submit-filter" name="SubmitFilter" type="submit"><i class="process-icon-save"></i> <span>{l s='Save' d='Admin.Actions'}</span></button>
|
||||
<a class="btn btn-default" href="{$current_url}">
|
||||
<i class="process-icon-cancel"></i> <span>{l s='Cancel' d='Admin.Actions'}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var translations = new Array();
|
||||
{if isset($filters)}var filters = '{$filters|@json_encode}';{/if}
|
||||
translations['no_selected_categories'] = "{l s='You must select at least one category' d='Modules.Facetedsearch.Admin'}";
|
||||
translations['no_selected_filters'] = "{l s='You must select at least one filter' d='Modules.Facetedsearch.Admin'}";
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='Template name:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" id="layered_tpl_name" name="layered_tpl_name" maxlength="64" value="{$template_name}" />
|
||||
<p class="help-block">{l s='Only as a reminder' d='Modules.Facetedsearch.Admin'}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{if isset($message)}{$message}{/if}
|
||||
|
||||
<div id="ajax-message-ok" class="conf ajax-message alert alert-success" style="display: none">
|
||||
<span class="message"></span>
|
||||
</div>
|
||||
<div id="ajax-message-ko" class="error ajax-message alert alert-danger" style="display: none">
|
||||
<span class="message"></span>
|
||||
</div>
|
||||
|
||||
{if !empty($limit_warning)}
|
||||
<div class="alert alert-danger">
|
||||
{if $limit_warning['error_type'] == 'suhosin'}
|
||||
{l s='Warning! Your hosting provider is using the Suhosin patch for PHP, which limits the maximum number of fields allowed in a form:' d='Modules.Facetedsearch.Admin'}
|
||||
|
||||
<b>{$limit_warning['post.max_vars']}</b> {l s='for suhosin.post.max_vars.' d='Modules.Facetedsearch.Admin'}<br/>
|
||||
<b>{$limit_warning['request.max_vars']}</b> {l s='for suhosin.request.max_vars.' d='Modules.Facetedsearch.Admin'}<br/>
|
||||
{l s='Please ask your hosting provider to increase the Suhosin limit to' d='Modules.Facetedsearch.Admin'}
|
||||
{else}
|
||||
{l s='Warning! Your PHP configuration limits the maximum number of fields allowed in a form:' d='Modules.Facetedsearch.Admin'}<br/>
|
||||
<b>{$limit_warning['max_input_vars']}</b> {l s='for max_input_vars.' d='Modules.Facetedsearch.Admin'}<br/>
|
||||
{l s='Please ask your hosting provider to increase this limit to' d='Modules.Facetedsearch.Admin'}
|
||||
{/if}
|
||||
{l s='%s at least, or you will have to edit the translation files manually.' sprintf=$limit_warning['needed_limit'] d='Modules.Facetedsearch.Admin'}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,24 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{if isset($asso_shops)}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='Choose shop association:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">{$asso_shops}</div>
|
||||
</div>
|
||||
{/if}
|
||||
345
modules/ps_facetedsearch/views/templates/admin/add.tpl
Normal file
@@ -0,0 +1,345 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{include file='./_partials/messages.tpl'}
|
||||
{include file='./_functions/show_limit.tpl'}
|
||||
|
||||
<div class="panel">
|
||||
<h3><i class="icon-cogs"></i> {l s='New filters template' d='Modules.Facetedsearch.Admin'}</h3>
|
||||
|
||||
<form action="{$current_url}" method="post" class="form-horizontal" onsubmit="return checkForm();">
|
||||
<input type="hidden" name="id_layered_filter" id="id_layered_filter" value="{$id_layered_filter}" />
|
||||
|
||||
{include file='./_partials/header.tpl'}
|
||||
{include file='./_partials/categories-tree.tpl'}
|
||||
{include file='./_partials/shops.tpl'}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">
|
||||
<span class="badge" id="selected_filters">0</span>
|
||||
<span class="label-tooltip" data-toggle="tooltip" title="" data-original-title="{l s='You can drag and drop filters to adjust position' d='Modules.Facetedsearch.Admin'}">{l s='Filters:' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</label>
|
||||
<div class="col-lg-9">
|
||||
<section class="filter_panel">
|
||||
<header class="clearfix">
|
||||
<span class="badge pull-right">
|
||||
{l
|
||||
s='Total filters: %s'
|
||||
sprintf=[$total_filters]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
</span>
|
||||
</header>
|
||||
<section class="filter_list">
|
||||
<ul class="list-unstyled sortable">
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_subcategories" id="layered_selection_subcategories" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<h4>{l s='Sub-categories filter' d='Modules.Facetedsearch.Admin'}</h4>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_subcategories"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_subcategories_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_stock" id="layered_selection_stock" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">{l s='Product stock filter' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_stock"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_stock_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_condition" id="layered_selection_condition" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">{l s='Product condition filter' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_condition"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_condition_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_manufacturer" id="layered_selection_manufacturer" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">{l s='Product brand filter' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_manufacturer"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_manufacturer_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_weight_slider" id="layered_selection_weight_slider" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">{l s='Product weight filter (slider)' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="hidden" name="layered_selection_weight_slider_filter_type" value="1">
|
||||
<p class="form-control-static">{l s='List of ranges' d='Modules.Facetedsearch.Admin'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_price_slider" id="layered_selection_price_slider" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">{l s='Product price filter (slider)' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="hidden" name="layered_selection_price_slider_filter_type" value="1">
|
||||
<p class="form-control-static">{l s='List of ranges' d='Modules.Facetedsearch.Admin'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{if $attribute_groups|count > 0}
|
||||
{foreach $attribute_groups as $attribute_group}
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}" id="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">
|
||||
{if $attribute_group['n'] > 1}
|
||||
{l
|
||||
s='Attribute group: %name% (%count% attributes)'
|
||||
sprintf=[
|
||||
'%name%' => $attribute_group['name'],
|
||||
'%count%' => $attribute_group['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{else}
|
||||
{l
|
||||
s='Attribute group: %name% (%count% attribute)'
|
||||
sprintf=[
|
||||
'%name%' => $attribute_group['name'],
|
||||
'%count%' => $attribute_group['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{/if}
|
||||
{if $attribute_group['is_color_group']}
|
||||
<img src="../img/admin/color_swatch.png" alt="" title="{l s='This group will allow user to select a color' d='Modules.Facetedsearch.Admin'}" />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{/if}
|
||||
|
||||
{if $features|count > 0}
|
||||
{foreach $features as $feature}
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_feat_{(int)$feature['id_feature']}" id="layered_selection_feat_{(int)$feature['id_feature']}" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">
|
||||
{if $feature['n'] > 1}
|
||||
{l
|
||||
s='Feature: %name% (%count% values)'
|
||||
sprintf=[
|
||||
'%name%' => $feature['name'],
|
||||
'%count%' => $feature['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{else}
|
||||
{l
|
||||
s='Feature: %name% (%count% value)'
|
||||
sprintf=[
|
||||
'%name%' => $feature['name'],
|
||||
'%count%' => $feature['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_feat_{(int)$feature['id_feature']}"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_feat_{(int)$feature['id_feature']}_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{include file='./_partials/footer.tpl'}
|
||||
</form>
|
||||
</div>
|
||||
28
modules/ps_facetedsearch/views/templates/admin/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*/
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../../../../');
|
||||
exit;
|
||||
266
modules/ps_facetedsearch/views/templates/admin/manage.tpl
Normal file
@@ -0,0 +1,266 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{include file='./_partials/messages.tpl'}
|
||||
|
||||
<div class="panel">
|
||||
<h3><i class="icon-cogs"></i> {l s='Indexes and caches' d='Modules.Facetedsearch.Admin'}</h3>
|
||||
<div id="indexing-warning" class="alert alert-warning" style="display: none">
|
||||
{l s='Indexing is in progress. Please do not leave this page' d='Modules.Facetedsearch.Admin'}
|
||||
</div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<a class="ajaxcall-recurcive btn btn-default" href="{$price_indexer_url}">{l s='Index all missing prices' d='Modules.Facetedsearch.Admin'}</a>
|
||||
<a class="ajaxcall-recurcive btn btn-default" href="{$full_price_indexer_url}">{l s='Rebuild entire price index' d='Modules.Facetedsearch.Admin'}</a>
|
||||
<a class="ajaxcall btn btn-default" href="{$attribute_indexer_url}">{l s='Build attributes and features indexes' d='Modules.Facetedsearch.Admin'}</a>
|
||||
<a class="ajaxcall btn btn-default" href="{$clear_cache_url}">{l s='Clear cache' d='Modules.Facetedsearch.Admin'}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="alert alert-info">
|
||||
{l s='You can set a cron job that will rebuild price index using the following URL:' d='Modules.Facetedsearch.Admin'}
|
||||
<br>
|
||||
<strong>{$price_indexer_url}</strong>
|
||||
<br>
|
||||
<br>
|
||||
{l s='You can set a cron job that will rebuild attribute index using the following URL:' d='Modules.Facetedsearch.Admin'}
|
||||
<br>
|
||||
<strong>{$attribute_indexer_url}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="alert alert-info">{l s='A nightly rebuild is recommended.' d='Modules.Facetedsearch.Admin'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h3><i class="icon-cogs"></i> {l s='Filters templates' d='Modules.Facetedsearch.Admin'}<span class="badge">{$filters_templates|count}</span></h3>
|
||||
{if $filters_templates|count > 0}
|
||||
<div class="row">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fixed-width-xs center"><span class="title_box">{l s='ID' d='Admin.Global'}</span></th>
|
||||
<th><span class="title_box text-left">{l s='Name' d='Admin.Global'}</span></th>
|
||||
<th class="fixed-width-sm center"><span class="title_box">{l s='Categories' d='Admin.Global'}</span></th>
|
||||
<th class="fixed-width-lg"><span class="title_box">{l s='Created on' d='Modules.Facetedsearch.Admin'}</span></th>
|
||||
<th class="fixed-width-sm"><span class="title_box text-right">{l s='Actions' d='Modules.Facetedsearch.Admin'}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $filters_templates as $template}
|
||||
<tr>
|
||||
<td class="center">{(int)$template['id_layered_filter']}</td>
|
||||
<td class="text-left">{$template['name']}</td>
|
||||
<td class="center">{(int)$template['n_categories']}</td>
|
||||
<td>{Tools::displayDate($template['date_add'],null , true)}</td>
|
||||
<td>
|
||||
{if empty($limit_warning)}
|
||||
<div class="btn-group-action">
|
||||
<div class="btn-group pull-right">
|
||||
<a href="{$current_url}&edit_filters_template=1&id_layered_filter={(int)$template['id_layered_filter']}" class="btn btn-default">
|
||||
<i class="icon-pencil"></i> {l s='Edit' d='Admin.Actions'}
|
||||
</a>
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="{$current_url}&deleteFilterTemplate=1&id_layered_filter={(int)$template['id_layered_filter']}"
|
||||
onclick="return confirm('{l s='Do you really want to delete this filter template?' d='Modules.Facetedsearch.Admin'}');">
|
||||
<i class="icon-trash"></i> {l s='Delete' d='Admin.Actions'}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="clearfix"> </div>
|
||||
</div>
|
||||
{else}
|
||||
<div class="row alert alert-warning">{l s='No filter template found.' d='Modules.Facetedsearch.Admin'}</div>
|
||||
{/if}
|
||||
{if empty($limit_warning)}
|
||||
<div class="panel-footer">
|
||||
<a class="btn btn-default pull-right" href="{$current_url}&add_new_filters_template=1"><i class="process-icon-plus"></i> {l s='Add new template' d='Modules.Facetedsearch.Admin'}</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h3><i class="icon-cogs"></i> {l s='Configuration' d='Admin.Global'}</h3>
|
||||
<form action="{$current_url}" method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Enable cache system' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_cache_enabled" id="ps_layered_cache_enabled_on" value="1"{if $cache_enabled} checked="checked"{/if}>
|
||||
<label for="ps_layered_cache_enabled_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_cache_enabled" id="ps_layered_cache_enabled_off" value="0"{if !$cache_enabled} checked="checked"{/if}>
|
||||
<label for="ps_layered_cache_enabled_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Show the number of matching products' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_show_qties" id="ps_layered_show_qties_on" value="1"{if $show_quantities} checked="checked"{/if}>
|
||||
<label for="ps_layered_show_qties_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_show_qties" id="ps_layered_show_qties_off" value="0"{if !$show_quantities} checked="checked"{/if}>
|
||||
<label for="ps_layered_show_qties_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Show products from subcategories' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_full_tree" id="ps_layered_full_tree_on" value="1"{if $full_tree} checked="checked"{/if}>
|
||||
<label for="ps_layered_full_tree_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_full_tree" id="ps_layered_full_tree_off" value="0"{if !$full_tree} checked="checked"{/if}>
|
||||
<label for="ps_layered_full_tree_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Show products only from default category' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_filter_by_default_category" id="ps_layered_filter_by_default_category_on" value="1"{if $filter_by_default_category} checked="checked"{/if}>
|
||||
<label for="ps_layered_filter_by_default_category_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_filter_by_default_category" id="ps_layered_filter_by_default_category_off" value="0"{if !$filter_by_default_category} checked="checked"{/if}>
|
||||
<label for="ps_layered_filter_by_default_category_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-9 col-lg-offset-3">
|
||||
<div class="help-block">
|
||||
{l s='Works only if "Show products from subcategories" is off.' d='Modules.Facetedsearch.Admin'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Category filter depth (0 for no limits, 1 by default)' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" name="ps_layered_filter_category_depth" value="{if $category_depth !== false}{$category_depth}{else}1{/if}" class="fixed-width-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Use tax to filter price' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_filter_price_usetax" id="ps_layered_filter_price_usetax_on" value="1"{if $price_use_tax} checked="checked"{/if}>
|
||||
<label for="ps_layered_filter_price_usetax_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_filter_price_usetax" id="ps_layered_filter_price_usetax_off" value="0"{if !$price_use_tax} checked="checked"{/if}>
|
||||
<label for="ps_layered_filter_price_usetax_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Use rounding to filter price' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_filter_price_rounding" id="ps_layered_filter_price_rounding_on" value="1"{if $price_use_rounding} checked="checked"{/if}/>
|
||||
<label for="ps_layered_filter_price_rounding_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_filter_price_rounding" id="ps_layered_filter_price_rounding_off" value="0"{if !$price_use_rounding} checked="checked"{/if}/>
|
||||
<label for="ps_layered_filter_price_rounding_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">{l s='Show unavailable, out of stock last' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-9">
|
||||
<span class="switch prestashop-switch fixed-width-lg">
|
||||
<input type="radio" name="ps_layered_filter_show_out_of_stock_last" id="ps_layered_filter_show_out_of_stock_last_on" value="1"{if $show_out_of_stock_last} checked="checked"{/if}/>
|
||||
<label for="ps_layered_filter_show_out_of_stock_last_on" class="radioCheck">
|
||||
<i class="color_success"></i> {l s='Yes' d='Admin.Global'}
|
||||
</label>
|
||||
<input type="radio" name="ps_layered_filter_show_out_of_stock_last" id="ps_layered_filter_show_out_of_stock_last_off" value="0"{if !$show_out_of_stock_last} checked="checked"{/if}/>
|
||||
<label for="ps_layered_filter_show_out_of_stock_last_off" class="radioCheck">
|
||||
<i class="color_danger"></i> {l s='No' d='Admin.Global'}
|
||||
</label>
|
||||
<a class="slide-button btn"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer">
|
||||
<button type="submit" class="btn btn-default pull-right" name="submitLayeredSettings"><i class="process-icon-save"></i> {l s='Save' d='Admin.Actions'}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
{if isset($PS_LAYERED_INDEXED)}var PS_LAYERED_INDEXED = {$PS_LAYERED_INDEXED};{/if}
|
||||
var token = '{$token}';
|
||||
var id_lang = {$id_lang};
|
||||
var base_folder = '{$base_folder}';
|
||||
var translations = new Object();
|
||||
|
||||
translations.in_progress = '{l s='(in progress)' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.url_indexation_finished = '{l s='URL indexing finished' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.attribute_indexation_finished = '{l s='Attribute indexing finished' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.url_indexation_failed = '{l s='URL indexing failed' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.attribute_indexation_failed = '{l s='Attribute indexing failed' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.price_indexation_finished = '{l s='Price indexing finished' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.price_indexation_failed = '{l s='Price indexing failed' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.price_indexation_in_progress = '{l s='(in progress, %s products price to index)' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.loading = '{l s='Loading...' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.delete_all_filters_templates = '{l s='You selected -All categories-: all existing filter templates will be deleted. Is it OK?' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
translations.no_selected_categories = '{l s='You must select at least one category' js=1 d='Modules.Facetedsearch.Admin'}';
|
||||
</script>
|
||||
215
modules/ps_facetedsearch/views/templates/admin/view.tpl
Normal file
@@ -0,0 +1,215 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{include file='./_partials/messages.tpl'}
|
||||
{include file='./_functions/show_limit.tpl'}
|
||||
|
||||
<div class="panel">
|
||||
<h3><i class="icon-cogs"></i> {l s='New filters template' d='Modules.Facetedsearch.Admin'}</h3>
|
||||
|
||||
<form action="{$current_url}" method="post" class="form-horizontal" onsubmit="return checkForm();">
|
||||
<input type="hidden" name="id_layered_filter" id="id_layered_filter" value="{$id_layered_filter}" />
|
||||
|
||||
{include file='./_partials/header.tpl'}
|
||||
{include file='./_partials/categories-tree.tpl'}
|
||||
{include file='./_partials/shops.tpl'}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">
|
||||
<span class="badge" id="selected_filters">0</span>
|
||||
<span class="label-tooltip" data-toggle="tooltip" title="" data-original-title="{l s='You can drag and drop filters to adjust position' d='Modules.Facetedsearch.Admin'}">{l s='Filters:' d='Modules.Facetedsearch.Admin'}</span>
|
||||
</label>
|
||||
<div class="col-lg-9">
|
||||
<section class="filter_panel">
|
||||
<header class="clearfix">
|
||||
<span class="badge pull-right">
|
||||
{l
|
||||
s='Total filters: %s'
|
||||
sprintf=[$total_filters]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
</span>
|
||||
</header>
|
||||
<section class="filter_list">
|
||||
<ul id="" class="list-unstyled sortable">
|
||||
|
||||
{foreach from=$default_filters item=filter key=filterId}
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="{$filterId}" id="{$filterId}" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<h4>{l s=$filter['label'] d='Modules.Facetedsearch.Admin'}</h4>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
{if !empty($filter['slider'])}
|
||||
<p class="form-control-static">{l s='List of ranges' d='Modules.Facetedsearch.Admin'}</p>
|
||||
{else}
|
||||
<div class="col-lg-6">
|
||||
<select name="{$filterId}_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
{if empty($filter['slider'])}
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element=$filterId}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
|
||||
{if $attribute_groups|count > 0}
|
||||
{foreach $attribute_groups as $attribute_group}
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}" id="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">
|
||||
{if $attribute_group['n'] > 1}
|
||||
{l
|
||||
s='Attribute group: %name% (%count% attributes)'
|
||||
sprintf=[
|
||||
'%name%' => $attribute_group['name'],
|
||||
'%count%' => $attribute_group['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{else}
|
||||
{l
|
||||
s='Attribute group: %name% (%count% attribute)'
|
||||
sprintf=[
|
||||
'%name%' => $attribute_group['name'],
|
||||
'%count%' => $attribute_group['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{/if}
|
||||
{if $attribute_group['is_color_group']}
|
||||
<img src="../img/admin/color_swatch.png" alt="" title="{l s='This group will allow user to select a color' d='Modules.Facetedsearch.Admin'}" />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_ag_{(int)$attribute_group['id_attribute_group']}_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{/if}
|
||||
|
||||
{if $features|count > 0}
|
||||
{foreach $features as $feature}
|
||||
<li class="filter_list_item row" draggable="true">
|
||||
<div class="col-lg-2">
|
||||
<label class="switch-light prestashop-switch fixed-width-lg">
|
||||
<input name="layered_selection_feat_{(int)$feature['id_feature']}" id="layered_selection_feat_{(int)$feature['id_feature']}" type="checkbox" />
|
||||
<span>
|
||||
<span>{l s='Yes' d='Admin.Global'}</span>
|
||||
<span>{l s='No' d='Admin.Global'}</span>
|
||||
</span>
|
||||
<a class="slide-button btn"></a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<span class="module_name">
|
||||
{if $feature['n'] > 1}
|
||||
{l
|
||||
s='Feature: %name% (%count% values)'
|
||||
sprintf=[
|
||||
'%name%' => $feature['name'],
|
||||
'%count%' => $feature['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{else}
|
||||
{l
|
||||
s='Feature: %name% (%count% value)'
|
||||
sprintf=[
|
||||
'%name%' => $feature['name'],
|
||||
'%count%' => $feature['n']
|
||||
]
|
||||
d='Modules.Facetedsearch.Admin'
|
||||
}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter result limit:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
{call get_limit_select element="layered_selection_feat_{(int)$feature['id_feature']}"}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 pull-right">
|
||||
<label class="control-label col-lg-6">{l s='Filter style:' d='Modules.Facetedsearch.Admin'}</label>
|
||||
<div class="col-lg-6">
|
||||
<select name="layered_selection_feat_{(int)$feature['id_feature']}_filter_type">
|
||||
<option value="0">{l s='Checkbox' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="1">{l s='Radio button' d='Modules.Facetedsearch.Admin'}</option>
|
||||
<option value="2">{l s='Drop-down list' d='Modules.Facetedsearch.Admin'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{include file='./_partials/footer.tpl'}
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
<section id="js-active-search-filters" class="{if $activeFilters|count}active_filters{else}hide{/if}">
|
||||
{block name='active_filters_title'}
|
||||
<p class="h6 {if $activeFilters|count}active-filter-title{else}hidden-xs-up{/if}">{l s='Active filters' d='Shop.Theme.Global'}</p>
|
||||
{/block}
|
||||
|
||||
{if $activeFilters|count}
|
||||
<ul>
|
||||
{foreach from=$activeFilters item="filter"}
|
||||
{block name='active_filters_item'}
|
||||
<li class="filter-block">
|
||||
{l s='%1$s:' d='Shop.Theme.Catalog' sprintf=[$filter.facetLabel]}
|
||||
{$filter.label}
|
||||
<a class="js-search-link" href="{$filter.nextEncodedFacetsURL}"><i class="material-icons close"></i></a>
|
||||
</li>
|
||||
{/block}
|
||||
{/foreach}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
@@ -0,0 +1,185 @@
|
||||
{**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/AFL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* @author PrestaShop SA <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
|
||||
*}
|
||||
{if $displayedFacets|count}
|
||||
<div id="search_filters">
|
||||
|
||||
{block name='facets_clearall_button'}
|
||||
{if $activeFilters|count}
|
||||
<div id="_desktop_search_filters_clear_all" class="hidden-sm-down clear-all-wrapper">
|
||||
<button data-search-url="{$clear_all_link}" class="btn btn-tertiary js-search-filters-clear-all">
|
||||
<i class="material-icons"></i>
|
||||
{l s='Clear all' d='Shop.Theme.Actions'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{foreach from=$displayedFacets item="facet" key="k"}
|
||||
|
||||
{assign var="facet_color" value=false}
|
||||
|
||||
{foreach from=$facet.filters key=filter_key item="filter"}
|
||||
{if isset($filter.properties.color)}
|
||||
{$facet_color = true}
|
||||
{/if}
|
||||
{/foreach}
|
||||
<section class="facet {if $facet_color == true}facet-color{/if} clearfix" type="{$facet.type}">
|
||||
<p class="h6 facet-title hidden-sm-down">{$facet.label}</p>
|
||||
{assign var=_expand_id value=10|mt_rand:100000}
|
||||
{assign var=_collapse value=true}
|
||||
{foreach from=$facet.filters item="filter"}
|
||||
{if $filter.active}{assign var=_collapse value=false}{/if}
|
||||
{/foreach}
|
||||
|
||||
<div class="title hidden-md-up" data-target="#facet_{$_expand_id}" data-toggle="collapse"{if !$_collapse} aria-expanded="true"{/if}>
|
||||
<p class="h6 facet-title">{$facet.label}</p>
|
||||
<span class="navbar-toggler collapse-icons">
|
||||
<i class="material-icons add"></i>
|
||||
<i class="material-icons remove"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{if in_array($facet.widgetType, ['radio', 'checkbox'])}
|
||||
{block name='facet_item_other'}
|
||||
<ul id="facet_{$_expand_id}" class="collapse{if !$_collapse} in{/if}">
|
||||
{foreach from=$facet.filters key=filter_key item="filter"}
|
||||
{if !$filter.displayed}
|
||||
{continue}
|
||||
{/if}
|
||||
|
||||
<li>
|
||||
<label class="facet-label{if $filter.active} active {/if}" for="facet_input_{$_expand_id}_{$filter_key}" id="filter_{$filter_key}">
|
||||
{if $facet.multipleSelectionAllowed}
|
||||
<span class="custom-checkbox">
|
||||
<input
|
||||
id="facet_input_{$_expand_id}_{$filter_key}"
|
||||
data-search-url="{$filter.nextEncodedFacetsURL}"
|
||||
type="checkbox"
|
||||
{if $filter.active }checked{/if}
|
||||
>
|
||||
{if isset($filter.properties.color)}
|
||||
<span class="color" style="background-color:{$filter.properties.color}"></span>
|
||||
{elseif isset($filter.properties.texture)}
|
||||
<span class="color texture" style="background-image:url({$filter.properties.texture})"></span>
|
||||
{else}
|
||||
<span {if !$js_enabled} class="ps-shown-by-js" {/if}><i class="material-icons rtl-no-flip checkbox-checked"></i></span>
|
||||
{/if}
|
||||
</span>
|
||||
{else}
|
||||
<span class="custom-radio">
|
||||
<input
|
||||
id="facet_input_{$_expand_id}_{$filter_key}"
|
||||
data-search-url="{$filter.nextEncodedFacetsURL}"
|
||||
type="radio"
|
||||
name="filter {$facet.label}"
|
||||
{if $filter.active }checked{/if}
|
||||
>
|
||||
<span {if !$js_enabled} class="ps-shown-by-js" {/if}></span>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<a
|
||||
href="{$filter.nextEncodedFacetsURL}"
|
||||
class="_gray-darker search-link js-search-link"
|
||||
rel="nofollow"
|
||||
>
|
||||
{$filter.label}
|
||||
{if $filter.magnitude and $show_quantities}
|
||||
<span class="magnitude">({$filter.magnitude})</span>
|
||||
{/if}
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
{/block}
|
||||
|
||||
{elseif $facet.widgetType == 'dropdown'}
|
||||
{block name='facet_item_dropdown'}
|
||||
<ul id="facet_{$_expand_id}" class="collapse{if !$_collapse} in{/if}">
|
||||
<li>
|
||||
<div class="col-sm-12 col-xs-12 col-md-12 facet-dropdown dropdown">
|
||||
<a class="select-title" rel="nofollow" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{$active_found = false}
|
||||
<span>
|
||||
{foreach from=$facet.filters item="filter"}
|
||||
{if $filter.active}
|
||||
{$filter.label}
|
||||
{if $filter.magnitude and $show_quantities}
|
||||
({$filter.magnitude})
|
||||
{/if}
|
||||
{$active_found = true}
|
||||
{/if}
|
||||
{/foreach}
|
||||
{if !$active_found}
|
||||
{l s='(no filter)' d='Shop.Theme.Global'}
|
||||
{/if}
|
||||
</span>
|
||||
<i class="material-icons float-xs-right"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
{foreach from=$facet.filters item="filter"}
|
||||
{if !$filter.active}
|
||||
<a
|
||||
rel="nofollow"
|
||||
href="{$filter.nextEncodedFacetsURL}"
|
||||
class="select-list js-search-link"
|
||||
>
|
||||
{$filter.label}
|
||||
{if $filter.magnitude and $show_quantities}
|
||||
({$filter.magnitude})
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{/block}
|
||||
|
||||
{elseif $facet.widgetType == 'slider'}
|
||||
{block name='facet_item_slider'}
|
||||
{foreach from=$facet.filters item="filter"}
|
||||
<ul id="facet_{$_expand_id}"
|
||||
class="faceted-slider collapse{if !$_collapse} in{/if}"
|
||||
data-slider-min="{$facet.properties.min}"
|
||||
data-slider-max="{$facet.properties.max}"
|
||||
data-slider-id="{$_expand_id}"
|
||||
data-slider-values="{$filter.value|@json_encode}"
|
||||
data-slider-unit="{$facet.properties.unit}"
|
||||
data-slider-label="{$facet.label}"
|
||||
data-slider-specifications="{$facet.properties.specifications|@json_encode}"
|
||||
data-slider-encoded-url="{$filter.nextEncodedFacetsURL}"
|
||||
>
|
||||
<li>
|
||||
<p id="facet_label_{$_expand_id}">
|
||||
{$filter.label}
|
||||
</p>
|
||||
|
||||
<div id="slider-range_{$_expand_id}"></div>
|
||||
</li>
|
||||
</ul>
|
||||
{/foreach}
|
||||
{/block}
|
||||
{/if}
|
||||
</section>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/if}
|
||||