Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

10
controllers/.htaccess Normal file
View File

@@ -0,0 +1,10 @@
# Apache 2.2
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
# Apache 2.4
<IfModule mod_authz_core.c>
Require all denied
</IfModule>

View File

@@ -0,0 +1,215 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Profile $object
*/
class AdminAccessControllerCore extends AdminController
{
/** @var array : Black list of id_tab that do not have access */
public $accesses_black_list = [];
public function __construct()
{
$this->bootstrap = true;
$this->show_toolbar = false;
$this->table = 'access';
$this->className = 'Profile';
$this->multishop_context = Shop::CONTEXT_ALL;
$this->lang = false;
$this->context = Context::getContext();
// Blacklist AdminLogin
$this->accesses_black_list[] = Tab::getIdFromClassName('AdminLogin');
parent::__construct();
}
/**
* AdminController::renderForm() override.
*
* @see AdminController::renderForm()
*/
public function renderForm()
{
$current_profile = (int) $this->getCurrentProfileId();
$profiles = Profile::getProfiles($this->context->language->id);
$tabs = Tab::getTabs($this->context->language->id);
$accesses = [];
foreach ($profiles as $profile) {
$accesses[$profile['id_profile']] = Profile::getProfileAccesses($profile['id_profile']);
}
// Deleted id_tab that do not have access
foreach ($tabs as $key => $tab) {
// Don't allow permissions for unnamed tabs (ie. AdminLogin)
if (empty($tab['name'])) {
unset($tabs[$key]);
}
foreach ($this->accesses_black_list as $id_tab) {
if ($tab['id_tab'] == (int) $id_tab) {
unset($tabs[$key]);
}
}
}
$modules = [];
foreach ($profiles as $profile) {
$modules[$profile['id_profile']] = Module::getModulesAccessesByIdProfile($profile['id_profile']);
uasort($modules[$profile['id_profile']], [$this, 'sortModuleByName']);
}
$this->fields_form = [''];
$this->tpl_form_vars = [
'profiles' => $profiles,
'accesses' => $accesses,
'id_tab_parentmodule' => (int) Tab::getIdFromClassName('AdminParentModules'),
'id_tab_module' => (int) Tab::getIdFromClassName('AdminModules'),
'tabs' => $this->displayTabs($tabs),
'current_profile' => (int) $current_profile,
'admin_profile' => (int) _PS_ADMIN_PROFILE_,
'access_edit' => $this->access('edit'),
'perms' => ['view', 'add', 'edit', 'delete'],
'id_perms' => ['view' => 0, 'add' => 1, 'edit' => 2, 'delete' => 3, 'all' => 4],
'modules' => $modules,
'link' => $this->context->link,
'employee_profile_id' => (int) $this->context->employee->id_profile,
];
return parent::renderForm();
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
$this->display = 'edit';
if (!$this->loadObject(true)) {
return;
}
$this->content .= $this->renderForm();
$this->context->smarty->assign([
'content' => $this->content,
]);
}
public function initToolbarTitle()
{
$this->toolbar_title = array_unique($this->breadcrumbs);
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
unset($this->page_header_toolbar_btn['cancel']);
}
public function ajaxProcessUpdateAccess()
{
if (_PS_MODE_DEMO_) {
throw new PrestaShopException($this->trans('This functionality has been disabled.', [], 'Admin.Notifications.Error'));
}
if ($this->access('edit') != '1') {
throw new PrestaShopException($this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'));
}
if (Tools::isSubmit('submitAddAccess')) {
$access = new Access();
$perm = Tools::getValue('perm');
if (!in_array($perm, ['view', 'add', 'edit', 'delete', 'all'])) {
throw new PrestaShopException('permission does not exist');
}
$enabled = (bool) Tools::getValue('enabled');
$id_tab = (int) Tools::getValue('id_tab');
$id_profile = (int) Tools::getValue('id_profile');
$addFromParent = (bool) Tools::getValue('addFromParent');
die($access->updateLgcAccess((int) $id_profile, $id_tab, $perm, $enabled, $addFromParent));
}
}
public function ajaxProcessUpdateModuleAccess()
{
if (_PS_MODE_DEMO_) {
throw new PrestaShopException($this->trans('This functionality has been disabled.', [], 'Admin.Notifications.Error'));
}
if ($this->access('edit') != '1') {
throw new PrestaShopException($this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'));
}
if (Tools::isSubmit('changeModuleAccess')) {
$access = new Access();
$perm = Tools::getValue('perm');
$enabled = (bool) Tools::getValue('enabled');
$id_module = (int) Tools::getValue('id_module');
$id_profile = (int) Tools::getValue('id_profile');
if (!in_array($perm, ['view', 'configure', 'uninstall'])) {
throw new PrestaShopException('permission does not exist');
}
die($access->updateLgcModuleAccess((int) $id_profile, $id_module, $perm, $enabled));
}
}
/**
* Get the current profile id.
*
* @return int the $_GET['profile'] if valid, else 1 (the first profile id)
*/
public function getCurrentProfileId()
{
return (isset($_GET['id_profile']) && !empty($_GET['id_profile']) && is_numeric($_GET['id_profile'])) ? (int) $_GET['id_profile'] : 1;
}
/**
* @param array $a module data
* @param array $b module data
*
* @return int
*/
protected function sortModuleByName(array $a, array $b)
{
$moduleAName = isset($a['name']) ? $a['name'] : null;
$moduleBName = isset($b['name']) ? $b['name'] : null;
return strnatcmp($moduleAName, $moduleBName);
}
/**
* return human readable Tabs hierarchy for display.
*/
protected function displayTabs(array $tabs)
{
$tabsTree = $this->getChildrenTab($tabs);
return $tabsTree;
}
protected function getChildrenTab(array &$tabs, int $id_parent = 0)
{
$children = [];
foreach ($tabs as $tab) {
$id = $tab['id_tab'];
if ($tab['id_parent'] == $id_parent) {
$children[$id] = $tab;
$children[$id]['children'] = $this->getChildrenTab($tabs, $id);
}
}
return $children;
}
}

View File

@@ -0,0 +1,967 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Carrier $object
*/
class AdminCarrierWizardControllerCore extends AdminController
{
protected $wizard_access;
/**
* @var Context
*/
public $old_context;
/**
* @var int
*/
public $type_context;
/**
* @var array<string, string|array<array<string, string>>>
*/
public $wizard_steps;
public function __construct()
{
$this->bootstrap = true;
$this->display = 'view';
$this->table = 'carrier';
$this->identifier = 'id_carrier';
$this->className = 'Carrier';
$this->lang = false;
$this->deleted = true;
$this->type_context = Shop::getContext();
$this->old_context = Context::getContext();
$this->multishop_context = Shop::CONTEXT_ALL;
$this->context = Context::getContext();
$this->fieldImageSettings = [
'name' => 'logo',
'dir' => 's',
];
parent::__construct();
$this->tabAccess = Profile::getProfileAccess($this->context->employee->id_profile, Tab::getIdFromClassName('AdminCarriers'));
}
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryPlugin('smartWizard');
$this->addJqueryPlugin('typewatch');
$this->addJs(_PS_JS_DIR_ . 'admin/carrier_wizard.js');
}
public function initWizard()
{
$this->wizard_steps = [
'name' => 'carrier_wizard',
'steps' => [
[
'title' => $this->trans('General settings', [], 'Admin.Shipping.Feature'),
],
[
'title' => $this->trans('Shipping locations and costs', [], 'Admin.Shipping.Feature'),
],
[
'title' => $this->trans('Size, weight, and group access', [], 'Admin.Shipping.Feature'),
],
[
'title' => $this->trans('Summary', [], 'Admin.Global'),
], ],
];
if (Shop::isFeatureActive()) {
$multistore_step = [
[
'title' => $this->trans('MultiStore', [], 'Admin.Global'),
],
];
array_splice($this->wizard_steps['steps'], 1, 0, $multistore_step);
}
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderView()
{
$this->initWizard();
if (Tools::getValue('id_carrier') && $this->access('edit')) {
/** @var Carrier $carrier */
$carrier = $this->loadObject();
} elseif ($this->access('add')) {
$carrier = new Carrier();
}
if (
(!$this->access('edit') && Tools::getValue('id_carrier'))
|| (!$this->access('add') && !Tools::getValue('id_carrier'))
|| !isset($carrier)
) {
$this->errors[] = $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification');
return;
}
$currency = $this->getActualCurrency();
$this->tpl_view_vars = [
'currency_sign' => $currency->sign,
'PS_WEIGHT_UNIT' => Configuration::get('PS_WEIGHT_UNIT'),
'enableAllSteps' => Validate::isLoadedObject($carrier),
'wizard_steps' => $this->wizard_steps,
'validate_url' => $this->context->link->getAdminLink('AdminCarrierWizard'),
'carrierlist_url' => $this->context->link->getAdminLink('AdminCarriers') . '&conf=' . ((int) Validate::isLoadedObject($carrier) ? 4 : 3),
'multistore_enable' => Shop::isFeatureActive(),
'wizard_contents' => [
'contents' => [
0 => $this->renderStepOne($carrier),
1 => $this->renderStepThree($carrier),
2 => $this->renderStepFour($carrier),
3 => $this->renderStepFive($carrier),
],
],
'labels' => [
'next' => $this->trans('Next', [], 'Admin.Global'),
'previous' => $this->trans('Previous', [], 'Admin.Global'),
'finish' => $this->trans('Finish', [], 'Admin.Actions'), ],
];
if (Shop::isFeatureActive()) {
array_splice($this->tpl_view_vars['wizard_contents']['contents'], 1, 0, [0 => $this->renderStepTwo($carrier)]);
}
$this->context->smarty->assign([
'carrier_logo' => (Validate::isLoadedObject($carrier) && file_exists(_PS_SHIP_IMG_DIR_ . $carrier->id . '.jpg') ? _THEME_SHIP_DIR_ . $carrier->id . '.jpg' : false),
]);
$this->context->smarty->assign([
'logo_content' => $this->createTemplate('logo.tpl')->fetch(),
]);
$this->addjQueryPlugin(['ajaxfileupload']);
return parent::renderView();
}
public function initBreadcrumbs($tab_id = null, $tabs = null)
{
if (Tools::getValue('id_carrier')) {
$this->display = 'edit';
} else {
$this->display = 'add';
}
parent::initBreadcrumbs((int) Tab::getIdFromClassName('AdminCarriers'));
$this->display = 'view';
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
$this->page_header_toolbar_btn['cancel'] = [
'href' => $this->context->link->getAdminLink('AdminCarriers'),
'desc' => $this->trans('Cancel', [], 'Admin.Actions'),
];
}
public function renderStepOne($carrier)
{
$this->fields_form = [
'form' => [
'id_form' => 'step_carrier_general',
'input' => [
[
'type' => 'text',
'label' => $this->trans('Carrier name', [], 'Admin.Shipping.Feature'),
'name' => 'name',
'required' => true,
'hint' => [
$this->trans('Allowed characters: letters, spaces and "%special_chars%".', ['%special_chars%' => '().-'], 'Admin.Shipping.Help'),
$this->trans('The carrier\'s name will be displayed during checkout.', [], 'Admin.Shipping.Help'),
$this->trans('For in-store pickup, enter 0 to replace the carrier name with your shop name.', [], 'Admin.Shipping.Help'),
],
],
[
'type' => 'text',
'label' => $this->trans('Transit time', [], 'Admin.Shipping.Feature'),
'name' => 'delay',
'lang' => true,
'required' => true,
'maxlength' => 512,
'hint' => $this->trans('The delivery time will be displayed during checkout.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Speed grade', [], 'Admin.Shipping.Feature'),
'name' => 'grade',
'required' => false,
'size' => 1,
'hint' => $this->trans('Enter "0" for a longest shipping delay, or "9" for the shortest shipping delay.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'logo',
'label' => $this->trans('Logo', [], 'Admin.Global'),
'name' => 'logo',
],
[
'type' => 'text',
'label' => $this->trans('Tracking URL', [], 'Admin.Shipping.Feature'),
'name' => 'url',
'hint' => $this->trans('Delivery tracking URL: Type \'@\' where the tracking number should appear. It will be automatically replaced by the tracking number.', [], 'Admin.Shipping.Help'),
'desc' => $this->trans('For example: \'http://example.com/track.php?num=@\' with \'@\' where the tracking number should appear.', [], 'Admin.Shipping.Help'),
],
],
],
];
$tpl_vars = ['max_image_size' => (int) Configuration::get('PS_PRODUCT_PICTURE_MAX_SIZE') / 1024 / 1024];
$fields_value = $this->getStepOneFieldsValues($carrier);
return $this->renderGenericForm(['form' => $this->fields_form], $fields_value, $tpl_vars);
}
public function renderStepTwo($carrier)
{
$this->fields_form = [
'form' => [
'id_form' => 'step_carrier_shops',
'force' => true,
'input' => [
[
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
],
],
],
];
$fields_value = $this->getStepTwoFieldsValues($carrier);
return $this->renderGenericForm(['form' => $this->fields_form], $fields_value);
}
public function renderStepThree($carrier)
{
$this->fields_form = [
'form' => [
'id_form' => 'step_carrier_ranges',
'input' => [
'shipping_handling' => [
'type' => 'switch',
'label' => $this->trans('Add handling costs', [], 'Admin.Shipping.Feature'),
'name' => 'shipping_handling',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'shipping_handling_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'shipping_handling_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Include the handling costs (as set in Shipping > Preferences) in the final carrier price.', [], 'Admin.Shipping.Help'),
],
'is_free' => [
'type' => 'switch',
'label' => $this->trans('Free shipping', [], 'Admin.Shipping.Feature'),
'name' => 'is_free',
'required' => false,
'class' => 't',
'values' => [
[
'id' => 'is_free_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'is_free_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
'shipping_method' => [
'type' => 'radio',
'label' => $this->trans('Billing', [], 'Admin.Shipping.Feature'),
'name' => 'shipping_method',
'required' => false,
'class' => 't',
'br' => true,
'values' => [
[
'id' => 'billing_price',
'value' => Carrier::SHIPPING_METHOD_PRICE,
'label' => $this->trans('According to total price.', [], 'Admin.Shipping.Feature'),
],
[
'id' => 'billing_weight',
'value' => Carrier::SHIPPING_METHOD_WEIGHT,
'label' => $this->trans('According to total weight.', [], 'Admin.Shipping.Feature'),
],
],
],
'id_tax_rules_group' => [
'type' => 'select',
'label' => $this->trans('Tax', [], 'Admin.Global'),
'name' => 'id_tax_rules_group',
'options' => [
'query' => TaxRulesGroup::getTaxRulesGroups(true),
'id' => 'id_tax_rules_group',
'name' => 'name',
'default' => [
'label' => $this->trans('No tax', [], 'Admin.Global'),
'value' => 0,
],
],
],
'range_behavior' => [
'type' => 'select',
'label' => $this->trans('Out-of-range behavior', [], 'Admin.Shipping.Feature'),
'name' => 'range_behavior',
'options' => [
'query' => [
[
'id' => 0,
'name' => $this->trans('Apply the cost of the highest defined range', [], 'Admin.Shipping.Feature'),
],
[
'id' => 1,
'name' => $this->trans('Disable carrier', [], 'Admin.Shipping.Feature'),
],
],
'id' => 'id',
'name' => 'name',
],
'hint' => $this->trans('Out-of-range behavior occurs when no defined range matches the customer\'s cart (e.g. when the weight of the cart is greater than the highest weight limit defined by the weight ranges).', [], 'Admin.Shipping.Help'),
],
'zones' => [
'type' => 'zone',
'name' => 'zones',
],
],
],
];
if (Configuration::get('PS_ATCP_SHIPWRAP')) {
unset($this->fields_form['form']['input']['id_tax_rules_group']);
}
$tpl_vars = [];
$tpl_vars['PS_WEIGHT_UNIT'] = Configuration::get('PS_WEIGHT_UNIT');
$currency = $this->getActualCurrency();
$tpl_vars['currency_sign'] = $currency->sign;
$fields_value = $this->getStepThreeFieldsValues($carrier);
$this->getTplRangesVarsAndValues($carrier, $tpl_vars, $fields_value);
return $this->renderGenericForm(['form' => $this->fields_form], $fields_value, $tpl_vars);
}
/**
* @param Carrier $carrier
*
* @return string
*/
public function renderStepFour($carrier)
{
$this->fields_form = [
'form' => [
'id_form' => 'step_carrier_conf',
'input' => [
[
'type' => 'text',
'label' => $this->trans('Maximum package width (%s)', ['%s' => Configuration::get('PS_DIMENSION_UNIT')], 'Admin.Shipping.Feature'),
'name' => 'max_width',
'required' => false,
'hint' => $this->trans('Maximum width managed by this carrier. Set the value to "0", or leave this field blank to ignore.', [], 'Admin.Shipping.Help') . ' ' . $this->trans('The value must be an integer.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package height (%s)', ['%s' => Configuration::get('PS_DIMENSION_UNIT')], 'Admin.Shipping.Feature'),
'name' => 'max_height',
'required' => false,
'hint' => $this->trans('Maximum height managed by this carrier. Set the value to "0", or leave this field blank to ignore.', [], 'Admin.Shipping.Help') . ' ' . $this->trans('The value must be an integer.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package depth (%s)', ['%s' => Configuration::get('PS_DIMENSION_UNIT')], 'Admin.Shipping.Feature'),
'name' => 'max_depth',
'required' => false,
'hint' => $this->trans('Maximum depth managed by this carrier. Set the value to "0", or leave this field blank to ignore.', [], 'Admin.Shipping.Help') . ' ' . $this->trans('The value must be an integer.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package weight (%s)', ['%s' => Configuration::get('PS_WEIGHT_UNIT')], 'Admin.Shipping.Feature'),
'name' => 'max_weight',
'required' => false,
'hint' => $this->trans('Maximum weight managed by this carrier. Set the value to "0", or leave this field blank to ignore.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'group',
'label' => $this->trans('Group access', [], 'Admin.Shipping.Feature'),
'name' => 'groupBox',
'values' => Group::getGroups(Context::getContext()->language->id),
'hint' => $this->trans('Mark the groups that are allowed access to this carrier.', [], 'Admin.Shipping.Help'),
],
],
],
];
$fields_value = $this->getStepFourFieldsValues($carrier);
// Added values of object Group
$carrier_groups = $carrier->getGroups();
$carrier_groups_ids = [];
if (is_array($carrier_groups)) {
foreach ($carrier_groups as $carrier_group) {
$carrier_groups_ids[] = $carrier_group['id_group'];
}
}
$groups = Group::getGroups($this->context->language->id);
foreach ($groups as $group) {
$fields_value['groupBox_' . $group['id_group']] = Tools::getValue('groupBox_' . $group['id_group'], in_array($group['id_group'], $carrier_groups_ids) || empty($carrier_groups_ids) && !$carrier->id);
}
return $this->renderGenericForm(['form' => $this->fields_form], $fields_value);
}
public function renderStepFive($carrier)
{
$this->fields_form = [
'form' => [
'id_form' => 'step_carrier_summary',
'input' => [
[
'type' => 'switch',
'label' => $this->trans('Enabled', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
],
[
'id' => 'active_off',
'value' => 0,
],
],
'hint' => $this->trans('Enable the carrier in the front office.', [], 'Admin.Shipping.Help'),
],
],
],
];
$template = $this->createTemplate('controllers/carrier_wizard/summary.tpl');
$fields_value = $this->getStepFiveFieldsValues($carrier);
$active_form = $this->renderGenericForm(['form' => $this->fields_form], $fields_value);
$active_form = str_replace(['<fieldset id="fieldset_form">', '</fieldset>'], '', $active_form);
$template->assign('active_form', $active_form);
return $template->fetch();
}
/**
* @param Carrier $carrier
* @param array $tpl_vars
* @param array $fields_value
*/
protected function getTplRangesVarsAndValues(Carrier $carrier, array &$tpl_vars, array &$fields_value)
{
$tpl_vars['zones'] = Zone::getZones(false, true);
$carrier_zones = $carrier->getZones();
$carrier_zones_ids = [];
if (is_array($carrier_zones)) {
foreach ($carrier_zones as $carrier_zone) {
$carrier_zones_ids[] = $carrier_zone['id_zone'];
}
}
if ($fields_value['zones'] === false) {
$fields_value['zones'] = [];
}
$range_table = $carrier->getRangeTable();
$shipping_method = $carrier->getShippingMethod();
$zones = Zone::getZones(false);
foreach ($zones as $zone) {
$fields_value['zones'][$zone['id_zone']] = Tools::getValue('zone_' . $zone['id_zone'], in_array($zone['id_zone'], $carrier_zones_ids));
}
if ($shipping_method == Carrier::SHIPPING_METHOD_FREE) {
$range_obj = $carrier->getRangeObject($carrier->shipping_method);
$price_by_range = [];
} else {
$range_obj = $carrier->getRangeObject();
$price_by_range = Carrier::getDeliveryPriceByRanges($range_table, (int) $carrier->id);
}
foreach ($price_by_range as $price) {
$tpl_vars['price_by_range'][$price['id_' . $range_table]][$price['id_zone']] = $price['price'];
}
$tmp_range = $range_obj->getRanges((int) $carrier->id);
$tpl_vars['ranges'] = [];
if ($shipping_method != Carrier::SHIPPING_METHOD_FREE) {
foreach ($tmp_range as $id => $range) {
$tpl_vars['ranges'][$range['id_' . $range_table]] = $range;
$tpl_vars['ranges'][$range['id_' . $range_table]]['id_range'] = $range['id_' . $range_table];
}
}
// init blank range
if (!count($tpl_vars['ranges'])) {
$tpl_vars['ranges'][] = ['id_range' => 0, 'delimiter1' => 0, 'delimiter2' => 0];
}
}
public function renderGenericForm($fields_form, $fields_value, $tpl_vars = [])
{
$helper = new HelperForm();
$helper->show_toolbar = false;
$helper->table = $this->table;
$lang = new Language((int) Configuration::get('PS_LANG_DEFAULT'));
$helper->default_form_language = $lang->id;
$helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') ? Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') : 0;
$this->fields_form = [];
$helper->id = (int) Tools::getValue('id_carrier');
$helper->identifier = $this->identifier;
$helper->tpl_vars = array_merge([
'fields_value' => $fields_value,
'languages' => $this->getLanguages(),
'id_language' => $this->context->language->id,
], $tpl_vars);
$helper->override_folder = 'carrier_wizard/';
return $helper->generateForm($fields_form);
}
public function getStepOneFieldsValues($carrier)
{
return [
'id_carrier' => $this->getFieldValue($carrier, 'id_carrier'),
'name' => $this->getFieldValue($carrier, 'name'),
'delay' => $this->getFieldValue($carrier, 'delay'),
'grade' => $this->getFieldValue($carrier, 'grade'),
'url' => $this->getFieldValue($carrier, 'url'),
];
}
public function getStepTwoFieldsValues($carrier)
{
return ['shop' => $this->getFieldValue($carrier, 'shop')];
}
public function getStepThreeFieldsValues($carrier)
{
$id_tax_rules_group = (is_object($this->object) && !$this->object->id) ? Carrier::getIdTaxRulesGroupMostUsed() : Carrier::getIdTaxRulesGroupByIdCarrier($this->object->id);
$shipping_handling = (is_object($this->object) && !$this->object->id) ? 0 : $this->getFieldValue($carrier, 'shipping_handling');
return [
'is_free' => $this->getFieldValue($carrier, 'is_free'),
'id_tax_rules_group' => (int) $id_tax_rules_group,
'shipping_handling' => $shipping_handling,
'shipping_method' => $this->getFieldValue($carrier, 'shipping_method'),
'range_behavior' => $this->getFieldValue($carrier, 'range_behavior'),
'zones' => $this->getFieldValue($carrier, 'zones'),
];
}
public function getStepFourFieldsValues($carrier)
{
return [
'range_behavior' => $this->getFieldValue($carrier, 'range_behavior'),
'max_height' => $this->getFieldValue($carrier, 'max_height'),
'max_width' => $this->getFieldValue($carrier, 'max_width'),
'max_depth' => $this->getFieldValue($carrier, 'max_depth'),
'max_weight' => $this->getFieldValue($carrier, 'max_weight'),
'group' => $this->getFieldValue($carrier, 'group'),
];
}
public function getStepFiveFieldsValues($carrier)
{
return ['active' => $this->getFieldValue($carrier, 'active')];
}
public function ajaxProcessChangeRanges()
{
if ((Validate::isLoadedObject($this->object) && !$this->access('edit')) || !$this->access('add')) {
$this->errors[] = $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification');
return;
}
if ((!(int) $shipping_method = Tools::getValue('shipping_method')) || !in_array($shipping_method, [Carrier::SHIPPING_METHOD_PRICE, Carrier::SHIPPING_METHOD_WEIGHT])) {
return;
}
/** @var Carrier $carrier */
$carrier = $this->loadObject(true);
$carrier->shipping_method = $shipping_method;
$tpl_vars = [];
$fields_value = $this->getStepThreeFieldsValues($carrier);
$this->getTplRangesVarsAndValues($carrier, $tpl_vars, $fields_value);
$template = $this->createTemplate('controllers/carrier_wizard/helpers/form/form_ranges.tpl');
$template->assign($tpl_vars);
$template->assign('change_ranges', 1);
$template->assign('fields_value', $fields_value);
$template->assign('input', ['type' => 'zone', 'name' => 'zones']);
$currency = $this->getActualCurrency();
$template->assign('currency_sign', $currency->sign);
$template->assign('PS_WEIGHT_UNIT', Configuration::get('PS_WEIGHT_UNIT'));
die($template->fetch());
}
protected function validateForm(bool $die = true)
{
$step_number = (int) Tools::getValue('step_number');
$return = ['has_error' => false];
if (!$this->access('edit')) {
$this->errors[] = $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification');
} else {
if (Shop::isFeatureActive() && $step_number == 2) {
if (!Tools::getValue('checkBoxShopAsso_carrier')) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('You must choose at least one shop or group shop.', [], 'Admin.Shipping.Notification');
}
} else {
$this->validateRules();
}
}
if (count($this->errors)) {
$return['has_error'] = true;
$return['errors'] = $this->errors;
}
if (count($this->errors) || $die) {
die(json_encode($return));
}
}
public function ajaxProcessValidateStep()
{
$this->validateForm(true);
}
public function processRanges($id_carrier)
{
if (!$this->access('edit') || !$this->access('add')) {
$this->errors[] = $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification');
return;
}
$carrier = new Carrier((int) $id_carrier);
if (!Validate::isLoadedObject($carrier)) {
return false;
}
$range_inf = Tools::getValue('range_inf');
$range_sup = Tools::getValue('range_sup');
$range_type = Tools::getValue('shipping_method');
$fees = Tools::getValue('fees');
$carrier->deleteDeliveryPrice($carrier->getRangeTable());
if ($range_type != Carrier::SHIPPING_METHOD_FREE) {
foreach ($range_inf as $key => $delimiter1) {
if (!isset($range_sup[$key])) {
continue;
}
$range = $carrier->getRangeObject((int) $range_type);
$range->id_carrier = (int) $carrier->id;
$range->delimiter1 = (float) $delimiter1;
$range->delimiter2 = (float) $range_sup[$key];
$range->save();
if (!Validate::isLoadedObject($range)) {
return false;
}
$price_list = [];
if (is_array($fees) && count($fees)) {
foreach ($fees as $id_zone => $fee) {
$price_list[] = [
'id_range_price' => ($range_type == Carrier::SHIPPING_METHOD_PRICE ? (int) $range->id : null),
'id_range_weight' => ($range_type == Carrier::SHIPPING_METHOD_WEIGHT ? (int) $range->id : null),
'id_carrier' => (int) $carrier->id,
'id_zone' => (int) $id_zone,
'price' => isset($fee[$key]) ? (float) str_replace(',', '.', $fee[$key]) : 0,
];
}
}
if (count($price_list) && !$carrier->addDeliveryPrice($price_list, true)) {
return false;
}
}
}
return true;
}
public function ajaxProcessUploadLogo()
{
if (!$this->access('edit')) {
die('<return result="error" message="' . $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification') . '" />');
}
$logo = ($_FILES['carrier_logo_input'] ?? false);
if ($logo
&& !empty($logo['tmp_name'])
&& $logo['tmp_name'] != 'none'
&& (!isset($logo['error']) || !$logo['error'])
&& ImageManager::isCorrectImageFileExt($logo['name'])
&& is_uploaded_file($logo['tmp_name'])
&& ImageManager::isRealImage($logo['tmp_name'], $logo['type'])
) {
$file = $logo['tmp_name'];
do {
$tmp_name = uniqid() . '.jpg';
} while (file_exists(_PS_TMP_IMG_DIR_ . $tmp_name));
if (!ImageManager::resize($file, _PS_TMP_IMG_DIR_ . $tmp_name)) {
die('<return result="error" message="Impossible to resize the image into ' . Tools::safeOutput(_PS_TMP_IMG_DIR_) . '" />');
}
@unlink($file);
die('<return result="success" message="' . Tools::safeOutput(_PS_TMP_IMG_ . $tmp_name) . '" />');
}
die('<return result="error" message="Cannot upload file" />');
}
public function ajaxProcessFinishStep()
{
$return = ['has_error' => false];
if (!$this->access('edit')) {
$return = [
'has_error' => true,
$return['errors'][] = $this->trans('You do not have permission to use this wizard.', [], 'Admin.Shipping.Notification'),
];
} else {
$this->validateForm(false);
if ($id_carrier = Tools::getValue('id_carrier')) {
$current_carrier = new Carrier((int) $id_carrier);
// if update we duplicate current Carrier
/** @var Carrier $new_carrier */
$new_carrier = $current_carrier->duplicateObject();
if (Validate::isLoadedObject($new_carrier)) {
// Set flag deteled to true for historization
$current_carrier->deleted = true;
$current_carrier->update();
// Fill the new carrier object
$this->copyFromPost($new_carrier, $this->table);
$new_carrier->position = $current_carrier->position;
$new_carrier->update();
$this->updateAssoShop((int) $new_carrier->id);
$this->duplicateLogo((int) $new_carrier->id, (int) $current_carrier->id);
$this->changeGroups((int) $new_carrier->id);
// Copy default carrier
if (Configuration::get('PS_CARRIER_DEFAULT') == $current_carrier->id) {
Configuration::updateValue('PS_CARRIER_DEFAULT', (int) $new_carrier->id);
}
// Call of hooks
Hook::exec('actionCarrierUpdate', [
'id_carrier' => (int) $current_carrier->id,
'carrier' => $new_carrier,
]);
$this->postImage($new_carrier->id);
$this->changeZones($new_carrier->id);
$new_carrier->setTaxRulesGroup((int) Tools::getValue('id_tax_rules_group'));
$carrier = $new_carrier;
}
} else {
$carrier = new Carrier();
$this->copyFromPost($carrier, $this->table);
if (!$carrier->add()) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving this carrier.', [], 'Admin.Shipping.Notification');
}
}
if (isset($carrier)) {
if ($carrier->is_free) {
// if carrier is free delete shipping cost
$carrier->deleteDeliveryPrice('range_weight');
$carrier->deleteDeliveryPrice('range_price');
}
if (Validate::isLoadedObject($carrier)) {
if (!$this->changeGroups((int) $carrier->id)) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving carrier groups.', [], 'Admin.Shipping.Notification');
}
if (!$this->changeZones((int) $carrier->id)) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving carrier zones.', [], 'Admin.Shipping.Notification');
}
if (!$carrier->is_free) {
if (!$this->processRanges((int) $carrier->id)) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving carrier ranges.', [], 'Admin.Shipping.Notification');
}
}
if (Shop::isFeatureActive() && !$this->updateAssoShop((int) $carrier->id)) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving associations of shops.', [], 'Admin.Shipping.Notification');
}
if (!$carrier->setTaxRulesGroup((int) Tools::getValue('id_tax_rules_group'))) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving the tax rules group.', [], 'Admin.Shipping.Notification');
}
if (Tools::getValue('logo')) {
if (Tools::getValue('logo') == 'null' && file_exists(_PS_SHIP_IMG_DIR_ . $carrier->id . '.jpg')) {
unlink(_PS_SHIP_IMG_DIR_ . $carrier->id . '.jpg');
} else {
$logo = basename(Tools::getValue('logo'));
if (!file_exists(_PS_TMP_IMG_DIR_ . $logo) || !copy(_PS_TMP_IMG_DIR_ . $logo, _PS_SHIP_IMG_DIR_ . $carrier->id . '.jpg')) {
$return['has_error'] = true;
$return['errors'][] = $this->trans('An error occurred while saving carrier logo.', [], 'Admin.Shipping.Notification');
}
}
}
$return['id_carrier'] = $carrier->id;
}
}
}
die(json_encode($return));
}
protected function changeGroups(int $id_carrier, bool $delete = true)
{
$carrier = new Carrier((int) $id_carrier);
if (!Validate::isLoadedObject($carrier)) {
return false;
}
return $carrier->setGroups(Tools::getValue('groupBox'));
}
public function changeZones($id)
{
$return = true;
$carrier = new Carrier($id);
if (!Validate::isLoadedObject($carrier)) {
die($this->trans('The object cannot be loaded.', [], 'Admin.Notifications.Error'));
}
$zones = Zone::getZones(false);
foreach ($zones as $zone) {
if (count($carrier->getZone($zone['id_zone']))) {
if (!isset($_POST['zone_' . $zone['id_zone']]) || !$_POST['zone_' . $zone['id_zone']]) {
$return &= $carrier->deleteZone((int) $zone['id_zone']);
}
} elseif (isset($_POST['zone_' . $zone['id_zone']]) && $_POST['zone_' . $zone['id_zone']]) {
$return &= $carrier->addZone((int) $zone['id_zone']);
}
}
return $return;
}
public function getValidationRules()
{
$step_number = (int) Tools::getValue('step_number');
if (!$step_number) {
return;
}
if ($step_number == 4 && !Shop::isFeatureActive() || $step_number == 5 && Shop::isFeatureActive()) {
return ['fields' => []];
}
$step_fields = [
1 => ['name', 'delay', 'grade', 'url'],
2 => ['is_free', 'id_tax_rules_group', 'shipping_handling', 'shipping_method', 'range_behavior'],
3 => ['range_behavior', 'max_height', 'max_width', 'max_depth', 'max_weight'],
4 => [],
];
if (Shop::isFeatureActive()) {
$tmp = $step_fields;
$step_fields = array_slice($tmp, 0, 1, true) + [2 => ['shop']];
$step_fields[3] = $tmp[2];
$step_fields[4] = $tmp[3];
}
$definition = ObjectModel::getDefinition('Carrier');
foreach ($definition['fields'] as $field => $def) {
if (is_array($step_fields[$step_number]) && !in_array($field, $step_fields[$step_number])) {
unset($definition['fields'][$field]);
}
}
return $definition;
}
public function duplicateLogo($new_id, $old_id)
{
$old_logo = _PS_SHIP_IMG_DIR_ . '/' . (int) $old_id . '.jpg';
if (file_exists($old_logo)) {
copy($old_logo, _PS_SHIP_IMG_DIR_ . '/' . (int) $new_id . '.jpg');
}
$old_tmp_logo = _PS_TMP_IMG_DIR_ . '/carrier_mini_' . (int) $old_id . '.jpg';
if (file_exists($old_tmp_logo)) {
if (!isset($_FILES['logo'])) {
copy($old_tmp_logo, _PS_TMP_IMG_DIR_ . '/carrier_mini_' . $new_id . '.jpg');
}
unlink($old_tmp_logo);
}
}
public function getActualCurrency()
{
if ($this->type_context == Shop::CONTEXT_SHOP) {
Shop::setContext($this->type_context, $this->old_context->shop->id);
} elseif ($this->type_context == Shop::CONTEXT_GROUP) {
Shop::setContext($this->type_context, $this->old_context->shop->id_shop_group);
}
$currency = Currency::getDefaultCurrency();
Shop::setContext(Shop::CONTEXT_ALL);
return $currency;
}
}

View File

@@ -0,0 +1,708 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Carrier $object
*/
class AdminCarriersControllerCore extends AdminController
{
/** @var string */
protected $position_identifier = 'id_carrier';
public function __construct()
{
if ($id_carrier = Tools::getValue('id_carrier') && !Tools::isSubmit('deletecarrier') && !Tools::isSubmit('statuscarrier') && !Tools::isSubmit('isFreecarrier')) {
Tools::redirectAdmin(Context::getContext()->link->getAdminLink('AdminCarrierWizard', true, [], ['id_carrier' => (int) $id_carrier]));
}
$this->bootstrap = true;
$this->table = 'carrier';
$this->className = 'Carrier';
$this->lang = false;
$this->deleted = true;
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_defaultOrderBy = 'position';
parent::__construct();
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Notifications.Info'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Info'),
'icon' => 'icon-trash',
],
];
$this->fieldImageSettings = [
'name' => 'logo',
'dir' => 's',
];
$this->fields_list = [
'id_carrier' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
],
'image' => [
'title' => $this->trans('Logo', [], 'Admin.Global'),
'align' => 'center',
'image' => 's',
'class' => 'fixed-width-xs',
'orderby' => false,
'search' => false,
],
'delay' => [
'title' => $this->trans('Delay', [], 'Admin.Shipping.Feature'),
'orderby' => false,
],
'active' => [
'title' => $this->trans('Status', [], 'Admin.Global'),
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'class' => 'fixed-width-sm',
'orderby' => false,
],
'is_free' => [
'title' => $this->trans('Free Shipping', [], 'Admin.Shipping.Feature'),
'align' => 'center',
'active' => 'isFree',
'type' => 'bool',
'class' => 'fixed-width-sm',
'orderby' => false,
],
'position' => [
'title' => $this->trans('Position', [], 'Admin.Global'),
'filter_key' => 'a!position',
'align' => 'center',
'class' => 'fixed-width-sm',
'position' => 'position',
],
];
}
/**
* Extends the renderOptions method to add a notice about migration to Symfony.
*/
public function renderOptions()
{
// Get original options, probably none in case of this controller
$renderedOptions = parent::renderOptions();
// Add a notice about the new carrier page
$renderedOptions .= sprintf(
'<div class="alert alert-info"><p>%s</p><p>%s</p></div>',
$this->trans(
'[1]A new, improved version of this page is ready.[/1] You can enable it with a single click in the [2]New & Experimental Features[/2] section.',
[
'[1]' => '<strong>',
'[/1]' => '</strong>',
'[2]' => '<a href="' . $this->context->link->getAdminLink('AdminFeatureFlag') . '">',
'[/2]' => '</a>',
],
'Admin.Shipping.Feature'
),
$this->trans(
'For compatibility reasons, the original version remains enabled on your shop. If no incompatible modules are holding you back, feel free to switch to the new version. The legacy version will eventually be removed in a future release.',
[],
'Admin.Shipping.Feature'
)
);
return $renderedOptions;
}
public function initToolbar()
{
parent::initToolbar();
if (isset($this->toolbar_btn['new']) && $this->display != 'view') {
$this->toolbar_btn['new']['href'] = $this->context->link->getAdminLink('AdminCarrierWizard');
}
}
public function initPageHeaderToolbar()
{
$this->page_header_toolbar_title = $this->trans('Carriers', [], 'Admin.Shipping.Feature');
if ($this->display != 'view') {
$this->page_header_toolbar_btn['new_carrier'] = [
'href' => $this->context->link->getAdminLink('AdminCarrierWizard'),
'desc' => $this->trans('Add new carrier', [], 'Admin.Shipping.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function renderList()
{
$this->_select = 'b.*';
$this->_join = 'INNER JOIN `' . _DB_PREFIX_ . 'carrier_lang` b ON a.id_carrier = b.id_carrier' . Shop::addSqlRestrictionOnLang('b') . ' AND b.id_lang = ' . (int) $this->context->language->id . ' LEFT JOIN `' . _DB_PREFIX_ . 'carrier_tax_rules_group_shop` ctrgs ON (a.`id_carrier` = ctrgs.`id_carrier` AND ctrgs.id_shop=' . (int) $this->context->shop->id . ')';
$this->_use_found_rows = false;
// test if need to show header alert.
$this->context->smarty->assign([
'showHeaderAlert' => (Db::getInstance()->executeS(
'SELECT COUNT(1) FROM `' . _DB_PREFIX_ . 'carrier` WHERE deleted = 0 AND id_reference > 2',
false
)->fetchColumn(0) == 0),
]);
return parent::renderList();
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderForm()
{
$this->fields_form = [
'legend' => [
'title' => $this->trans('Carriers', [], 'Admin.Shipping.Feature'),
'icon' => 'icon-truck',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Company', [], 'Admin.Global'),
'name' => 'name',
'required' => true,
'hint' => [
$this->trans('Allowed characters: letters, spaces and "%special_chars%".', ['%special_chars%' => '().-'], 'Admin.Shipping.Help'),
$this->trans('Carrier name displayed during checkout', [], 'Admin.Shipping.Help'),
$this->trans('For in-store pickup, enter 0 to replace the carrier name with your shop name.', [], 'Admin.Shipping.Help'),
],
],
[
'type' => 'file',
'label' => $this->trans('Logo', [], 'Admin.Global'),
'name' => 'logo',
'hint' => $this->trans('Upload a logo from your computer.', [], 'Admin.Shipping.Help') .
' (.gif, .jpg, .jpeg, .webp ' .
$this->trans('or', [], 'Admin.Shipping.Help') . ' .png)',
],
[
'type' => 'text',
'label' => $this->trans('Transit time', [], 'Admin.Shipping.Feature'),
'name' => 'delay',
'lang' => true,
'required' => true,
'maxlength' => 512,
'hint' => $this->trans('Estimated delivery time will be displayed during checkout.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Speed grade', [], 'Admin.Shipping.Feature'),
'name' => 'grade',
'required' => false,
'hint' => $this->trans('Enter "0" for a longest shipping delay, or "9" for the shortest shipping delay.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('URL', [], 'Admin.Global'),
'name' => 'url',
'hint' => $this->trans('Delivery tracking URL: Type \'@\' where the tracking number should appear. It will then be automatically replaced by the tracking number.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'checkbox',
'label' => $this->trans('Zone', [], 'Admin.Global'),
'name' => 'zone',
'values' => [
'query' => Zone::getZones(false),
'id' => 'id_zone',
'name' => 'name',
],
'hint' => $this->trans('The zones in which this carrier will be used.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'group',
'label' => $this->trans('Group access', [], 'Admin.Shipping.Help'),
'name' => 'groupBox',
'values' => Group::getGroups(Context::getContext()->language->id),
'hint' => $this->trans('Mark the groups that are allowed access to this carrier.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Status', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Enable the carrier in the front office.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Apply shipping cost', [], 'Admin.Shipping.Feature'),
'name' => 'is_free',
'required' => false,
'class' => 't',
'values' => [
[
'id' => 'is_free_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'is_free_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Apply both regular shipping cost and product-specific shipping costs.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'select',
'label' => $this->trans('Tax', [], 'Admin.Global'),
'name' => 'id_tax_rules_group',
'options' => [
'query' => TaxRulesGroup::getTaxRulesGroups(true),
'id' => 'id_tax_rules_group',
'name' => 'name',
'default' => [
'label' => $this->trans('No Tax', [], 'Admin.Global'),
'value' => 0,
],
],
],
[
'type' => 'switch',
'label' => $this->trans('Shipping and handling', [], 'Admin.Shipping.Feature'),
'name' => 'shipping_handling',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'shipping_handling_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'shipping_handling_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Include the shipping and handling costs in the carrier price.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'radio',
'label' => $this->trans('Billing', [], 'Admin.Shipping.Feature'),
'name' => 'shipping_method',
'required' => false,
'class' => 't',
'br' => true,
'values' => [
[
'id' => 'billing_default',
'value' => Carrier::SHIPPING_METHOD_DEFAULT,
'label' => $this->trans('Default behavior', [], 'Admin.Shipping.Feature'),
],
[
'id' => 'billing_price',
'value' => Carrier::SHIPPING_METHOD_PRICE,
'label' => $this->trans('According to total price', [], 'Admin.Shipping.Feature'),
],
[
'id' => 'billing_weight',
'value' => Carrier::SHIPPING_METHOD_WEIGHT,
'label' => $this->trans('According to total weight', [], 'Admin.Shipping.Feature'),
],
],
],
[
'type' => 'select',
'label' => $this->trans('Out-of-range behavior', [], 'Admin.Shipping.Feature'),
'name' => 'range_behavior',
'options' => [
'query' => [
[
'id' => 0,
'name' => $this->trans('Apply the cost of the highest defined range', [], 'Admin.Shipping.Help'),
],
[
'id' => 1,
'name' => $this->trans('Disable carrier', [], 'Admin.Shipping.Feature'),
],
],
'id' => 'id',
'name' => 'name',
],
'hint' => $this->trans('Out-of-range behavior occurs when none is defined (e.g. when a customer\'s cart weight is greater than the highest range limit).', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package height', [], 'Admin.Shipping.Feature'),
'name' => 'max_height',
'required' => false,
'hint' => $this->trans('Maximum height managed by this carrier. Set the value to "0," or leave this field blank to ignore.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package width', [], 'Admin.Shipping.Feature'),
'name' => 'max_width',
'required' => false,
'hint' => $this->trans('Maximum width managed by this carrier. Set the value to "0," or leave this field blank to ignore.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package depth', [], 'Admin.Shipping.Feature'),
'name' => 'max_depth',
'required' => false,
'hint' => $this->trans('Maximum depth managed by this carrier. Set the value to "0," or leave this field blank to ignore.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'text',
'label' => $this->trans('Maximum package weight', [], 'Admin.Shipping.Feature'),
'name' => 'max_weight',
'required' => false,
'hint' => $this->trans('Maximum weight managed by this carrier. Set the value to "0," or leave this field blank to ignore.', [], 'Admin.Shipping.Help'),
],
[
'type' => 'hidden',
'name' => 'is_module',
],
[
'type' => 'hidden',
'name' => 'external_module_name',
],
[
'type' => 'hidden',
'name' => 'shipping_external',
],
[
'type' => 'hidden',
'name' => 'need_range',
],
],
];
if (Shop::isFeatureActive()) {
$this->fields_form['input'][] = [
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
];
}
$this->fields_form['submit'] = [
'title' => $this->trans('Save', [], 'Admin.Actions'),
];
if (!($obj = $this->loadObject(true))) {
return;
}
$this->getFieldsValues($obj);
return parent::renderForm();
}
public function postProcess()
{
if (Tools::getValue('submitAdd' . $this->table)) {
/* Checking fields validity */
$this->validateRules();
if (!count($this->errors)) {
$id = (int) Tools::getValue('id_' . $this->table);
/* Object update */
if (!empty($id)) {
try {
if ($this->access('edit')) {
$current_carrier = new Carrier($id);
if (!Validate::isLoadedObject($current_carrier)) {
throw new PrestaShopException('Cannot load Carrier object');
}
/** @var Carrier $new_carrier */
// Duplicate current Carrier
$new_carrier = $current_carrier->duplicateObject();
if (Validate::isLoadedObject($new_carrier)) {
// Set flag deteled to true for historization
$current_carrier->deleted = true;
$current_carrier->update();
// Fill the new carrier object
$this->copyFromPost($new_carrier, $this->table);
$new_carrier->position = $current_carrier->position;
$new_carrier->update();
$this->updateAssoShop($new_carrier->id);
$new_carrier->copyCarrierData((int) $current_carrier->id);
$this->changeGroups($new_carrier->id);
// Call of hooks
Hook::exec('actionCarrierUpdate', [
'id_carrier' => (int) $current_carrier->id,
'carrier' => $new_carrier,
]);
$this->postImage($new_carrier->id);
$this->changeZones($new_carrier->id);
$new_carrier->setTaxRulesGroup((int) Tools::getValue('id_tax_rules_group'));
Tools::redirectAdmin(self::$currentIndex . '&id_' . $this->table . '=' . $current_carrier->id . '&conf=4&token=' . $this->token);
} else {
$this->errors[] = $this->trans('An error occurred while updating an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b>';
}
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
} catch (PrestaShopException $e) {
$this->errors[] = $e->getMessage();
}
} else {
// Object creation
if ($this->access('add')) {
// Create new Carrier
$carrier = new Carrier();
$this->copyFromPost($carrier, $this->table);
$carrier->position = Carrier::getHigherPosition() + 1;
if ($carrier->add()) {
if (($_POST['id_' . $this->table] = $carrier->id /* voluntary */) && $this->postImage($carrier->id) && $this->_redirect) {
$carrier->setTaxRulesGroup((int) Tools::getValue('id_tax_rules_group'), true);
$this->changeZones($carrier->id);
$this->changeGroups($carrier->id);
$this->updateAssoShop($carrier->id);
Tools::redirectAdmin(self::$currentIndex . '&id_' . $this->table . '=' . $carrier->id . '&conf=3&token=' . $this->token);
}
} else {
$this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b>';
}
} else {
$this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error');
}
}
}
parent::postProcess();
} elseif (isset($_GET['isFree' . $this->table])) {
if (!$this->access('edit')) {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
return;
}
$this->processIsFree();
} else {
parent::postProcess();
Carrier::cleanPositions();
}
}
public function processIsFree()
{
$carrier = new Carrier($this->id_object);
if (!Validate::isLoadedObject($carrier)) {
$this->errors[] = $this->trans('An error occurred while updating carrier information.', [], 'Admin.Shipping.Notification');
}
$carrier->is_free = !$carrier->is_free;
if (!$carrier->update()) {
$this->errors[] = $this->trans('An error occurred while updating carrier information.', [], 'Admin.Shipping.Notification');
}
Tools::redirectAdmin(self::$currentIndex . '&conf=5&token=' . $this->token);
}
/**
* Overload the property $fields_value.
*
* @param object $obj
*/
public function getFieldsValues($obj)
{
if ($this->getFieldValue($obj, 'is_module')) {
$this->fields_value['is_module'] = 1;
}
if ($this->getFieldValue($obj, 'shipping_external')) {
$this->fields_value['shipping_external'] = 1;
}
if ($this->getFieldValue($obj, 'need_range')) {
$this->fields_value['need_range'] = 1;
}
// Added values of object Zone
$carrier_zones = $obj->getZones();
$carrier_zones_ids = [];
if (is_array($carrier_zones)) {
foreach ($carrier_zones as $carrier_zone) {
$carrier_zones_ids[] = $carrier_zone['id_zone'];
}
}
$zones = Zone::getZones(false);
foreach ($zones as $zone) {
$this->fields_value['zone_' . $zone['id_zone']] = Tools::getValue('zone_' . $zone['id_zone'], in_array($zone['id_zone'], $carrier_zones_ids));
}
// Added values of object Group
$carrier_groups = $obj->getGroups();
$carrier_groups_ids = [];
if (is_array($carrier_groups)) {
foreach ($carrier_groups as $carrier_group) {
$carrier_groups_ids[] = $carrier_group['id_group'];
}
}
$groups = Group::getGroups($this->context->language->id);
foreach ($groups as $group) {
$this->fields_value['groupBox_' . $group['id_group']] = Tools::getValue('groupBox_' . $group['id_group'], in_array($group['id_group'], $carrier_groups_ids) || empty($carrier_groups_ids) && !$obj->id);
}
$this->fields_value['id_tax_rules_group'] = $this->object->getIdTaxRulesGroup($this->context);
}
/**
* @param Carrier $object
*
* @return bool
*/
protected function beforeDelete($object)
{
return (bool) $object->isUsed();
}
protected function changeGroups(int $id_carrier, bool $delete = true)
{
if ($delete) {
Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'carrier_group WHERE id_carrier = ' . (int) $id_carrier);
}
$groups = Db::getInstance()->executeS('SELECT id_group FROM `' . _DB_PREFIX_ . 'group`');
foreach ($groups as $group) {
if (Tools::getIsset('groupBox') && in_array($group['id_group'], Tools::getValue('groupBox'))) {
Db::getInstance()->execute('
INSERT INTO ' . _DB_PREFIX_ . 'carrier_group (id_group, id_carrier)
VALUES(' . (int) $group['id_group'] . ',' . (int) $id_carrier . ')
');
}
}
}
public function changeZones($id)
{
/** @var Carrier $carrier */
$carrier = new $this->className($id);
if (!Validate::isLoadedObject($carrier)) {
die($this->trans('The object cannot be loaded.', [], 'Admin.Notifications.Error'));
}
$zones = Zone::getZones(false);
foreach ($zones as $zone) {
if (count($carrier->getZone($zone['id_zone']))) {
if (!isset($_POST['zone_' . $zone['id_zone']]) || !$_POST['zone_' . $zone['id_zone']]) {
$carrier->deleteZone($zone['id_zone']);
}
} elseif (isset($_POST['zone_' . $zone['id_zone']]) && $_POST['zone_' . $zone['id_zone']]) {
$carrier->addZone($zone['id_zone']);
}
}
}
public function ajaxProcessUpdatePositions()
{
$way = (bool) Tools::getValue('way');
$id_carrier = (int) Tools::getValue('id');
$positions = Tools::getValue($this->table);
foreach ($positions as $position => $value) {
$pos = explode('_', $value);
if (isset($pos[2]) && (int) $pos[2] === $id_carrier) {
$carrier = new Carrier((int) $pos[2]);
if (Validate::isLoadedObject($carrier)) {
if (isset($position) && $carrier->updatePosition($way, $position)) {
echo 'ok position ' . (int) $position . ' for carrier ' . (int) $pos[1] . '\r\n';
} else {
echo '{"hasError" : true, "errors" : "Can not update carrier ' . (int) $id_carrier . ' to position ' . (int) $position . ' "}';
}
} else {
echo '{"hasError" : true, "errors" : "This carrier (' . (int) $id_carrier . ') can t be loaded"}';
}
break;
}
}
}
public function displayEditLink($token, $id, $name = null)
{
if ($this->access('edit')) {
$tpl = $this->createTemplate('helpers/list/list_action_edit.tpl');
if (!array_key_exists('Edit', self::$cache_lang)) {
self::$cache_lang['Edit'] = $this->trans('Edit', [], 'Admin.Actions');
}
$tpl->assign([
'href' => $this->context->link->getAdminLink('AdminCarrierWizard', true, [], ['id_carrier' => (int) $id]),
'action' => self::$cache_lang['Edit'],
'id' => $id,
]);
return $tpl->fetch();
} else {
return;
}
}
public function displayDeleteLink($token, $id, $name = null)
{
if ($this->access('delete')) {
$tpl = $this->createTemplate('helpers/list/list_action_delete.tpl');
if (!array_key_exists('Delete', self::$cache_lang)) {
self::$cache_lang['Delete'] = $this->trans('Delete', [], 'Admin.Actions');
}
if (!array_key_exists('DeleteItem', self::$cache_lang)) {
self::$cache_lang['DeleteItem'] = $this->trans('Delete selected item?', [], 'Admin.Notifications.Info');
}
if (!array_key_exists('Name', self::$cache_lang)) {
self::$cache_lang['Name'] = $this->trans('Name:', [], 'Admin.Shipping.Feature');
}
if (null !== $name) {
$name = '\n\n' . self::$cache_lang['Name'] . ' ' . $name;
}
$data = [
$this->identifier => $id,
'href' => $this->context->link->getAdminLink('AdminCarriers', true, [], ['id_carrier' => (int) $id, 'deletecarrier' => 1]),
'action' => self::$cache_lang['Delete'],
];
if ($this->specificConfirmDelete !== false) {
$data['confirm'] = null !== $this->specificConfirmDelete ? '\r' . $this->specificConfirmDelete : addcslashes(Tools::htmlentitiesDecodeUTF8(self::$cache_lang['DeleteItem'] . $name), '\'');
}
$tpl->assign(array_merge($this->tpl_delete_link_vars, $data));
return $tpl->fetch();
} else {
return;
}
}
}

View File

@@ -0,0 +1,877 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property CartRule $object
*/
class AdminCartRulesControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'cart_rule';
$this->className = 'CartRule';
$this->lang = true;
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_orderWay = 'DESC';
parent::__construct();
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'icon' => 'icon-trash',
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
],
];
$this->fields_list = [
'id_cart_rule' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'align' => 'center', 'class' => 'fixed-width-xs'],
'name' => ['title' => $this->trans('Name', [], 'Admin.Global')],
'priority' => ['title' => $this->trans('Priority', [], 'Admin.Global'), 'align' => 'center', 'class' => 'fixed-width-xs'],
'code' => ['title' => $this->trans('Code', [], 'Admin.Global'), 'class' => 'fixed-width-sm'],
'quantity' => ['title' => $this->trans('Quantity', [], 'Admin.Catalog.Feature'), 'align' => 'center', 'class' => 'fixed-width-xs'],
'date_to' => ['title' => $this->trans('Expiration date', [], 'Admin.Catalog.Feature'), 'type' => 'datetime', 'class' => 'fixed-width-lg'],
'active' => ['title' => $this->trans('Status', [], 'Admin.Global'), 'active' => 'status', 'type' => 'bool', 'align' => 'center', 'class' => 'fixed-width-xs', 'orderby' => false],
];
}
public function ajaxProcessLoadCartRules()
{
if (!$this->access('view')) {
return die(json_encode(['error' => 'You do not have the right permission']));
}
$type = $token = $search = '';
$limit = $count = $id_cart_rule = 0;
if (Tools::getIsset('limit')) {
$limit = Tools::getValue('limit');
}
if (Tools::getIsset('type')) {
$type = Tools::getValue('type');
}
if (Tools::getIsset('count')) {
$count = Tools::getValue('count');
}
if (Tools::getIsset('id_cart_rule')) {
$id_cart_rule = Tools::getValue('id_cart_rule');
}
if (Tools::getIsset('search')) {
$search = Tools::getValue('search');
}
$page = (int) floor($count / $limit);
$html = '';
$next_link = '';
if (($page * $limit) + 1 == $count || $count == 0) {
if ($count == 0) {
$count = 1;
}
/** @var CartRule $current_object */
$current_object = $this->loadObject(true);
$cart_rules = $current_object->getAssociatedRestrictions('cart_rule', false, true, $page * $limit, $limit, $search);
if ($type == 'selected') {
$i = 1;
foreach ($cart_rules['selected'] as $cart_rule) {
$html .= '<option value="' . (int) $cart_rule['id_cart_rule'] . '">&nbsp;' . Tools::safeOutput($cart_rule['name']) . '</option>';
if ($i == $limit) {
break;
}
++$i;
}
if ($i == $limit) {
$next_link = Context::getContext()->link->getAdminLink('AdminCartRules') . '&ajaxMode=1&ajax=1&id_cart_rule=' . (int) $id_cart_rule . '&action=loadCartRules&limit=' . (int) $limit . '&type=selected&count=' . ($count - 1 + count($cart_rules['selected']) . '&search=' . urlencode($search));
}
} else {
$i = 1;
foreach ($cart_rules['unselected'] as $cart_rule) {
$html .= '<option value="' . (int) $cart_rule['id_cart_rule'] . '">&nbsp;' . Tools::safeOutput($cart_rule['name']) . '</option>';
if ($i == $limit) {
break;
}
++$i;
}
if ($i == $limit) {
$next_link = Context::getContext()->link->getAdminLink('AdminCartRules') . '&ajaxMode=1&ajax=1&id_cart_rule=' . (int) $id_cart_rule . '&action=loadCartRules&limit=' . (int) $limit . '&type=unselected&count=' . ($count - 1 + count($cart_rules['unselected']) . '&search=' . urlencode($search));
}
}
}
echo json_encode(['html' => $html, 'next_link' => $next_link]);
}
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryPlugin(['typewatch', 'fancybox', 'autocomplete']);
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_cart_rule'] = [
'href' => self::$currentIndex . '&addcart_rule&token=' . $this->token,
'desc' => $this->trans('Add new cart rule', [], 'Admin.Catalog.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function postProcess()
{
if (Tools::isSubmit('submitAddcart_rule') || Tools::isSubmit('submitAddcart_ruleAndStay')) {
// If the reduction is associated to a specific product, then it must be part of the product restrictions
if ((int) Tools::getValue('reduction_product') && Tools::getValue('apply_discount_to') == 'specific' && Tools::getValue('apply_discount') != 'off') {
$reduction_product = (int) Tools::getValue('reduction_product');
// First, check if it is not already part of the restrictions
$already_restricted = false;
if (is_array($rule_group_array = Tools::getValue('product_rule_group')) && count($rule_group_array) && Tools::getValue('product_restriction')) {
foreach ($rule_group_array as $rule_group_id) {
if (is_array($rule_array = Tools::getValue('product_rule_' . $rule_group_id)) && count($rule_array)) {
foreach ($rule_array as $rule_id) {
if (Tools::getValue('product_rule_' . $rule_group_id . '_' . $rule_id . '_type') == 'products'
&& in_array($reduction_product, Tools::getValue('product_rule_select_' . $rule_group_id . '_' . $rule_id))) {
$already_restricted = true;
break 2;
}
}
}
}
}
if ($already_restricted == false) {
// Check the product restriction
$_POST['product_restriction'] = 1;
// Add a new rule group
$rule_group_id = 1;
if (is_array($rule_group_array)) {
// Find the first rule_group_id that is not available in the array
while (in_array($rule_group_id, $rule_group_array)) {
++$rule_group_id;
}
$_POST['product_rule_group'][] = $rule_group_id;
} else {
$_POST['product_rule_group'] = [$rule_group_id];
}
// Set a quantity of 1 for this new rule group
$_POST['product_rule_group_' . $rule_group_id . '_quantity'] = 1;
// Add one rule to the new rule group
$_POST['product_rule_' . $rule_group_id] = [1];
// Set a type 'product' for this 1 rule
$_POST['product_rule_' . $rule_group_id . '_1_type'] = 'products';
// Add the product in the selected products
$_POST['product_rule_select_' . $rule_group_id . '_1'] = [$reduction_product];
}
}
// These are checkboxes (which aren't sent through POST when they are not checked), so they are forced to 0
foreach (['country', 'carrier', 'group', 'cart_rule', 'product', 'shop'] as $type) {
if (!Tools::getValue($type . '_restriction')) {
$_POST[$type . '_restriction'] = 0;
}
}
// If the restriction is checked, but no item is selected, raise an error
foreach (['country', 'carrier', 'group', 'shop'] as $type) {
if (Tools::getValue($type . '_restriction') && empty(Tools::getValue($type . '_select'))) {
switch ($type) {
case 'country':
$restriction_name = $this->trans('Country selection', [], 'Admin.Catalog.Feature');
break;
case 'carrier':
$restriction_name = $this->trans('Carrier selection', [], 'Admin.Catalog.Feature');
break;
case 'group':
$restriction_name = $this->trans('Customer group selection', [], 'Admin.Catalog.Feature');
break;
case 'shop':
default:
$restriction_name = $this->trans('Store selection', [], 'Admin.Catalog.Feature');
break;
}
$this->errors[] = $this->trans('The "%s" restriction is checked, but no item is selected.', [$restriction_name], 'Admin.Catalog.Notification');
}
}
// Remove the gift if the radio button is set to "no"
if (!(int) Tools::getValue('free_gift')) {
$_POST['gift_product'] = 0;
}
// Retrieve the product attribute id of the gift (if available)
if ($id_product = (int) Tools::getValue('gift_product')) {
$_POST['gift_product_attribute'] = (int) Tools::getValue('ipa_' . $id_product);
}
// Do not allow products with required customization
if (!empty($_POST['gift_product']) && count(Product::getRequiredCustomizableFieldsStatic((int) $_POST['gift_product']))) {
$this->errors[] = $this->trans('Product with required customization fields cannot be used as a gift.', [], 'Admin.Catalog.Notification');
}
// Idiot-proof control
if (strtotime(Tools::getValue('date_from')) > strtotime(Tools::getValue('date_to'))) {
$this->errors[] = $this->trans('The voucher cannot end before it begins.', [], 'Admin.Catalog.Notification');
}
if ((int) Tools::getValue('minimum_amount') < 0) {
$this->errors[] = $this->trans('The minimum amount cannot be lower than zero.', [], 'Admin.Catalog.Notification');
}
if ((float) Tools::getValue('reduction_percent') < 0 || (float) Tools::getValue('reduction_percent') > 100) {
$this->errors[] = $this->trans('Reduction percentage must be between 0% and 100%', [], 'Admin.Catalog.Notification');
}
if ((int) Tools::getValue('reduction_amount') < 0) {
$this->errors[] = $this->trans('Reduction amount cannot be lower than zero.', [], 'Admin.Catalog.Notification');
}
if (Tools::getValue('code') && ($same_code = (int) CartRule::getIdByCode(Tools::getValue('code'))) && $same_code != Tools::getValue('id_cart_rule')) {
$this->errors[] = $this->trans('This cart rule code is already used (conflict with cart rule %rulename%)', ['%rulename%' => $same_code], 'Admin.Catalog.Notification');
}
if (Tools::getValue('apply_discount') == 'off' && !Tools::getValue('free_shipping') && !Tools::getValue('free_gift')) {
$this->errors[] = $this->trans('An action is required for this cart rule.', [], 'Admin.Catalog.Notification');
}
}
return parent::postProcess();
}
public function processDelete()
{
$res = parent::processDelete();
if (Tools::isSubmit('delete' . $this->table)) {
$back = rawurldecode(Tools::getValue('back', ''));
if (!empty($back)) {
$back .= (strpos($back, '?') === false ? '?' : '&') . 'conf=1';
$this->redirect_after = $back;
}
}
return $res;
}
/**
* @param CartRule $current_object
*
* @return bool|void
*
* @throws PrestaShopDatabaseException
*/
protected function afterUpdate($current_object)
{
// All the associations are deleted for an update, then recreated when we call the "afterAdd" method
$id_cart_rule = Tools::getValue('id_cart_rule');
foreach (['country', 'carrier', 'group', 'product_rule_group', 'shop'] as $type) {
Db::getInstance()->delete('cart_rule_' . $type, '`id_cart_rule` = ' . (int) $id_cart_rule);
}
Db::getInstance()->delete('cart_rule_product_rule', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule_group`
WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule_group` = `' . _DB_PREFIX_ . 'cart_rule_product_rule_group`.`id_product_rule_group`)');
Db::getInstance()->delete('cart_rule_product_rule_value', 'NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'cart_rule_product_rule`
WHERE `' . _DB_PREFIX_ . 'cart_rule_product_rule_value`.`id_product_rule` = `' . _DB_PREFIX_ . 'cart_rule_product_rule`.`id_product_rule`)');
Db::getInstance()->delete('cart_rule_combination', '`id_cart_rule_1` = ' . (int) $id_cart_rule . ' OR `id_cart_rule_2` = ' . (int) $id_cart_rule);
$this->afterAdd($current_object);
}
public function processAdd()
{
if ($cart_rule = parent::processAdd()) {
$this->context->smarty->assign('new_cart_rule', $cart_rule);
}
if (Tools::getValue('submitFormAjax')) {
$this->redirect_after = null;
if ($cart_rule) {
$this->context->smarty->assign('refresh_cart', true);
$this->display = 'edit';
}
}
return $cart_rule;
}
/**
* @TODO Move this function into CartRule
*
* @param CartRule $currentObject
*
* @return bool|void
*
* @throws PrestaShopDatabaseException
*/
protected function afterAdd($currentObject)
{
// Add shop restrictions if employee has not access to all shops
$context = Context::getContext();
$all_shops = Shop::getCompleteListOfShopsID();
if ($context->employee->isSuperAdmin()) {
$employee_shops = $all_shops;
} else {
$employee_shops = $context->employee->getAssociatedShops();
}
if (count($all_shops) > count($employee_shops) && Tools::getValue('shop_restriction') == '0') {
$values = [];
foreach ($employee_shops as $id) {
$values[] = '(' . (int) $currentObject->id . ',' . (int) $id . ')';
}
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_shop` (`id_cart_rule`, `id_shop`) VALUES ' . implode(',', $values));
}
// Add restrictions for generic entities like country, carrier and group
foreach (['country', 'carrier', 'group', 'shop'] as $type) {
if (Tools::getValue($type . '_restriction') && is_array($array = Tools::getValue($type . '_select')) && count($array)) {
$values = [];
foreach ($array as $id) {
$values[] = '(' . (int) $currentObject->id . ',' . (int) $id . ')';
}
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_' . $type . '` (`id_cart_rule`, `id_' . $type . '`) VALUES ' . implode(',', $values));
}
}
// Add cart rule restrictions
if (Tools::getValue('cart_rule_restriction') && is_array($array = Tools::getValue('cart_rule_select')) && count($array)) {
$values = [];
foreach ($array as $id) {
$values[] = '(' . (int) $currentObject->id . ',' . (int) $id . ')';
}
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_combination` (`id_cart_rule_1`, `id_cart_rule_2`) VALUES ' . implode(',', $values));
}
// Add product rule restrictions
if (Tools::getValue('product_restriction') && is_array($ruleGroupArray = Tools::getValue('product_rule_group')) && count($ruleGroupArray)) {
foreach ($ruleGroupArray as $ruleGroupId) {
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_product_rule_group` (`id_cart_rule`, `quantity`)
VALUES (' . (int) $currentObject->id . ', ' . (int) Tools::getValue('product_rule_group_' . $ruleGroupId . '_quantity') . ')');
$id_product_rule_group = Db::getInstance()->Insert_ID();
if (is_array($ruleArray = Tools::getValue('product_rule_' . $ruleGroupId)) && count($ruleArray)) {
foreach ($ruleArray as $ruleId) {
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_product_rule` (`id_product_rule_group`, `type`)
VALUES (' . (int) $id_product_rule_group . ', "' . pSQL(Tools::getValue('product_rule_' . $ruleGroupId . '_' . $ruleId . '_type')) . '")');
$id_product_rule = Db::getInstance()->Insert_ID();
$values = [];
foreach (Tools::getValue('product_rule_select_' . $ruleGroupId . '_' . $ruleId) as $id) {
$values[] = '(' . (int) $id_product_rule . ',' . (int) $id . ')';
}
$values = array_unique($values);
if (count($values)) {
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_product_rule_value` (`id_product_rule`, `id_item`) VALUES ' . implode(',', $values));
}
}
}
}
}
// If the new rule has no cart rule restriction, then it must be added to the white list of the other cart rules that have restrictions
if (!Tools::getValue('cart_rule_restriction')) {
Db::getInstance()->execute('
INSERT INTO `' . _DB_PREFIX_ . 'cart_rule_combination` (`id_cart_rule_1`, `id_cart_rule_2`) (
SELECT id_cart_rule, ' . (int) $currentObject->id . ' FROM `' . _DB_PREFIX_ . 'cart_rule` WHERE cart_rule_restriction = 1
)');
} else {
// And if the new cart rule has restrictions, previously unrestricted cart rules may now be restricted (a mug of coffee is strongly advised to understand this sentence)
$ruleCombinations = Db::getInstance()->executeS('
SELECT cr.id_cart_rule
FROM ' . _DB_PREFIX_ . 'cart_rule cr
WHERE cr.id_cart_rule != ' . (int) $currentObject->id . '
AND cr.cart_rule_restriction = 0
AND NOT EXISTS (
SELECT 1
FROM ' . _DB_PREFIX_ . 'cart_rule_combination
WHERE cr.id_cart_rule = ' . _DB_PREFIX_ . 'cart_rule_combination.id_cart_rule_2 AND ' . (int) $currentObject->id . ' = id_cart_rule_1
)
AND NOT EXISTS (
SELECT 1
FROM ' . _DB_PREFIX_ . 'cart_rule_combination
WHERE cr.id_cart_rule = ' . _DB_PREFIX_ . 'cart_rule_combination.id_cart_rule_1 AND ' . (int) $currentObject->id . ' = id_cart_rule_2
)
');
foreach ($ruleCombinations as $incompatibleRule) {
Db::getInstance()->execute('UPDATE `' . _DB_PREFIX_ . 'cart_rule` SET cart_rule_restriction = 1 WHERE id_cart_rule = ' . (int) $incompatibleRule['id_cart_rule'] . ' LIMIT 1');
Db::getInstance()->execute('
INSERT IGNORE INTO `' . _DB_PREFIX_ . 'cart_rule_combination` (`id_cart_rule_1`, `id_cart_rule_2`) (
SELECT id_cart_rule, ' . (int) $incompatibleRule['id_cart_rule'] . ' FROM `' . _DB_PREFIX_ . 'cart_rule`
WHERE active = 1
AND id_cart_rule != ' . (int) $currentObject->id . '
AND id_cart_rule != ' . (int) $incompatibleRule['id_cart_rule'] . '
)');
}
}
}
/**
* Retrieve the cart rule product rule groups in the POST data
* if available, and in the database if there is none.
*
* @param CartRule $cart_rule
*
* @return array
*/
public function getProductRuleGroupsDisplay($cart_rule)
{
$productRuleGroupsArray = [];
if (Tools::getValue('product_restriction') && is_array($array = Tools::getValue('product_rule_group')) && count($array)) {
$i = 1;
foreach ($array as $ruleGroupId) {
$productRulesArray = [];
$arrayPRGroupId = Tools::getValue('product_rule_' . $ruleGroupId);
if (is_array($arrayPRGroupId) && count($arrayPRGroupId)) {
foreach ($arrayPRGroupId as $ruleId) {
$productRulesArray[] = $this->getProductRuleDisplay(
$ruleGroupId,
$ruleId,
Tools::getValue('product_rule_' . $ruleGroupId . '_' . $ruleId . '_type'),
Tools::getValue('product_rule_select_' . $ruleGroupId . '_' . $ruleId)
);
}
}
$productRuleGroupsArray[] = $this->getProductRuleGroupDisplay(
$i++,
(int) Tools::getValue('product_rule_group_' . $ruleGroupId . '_quantity'),
$productRulesArray
);
}
} else {
$i = 1;
foreach ($cart_rule->getProductRuleGroups() as $productRuleGroup) {
$j = 1;
$productRulesDisplay = [];
foreach ($productRuleGroup['product_rules'] as $productRule) {
$productRulesDisplay[] = $this->getProductRuleDisplay($i, $j++, $productRule['type'], $productRule['values']);
}
$productRuleGroupsArray[] = $this->getProductRuleGroupDisplay($i++, $productRuleGroup['quantity'], $productRulesDisplay);
}
}
return $productRuleGroupsArray;
}
/* Return the form for a single cart rule group either with or without product_rules set up */
public function getProductRuleGroupDisplay($product_rule_group_id, $product_rule_group_quantity = 1, $product_rules = null)
{
Context::getContext()->smarty->assign('product_rule_group_id', $product_rule_group_id);
Context::getContext()->smarty->assign('product_rule_group_quantity', $product_rule_group_quantity);
Context::getContext()->smarty->assign('product_rules', $product_rules);
return $this->createTemplate('product_rule_group.tpl')->fetch();
}
public function getProductRuleDisplay($product_rule_group_id, $product_rule_id, $product_rule_type, $selected = [])
{
Context::getContext()->smarty->assign(
[
'product_rule_group_id' => (int) $product_rule_group_id,
'product_rule_id' => (int) $product_rule_id,
'product_rule_type' => $product_rule_type,
]
);
switch ($product_rule_type) {
case 'attributes':
$attributes = ['selected' => [], 'unselected' => []];
$results = Db::getInstance()->executeS('
SELECT CONCAT(agl.name, " - ", al.name) as name, a.id_attribute as id
FROM ' . _DB_PREFIX_ . 'attribute_group_lang agl
LEFT JOIN ' . _DB_PREFIX_ . 'attribute a ON a.id_attribute_group = agl.id_attribute_group
LEFT JOIN ' . _DB_PREFIX_ . 'attribute_lang al ON (a.id_attribute = al.id_attribute AND al.id_lang = ' . (int) Context::getContext()->language->id . ')
WHERE agl.id_lang = ' . (int) Context::getContext()->language->id . '
ORDER BY agl.name, al.name');
foreach ($results as $row) {
$attributes[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row;
}
Context::getContext()->smarty->assign('product_rule_itemlist', $attributes);
$choose_content = $this->createTemplate('controllers/cart_rules/product_rule_itemlist.tpl')->fetch();
Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content);
break;
case 'products':
$products = ['selected' => [], 'unselected' => []];
$results = Db::getInstance()->executeS('
SELECT DISTINCT name, p.id_product as id
FROM ' . _DB_PREFIX_ . 'product p
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl
ON (p.`id_product` = pl.`id_product`
AND pl.`id_lang` = ' . (int) Context::getContext()->language->id . Shop::addSqlRestrictionOnLang('pl') . ')
' . Shop::addSqlAssociation('product', 'p') . '
WHERE id_lang = ' . (int) Context::getContext()->language->id . '
ORDER BY name');
foreach ($results as $row) {
$products[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row;
}
Context::getContext()->smarty->assign('product_rule_itemlist', $products);
$choose_content = $this->createTemplate('product_rule_itemlist.tpl')->fetch();
Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content);
break;
case 'manufacturers':
$products = ['selected' => [], 'unselected' => []];
$results = Db::getInstance()->executeS('
SELECT name, id_manufacturer as id
FROM ' . _DB_PREFIX_ . 'manufacturer
ORDER BY name');
foreach ($results as $row) {
$products[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row;
}
Context::getContext()->smarty->assign('product_rule_itemlist', $products);
$choose_content = $this->createTemplate('product_rule_itemlist.tpl')->fetch();
Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content);
break;
case 'suppliers':
$products = ['selected' => [], 'unselected' => []];
$results = Db::getInstance()->executeS('
SELECT name, id_supplier as id
FROM ' . _DB_PREFIX_ . 'supplier
ORDER BY name');
foreach ($results as $row) {
$products[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row;
}
Context::getContext()->smarty->assign('product_rule_itemlist', $products);
$choose_content = $this->createTemplate('product_rule_itemlist.tpl')->fetch();
Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content);
break;
case 'categories':
$categories = ['selected' => [], 'unselected' => []];
$categoryTree = Category::getNestedCategories(Category::getRootCategory()->id, (int) Context::getContext()->language->id, false);
$flatCategories = $this->populateCategories([], $categoryTree);
foreach ($flatCategories as $row) {
$categories[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row;
}
Context::getContext()->smarty->assign('product_rule_itemlist', $categories);
$choose_content = $this->createTemplate('product_rule_itemlist.tpl')->fetch();
Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content);
break;
default:
Context::getContext()->smarty->assign('product_rule_itemlist', ['selected' => [], 'unselected' => []]);
Context::getContext()->smarty->assign('product_rule_choose_content', '');
}
return $this->createTemplate('product_rule.tpl')->fetch();
}
public function populateCategories(array $flatCategories, array $currentCategoryTree, string $currentPath = ''): array
{
if (!$currentCategoryTree) {
return $flatCategories;
}
$separator = ' > ';
foreach ($currentCategoryTree as $categoryArray) {
$fullName = ($currentPath ? $currentPath . $separator : '') . $categoryArray['name'];
$flatCategories[] = ['id' => $categoryArray['id_category'], 'name' => $fullName];
// recursive call for childrens
if (!empty($categoryArray['children'])) {
$flatCategories = $this->populateCategories($flatCategories, $categoryArray['children'], $fullName);
}
}
return $flatCategories;
}
public function ajaxProcess()
{
if (Tools::isSubmit('newProductRule')) {
die($this->getProductRuleDisplay(Tools::getValue('product_rule_group_id'), Tools::getValue('product_rule_id'), Tools::getValue('product_rule_type')));
}
if (Tools::isSubmit('newProductRuleGroup') && $product_rule_group_id = Tools::getValue('product_rule_group_id')) {
die($this->getProductRuleGroupDisplay($product_rule_group_id, Tools::getValue('product_rule_group_' . $product_rule_group_id . '_quantity', 1)));
}
if (Tools::isSubmit('customerFilter')) {
$display_shop_name = Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP;
$search_query = trim(Tools::getValue('q'));
$customers = Db::getInstance()->executeS('
SELECT c.`id_customer`, c.`email`, CONCAT(c.`firstname`, \' \', c.`lastname`) as cname' .
($display_shop_name ? ', s.name as shop_name' : '') . '
FROM `' . _DB_PREFIX_ . 'customer` c ' .
($display_shop_name ? ' INNER JOIN `' . _DB_PREFIX_ . 'shop` s ON s.id_shop = c.id_shop' : '') . '
WHERE c.`deleted` = 0 AND c.is_guest = 0 AND c.active = 1
AND (
c.`id_customer` = ' . (int) $search_query . '
OR c.`email` LIKE "%' . pSQL($search_query) . '%"
OR c.`firstname` LIKE "%' . pSQL($search_query) . '%"
OR c.`lastname` LIKE "%' . pSQL($search_query) . '%"
)
' . Shop::addSqlRestriction(Shop::SHARE_CUSTOMER, 'c') . '
ORDER BY c.`firstname`, c.`lastname`
LIMIT 50');
die(json_encode($customers));
}
// Both product filter (free product and product discount) search for products
if (Tools::isSubmit('giftProductFilter') || Tools::isSubmit('reductionProductFilter')) {
$products = Product::searchByName(Context::getContext()->language->id, trim(Tools::getValue('q')));
die(json_encode($products));
}
}
protected function searchProducts(string $searchString)
{
if ($products = Product::searchByName((int) $this->context->language->id, $searchString)) {
foreach ($products as &$product) {
$combinations = [];
$productObj = new Product((int) $product['id_product'], false, (int) $this->context->language->id);
$attributes = $productObj->getAttributesGroups((int) $this->context->language->id);
$product['formatted_price'] = $product['price_tax_incl']
? $this->context->getCurrentLocale()->formatPrice(Tools::convertPrice($product['price_tax_incl'], $this->context->currency), $this->context->currency->iso_code)
: '';
foreach ($attributes as $attribute) {
if (!isset($combinations[$attribute['id_product_attribute']]['attributes'])) {
$combinations[$attribute['id_product_attribute']]['attributes'] = '';
}
$combinations[$attribute['id_product_attribute']]['attributes'] .= $attribute['attribute_name'] . ' - ';
$combinations[$attribute['id_product_attribute']]['id_product_attribute'] = $attribute['id_product_attribute'];
$combinations[$attribute['id_product_attribute']]['default_on'] = $attribute['default_on'];
$price_tax_incl = Product::getPriceStatic((int) $product['id_product'], true, $attribute['id_product_attribute']);
$combinations[$attribute['id_product_attribute']]['formatted_price'] = $price_tax_incl
? $this->context->getCurrentLocale()->formatPrice(Tools::convertPrice($price_tax_incl, $this->context->currency), $this->context->currency->iso_code)
: '';
}
foreach ($combinations as &$combination) {
$combination['attributes'] = rtrim($combination['attributes'], ' - ');
}
$product['combinations'] = $combinations;
}
return [
'products' => $products,
'found' => true,
];
} else {
return ['found' => false, 'notfound' => $this->trans('No product has been found.', [], 'Admin.Catalog.Notification')];
}
}
public function ajaxProcessSearchProducts()
{
$array = $this->searchProducts(Tools::getValue('product_search'));
$this->content = trim(json_encode($array));
}
public function renderForm()
{
$limit = 40;
$this->toolbar_btn['save'] = [
'href' => '#',
'desc' => $this->trans('Save', [], 'Admin.Actions'),
];
$this->toolbar_btn['save-and-stay'] = [
'href' => '#',
'desc' => $this->trans('Save and stay', [], 'Admin.Actions'),
];
/** @var CartRule $current_object */
$current_object = $this->loadObject(true);
// All the filter are prefilled with the correct information
$customer_filter = '';
if (Validate::isUnsignedId($current_object->id_customer)
&& Validate::isLoadedObject($customer = new Customer($current_object->id_customer))
) {
$customer_filter = $customer->firstname . ' ' . $customer->lastname . ' (' . $customer->email . ')';
}
$gift_product_filter = '';
if (Validate::isUnsignedId($current_object->gift_product)
&& Validate::isLoadedObject($product = new Product($current_object->gift_product, false, $this->context->language->id))
) {
$gift_product_filter = (!empty($product->reference) ? $product->reference : $product->name);
}
$reduction_product_filter = '';
if (Validate::isUnsignedId($current_object->reduction_product)
&& Validate::isLoadedObject($product = new Product($current_object->reduction_product, false, $this->context->language->id))
) {
$reduction_product_filter = (!empty($product->reference) ? $product->reference : $product->name);
}
$product_rule_groups = $this->getProductRuleGroupsDisplay($current_object);
$attribute_groups = AttributeGroup::getAttributesGroups($this->context->language->id);
$currencies = Currency::getCurrencies(false, true, true);
$languages = Language::getLanguages();
$countries = $current_object->getAssociatedRestrictions('country', true, true);
$groups = $current_object->getAssociatedRestrictions('group', false, true);
$shops = $current_object->getAssociatedRestrictions('shop', false, false);
$cart_rules = $current_object->getAssociatedRestrictions('cart_rule', false, true, 0, $limit);
$carriers = $current_object->getAssociatedRestrictions('carrier', true, true);
foreach ($carriers as &$carriers2) {
$prev_id_carrier = 0;
foreach ($carriers2 as $key => &$carrier) {
if ($prev_id_carrier == $carrier['id_carrier']) {
unset($carriers2[$key]);
continue;
}
foreach ($carrier as $field => &$value) {
if ($field == 'name') {
if ($value == '0') {
$value = $carrier['id_carrier'] . ' - ' . Configuration::get('PS_SHOP_NAME');
} else {
$value = $carrier['id_carrier'] . ' - ' . $carrier['name'];
if (!empty($carrier['delay'])) {
$value .= ' (' . $carrier['delay'] . ')';
}
}
}
}
$prev_id_carrier = $carrier['id_carrier'];
}
}
$gift_product_select = '';
$gift_product_attribute_select = '';
if ((int) $current_object->gift_product) {
$search_products = $this->searchProducts($gift_product_filter);
if (isset($search_products['products']) && is_array($search_products['products'])) {
foreach ($search_products['products'] as $product) {
$gift_product_select .= '
<option value="' . $product['id_product'] . '" ' . ($product['id_product'] == $current_object->gift_product ? 'selected="selected"' : '') . '>
' . $product['name'] . (count($product['combinations']) == 0 ? ' - ' . $product['formatted_price'] : '') . '
</option>';
if (count($product['combinations'])) {
$gift_product_attribute_select .= '<select class="control-form id_product_attribute" id="ipa_' . $product['id_product'] . '" name="ipa_' . $product['id_product'] . '">';
foreach ($product['combinations'] as $combination) {
$gift_product_attribute_select .= '
<option ' . ($combination['id_product_attribute'] == $current_object->gift_product_attribute ? 'selected="selected"' : '') . ' value="' . $combination['id_product_attribute'] . '">
' . $combination['attributes'] . ' - ' . $combination['formatted_price'] . '
</option>';
}
$gift_product_attribute_select .= '</select>';
}
}
}
}
$product = new Product($current_object->gift_product);
$this->context->smarty->assign(
[
'show_toolbar' => true,
'toolbar_btn' => $this->toolbar_btn,
'toolbar_scroll' => $this->toolbar_scroll,
'title' => $this->trans('Cart Rules', [], 'Admin.Catalog.Feature'),
'defaultDateFrom' => date('Y-m-d H:00:00'),
'defaultDateTo' => date('Y-m-d H:00:00', strtotime('+1 month')),
'customerFilter' => $customer_filter,
'giftProductFilter' => $gift_product_filter,
'gift_product_select' => $gift_product_select,
'gift_product_attribute_select' => $gift_product_attribute_select,
'reductionProductFilter' => $reduction_product_filter,
'defaultCurrency' => Currency::getDefaultCurrencyId(),
'id_lang_default' => Configuration::get('PS_LANG_DEFAULT'),
'languages' => $languages,
'currencies' => $currencies,
'countries' => $countries,
'carriers' => $carriers,
'groups' => $groups,
'shops' => $shops,
'all_shops' => Shop::getCompleteListOfShopsID(),
'cart_rules' => $cart_rules,
'product_rule_groups' => $product_rule_groups,
'product_rule_groups_counter' => count($product_rule_groups),
'attribute_groups' => $attribute_groups,
'currentIndex' => self::$currentIndex,
'currentToken' => $this->token,
'currentObject' => $current_object,
'currentTab' => $this,
'hasAttribute' => $product->hasAttributes(),
]
);
Media::addJsDef(['baseHref' => $this->context->link->getAdminLink('AdminCartRules') . '&ajaxMode=1&ajax=1&id_cart_rule=' .
(int) Tools::getValue('id_cart_rule') . '&action=loadCartRules&limit=' . (int) $limit . '&count=0', ]);
$this->content .= $this->createTemplate('form.tpl')->fetch();
$this->addJqueryUI('ui.datepicker');
$this->addJqueryPlugin(['jscroll', 'typewatch']);
return parent::renderForm();
}
public function displayAjaxSearchCartRuleVouchers()
{
$found = false;
if ($vouchers = CartRule::getCartsRuleByCode(Tools::getValue('q'), (int) $this->context->language->id, true)) {
$found = true;
}
echo json_encode(['found' => $found, 'vouchers' => $vouchers]);
}
/**
* For the listing, Override the method displayDeleteLink for the HelperList
* That allows to have links with all characters (like < & >)
*
* @param string $token
* @param string $id
* @param string|null $name
*
* @return string
*/
public function displayDeleteLink(string $token, string $id, ?string $name = null): string
{
if (!$this->access('delete')) {
return '';
}
$tpl = $this->createTemplate('helpers/list/list_action_delete.tpl');
if (!array_key_exists('Delete', self::$cache_lang)) {
self::$cache_lang['Delete'] = $this->trans('Delete', [], 'Admin.Actions');
}
if (!array_key_exists('DeleteItem', self::$cache_lang)) {
self::$cache_lang['DeleteItem'] = $this->trans('Delete selected item?', [], 'Admin.Notifications.Info');
}
if (!array_key_exists('Name', self::$cache_lang)) {
self::$cache_lang['Name'] = $this->trans('Name:', [], 'Admin.Shipping.Feature');
}
if (null !== $name) {
// \n\n is not between double quotes because in js/jquery/plugins/alerts/jquery.alerts.js, \n is replaced by <br>.
$name = '\n\n' . self::$cache_lang['Name'] . ' ' . $name;
}
$data = [
$this->identifier => $id,
'href' => $this->context->link->getAdminLink(
'AdminCartRules',
true, [],
[
'id_cart_rule' => (int) $id,
'deletecart_rule' => 1,
]
),
'action' => self::$cache_lang['Delete'],
];
if ($this->specificConfirmDelete !== false) {
$data['confirm'] = null !== $this->specificConfirmDelete
? '\r' . $this->specificConfirmDelete
: Tools::htmlentitiesDecodeUTF8(self::$cache_lang['DeleteItem'] . $name);
}
$tpl->assign(array_merge($this->tpl_delete_link_vars, $data));
return $tpl->fetch();
}
}

View File

@@ -0,0 +1,516 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Country $object
*/
class AdminCountriesControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'country';
$this->className = 'Country';
$this->lang = true;
$this->deleted = false;
$this->_defaultOrderBy = 'name';
$this->_defaultOrderWay = 'ASC';
$this->explicitSelect = true;
$this->addRowAction('edit');
parent::__construct();
$this->bulk_actions = [
'delete' => ['text' => $this->trans('Delete selected', [], 'Admin.Actions'), 'confirm' => $this->trans('Delete selected items?', [], 'Admin.Actions')],
'AffectZone' => ['text' => $this->trans('Assign to a new zone', [], 'Admin.International.Feature')],
];
$this->fieldImageSettings = [
'name' => 'logo',
'dir' => 'st',
];
$this->fields_options = [
'general' => [
'title' => $this->trans('Country options', [], 'Admin.International.Feature'),
'fields' => [
'PS_RESTRICT_DELIVERED_COUNTRIES' => [
'title' => $this->trans('Restrict country selections in front office to those covered by active carriers', [], 'Admin.International.Help'),
'cast' => 'intval',
'type' => 'bool',
'default' => '0',
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
];
$zones_array = [];
foreach (Zone::getZones() as $zone) {
$zones_array[$zone['id_zone']] = $zone['name'];
}
$this->fields_list = [
'id_country' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Country', [], 'Admin.Global'),
'filter_key' => 'b!name',
],
'iso_code' => [
'title' => $this->trans('ISO code', [], 'Admin.International.Feature'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'call_prefix' => [
'title' => $this->trans('Call prefix', [], 'Admin.International.Feature'),
'align' => 'center',
'callback' => 'displayCallPrefix',
'class' => 'fixed-width-sm',
],
'zone' => [
'title' => $this->trans('Zone', [], 'Admin.Global'),
'type' => 'select',
'list' => $zones_array,
'filter_key' => 'z!id_zone',
'filter_type' => 'int',
'order_key' => 'z!name',
],
'active' => [
'title' => $this->trans('Enabled', [], 'Admin.Global'),
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'orderby' => false,
'filter_key' => 'a!active',
'class' => 'fixed-width-sm',
],
];
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_country'] = [
'href' => $this->context->link->getAdminLink('AdminCountries', true, [], ['addcountry' => 1]),
'desc' => $this->trans('Add new country', [], 'Admin.International.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
/**
* AdminController::setMedia() override.
*
* @see AdminController::setMedia()
*/
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryPlugin('fieldselection');
}
public function renderList()
{
$this->_select = 'z.`name` AS zone';
$this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'zone` z ON (z.`id_zone` = a.`id_zone`)';
$this->_use_found_rows = false;
$this->tpl_list_vars['zones'] = Zone::getZones();
$this->tpl_list_vars['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
$this->tpl_list_vars['POST'] = $_POST;
return parent::renderList();
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderForm()
{
if (!($obj = $this->loadObject(true))) {
return;
}
$address_layout = AddressFormat::getAddressCountryFormat($obj->id);
if ($value = Tools::getValue('address_layout')) {
$address_layout = $value;
}
$default_layout = '';
// TODO: Use format from XML
$default_layout_tab = [
['firstname', 'lastname'],
['company'],
['vat_number'],
['address1'],
['address2'],
['postcode', 'city'],
['Country:name'],
['phone'],
];
foreach ($default_layout_tab as $line) {
$default_layout .= implode(' ', $line) . AddressFormat::FORMAT_NEW_LINE;
}
$this->fields_form = [
'legend' => [
'title' => $this->trans('Countries', [], 'Admin.International.Feature'),
'icon' => 'icon-globe',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Country', [], 'Admin.Global'),
'name' => 'name',
'lang' => true,
'required' => true,
'hint' => $this->trans('Country name', [], 'Admin.International.Feature') . ' - ' . $this->trans('Invalid characters:', [], 'Admin.Global') . ' &lt;&gt;{} ',
],
[
'type' => 'text',
'label' => $this->trans('ISO code', [], 'Admin.International.Feature'),
'name' => 'iso_code',
'maxlength' => 3,
'class' => 'uppercase',
'required' => true,
'hint' => $this->trans('Two -- or three -- letter ISO code (e.g. "us" for United States).', [], 'Admin.International.Help'),
/* @TODO - add two lines for the hint? */
/*'desc' => $this->trans('Two -- or three -- letter ISO code (e.g. U.S. for United States)', [], 'Admin.International.Help').'.
<a href="http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm" target="_blank">'.
$this->trans('Official list here', [], 'Admin.International.Feature').'
</a>.'*/
],
[
'type' => 'text',
'label' => $this->trans('Call prefix', [], 'Admin.International.Feature'),
'name' => 'call_prefix',
'maxlength' => 3,
'class' => 'uppercase',
'hint' => $this->trans('International call prefix, (e.g. 1 for United States).', [], 'Admin.International.Help'),
],
[
'type' => 'select',
'label' => $this->trans('Default currency', [], 'Admin.International.Feature'),
'name' => 'id_currency',
'options' => [
'query' => Currency::getCurrencies(false, true, true),
'id' => 'id_currency',
'name' => 'name',
'default' => [
'label' => $this->trans('Default store currency', [], 'Admin.International.Feature'),
'value' => 0,
],
],
],
[
'type' => 'select',
'label' => $this->trans('Zone', [], 'Admin.Global'),
'name' => 'id_zone',
'options' => [
'query' => Zone::getZones(),
'id' => 'id_zone',
'name' => 'name',
],
'hint' => $this->trans('Geographical region.', [], 'Admin.International.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Does it need a ZIP/Postal code?', [], 'Admin.International.Feature'),
'name' => 'need_zip_code',
'required' => false,
'is_bool' => true,
'values' => [
[
'id' => 'need_zip_code_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'need_zip_code_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
[
'type' => 'text',
'label' => $this->trans('ZIP/Postal code format', [], 'Admin.International.Feature'),
'name' => 'zip_code_format',
'desc' => $this->trans('Indicate the format of the postal code: use L for a letter, N for a number, and C for the country\'s ISO 3166-1 alpha-2 code. For example, NNNNN for the United States, France, Poland and many other; LNNNNLLL for Argentina, etc. If you do not want PrestaShop to verify the postal code for this country, leave it blank.', [], 'Admin.International.Help'),
],
[
'type' => 'address_layout',
'label' => $this->trans('Address format', [], 'Admin.International.Feature'),
'name' => 'address_layout',
'address_layout' => $address_layout,
'encoding_address_layout' => urlencode($address_layout),
'encoding_default_layout' => urlencode($default_layout),
'display_valid_fields' => $this->displayValidFields(),
],
[
'type' => 'switch',
'label' => $this->trans('Active', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Display this country to your customers (the selected country will always be displayed in the Back Office).', [], 'Admin.International.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Contains states', [], 'Admin.International.Feature'),
'name' => 'contains_states',
'required' => false,
'values' => [
[
'id' => 'contains_states_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'contains_states_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
[
'type' => 'switch',
'label' => $this->trans('Do you need a tax identification number?', [], 'Admin.International.Feature'),
'name' => 'need_identification_number',
'required' => false,
'values' => [
[
'id' => 'need_identification_number_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'need_identification_number_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
[
'type' => 'switch',
'label' => $this->trans('Display tax label (e.g. "Tax incl.")', [], 'Admin.International.Feature'),
'name' => 'display_tax_label',
'required' => false,
'values' => [
[
'id' => 'display_tax_label_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'display_tax_label_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
],
];
if (Shop::isFeatureActive()) {
$this->fields_form['input'][] = [
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
];
}
$this->fields_form['submit'] = [
'title' => $this->trans('Save', [], 'Admin.Actions'),
];
return parent::renderForm();
}
public function processUpdate()
{
/** @var Country $country */
$country = $this->loadObject();
if (Validate::isLoadedObject($country) && Tools::getValue('id_zone')) {
$old_id_zone = $country->id_zone;
$results = Db::getInstance()->executeS('SELECT `id_state` FROM `' . _DB_PREFIX_ . 'state` WHERE `id_country` = ' . (int) $country->id . ' AND `id_zone` = ' . (int) $old_id_zone);
if ($results && count($results)) {
$ids = [];
foreach ($results as $res) {
$ids[] = (int) $res['id_state'];
}
if (count($ids)) {
$res = Db::getInstance()->execute(
'UPDATE `' . _DB_PREFIX_ . 'state`
SET `id_zone` = ' . (int) Tools::getValue('id_zone') . '
WHERE `id_state` IN (' . implode(',', $ids) . ')'
);
}
}
}
return parent::processUpdate();
}
public function postProcess()
{
if (!Tools::getValue('id_' . $this->table)) {
if (Validate::isLanguageIsoCode(Tools::getValue('iso_code')) && (int) Country::getByIso(Tools::getValue('iso_code'))) {
$this->errors[] = $this->trans('This ISO code already exists.You cannot create two countries with the same ISO code.', [], 'Admin.International.Notification');
}
} elseif (Validate::isLanguageIsoCode(Tools::getValue('iso_code'))) {
$id_country = (int) Country::getByIso(Tools::getValue('iso_code'));
if ($id_country != 0 && $id_country != Tools::getValue('id_' . $this->table)) {
$this->errors[] = $this->trans('This ISO code already exists.You cannot create two countries with the same ISO code.', [], 'Admin.International.Notification');
}
}
return parent::postProcess();
}
public function processSave()
{
if (!$this->id_object) {
$tmp_addr_format = new AddressFormat();
} else {
$tmp_addr_format = new AddressFormat($this->id_object);
}
$tmp_addr_format->format = Tools::getValue('address_layout');
if (!$tmp_addr_format->checkFormatFields()) {
$error_list = $tmp_addr_format->getErrorList();
foreach ($error_list as $error) {
$this->errors[] = $error;
}
}
if (strlen($tmp_addr_format->format) <= 0) {
$this->errors[] = $this->trans('Address format invalid', [], 'Admin.Notifications.Error');
}
$country = parent::processSave();
if (!count($this->errors)) {
if (null === $tmp_addr_format->id_country) {
$tmp_addr_format->id_country = $country->id;
}
if (!$tmp_addr_format->save()) {
$this->errors[] = $this->trans('Invalid address layout %s', [Db::getInstance()->getMsgError()], 'Admin.International.Notification');
}
}
return $country;
}
/**
* @return bool|ObjectModel|null
*
* @throws PrestaShopException
*/
public function processStatus()
{
parent::processStatus();
$object = $this->loadObject();
/** @var Country $object */
if (Validate::isLoadedObject($object) && $object->active == 1) {
return Country::addModuleRestrictions([], [['id_country' => $object->id]], []);
}
return false;
}
/**
* Allow the assignation of zone only if the form is displayed.
*
* @return bool|void
*/
protected function processBulkAffectZone()
{
$zone_to_affect = Tools::getValue('zone_to_affect');
if ($zone_to_affect && $zone_to_affect !== 0) {
parent::processBulkAffectZone();
}
if (Tools::getIsset('submitBulkAffectZonecountry')) {
$this->tpl_list_vars['assign_zone'] = true;
}
}
protected function displayValidFields()
{
/* The following translations are needed later - don't remove the comments!
$this->trans('Customer', [], 'Admin.Global');
$this->trans('Country', [], 'Admin.Global');
$this->trans('State', [], 'Admin.Global');
$this->trans('Address', [], 'Admin.Global');
*/
$html_tabnav = '<ul class="nav nav-tabs" id="custom-address-fields">';
$html_tabcontent = '<div class="tab-content" >';
$object_list = AddressFormat::getLiableClass('Address');
$object_list['Address'] = null;
// Get the available properties for each class
$i = 0;
$class_tab_active = 'active';
foreach ($object_list as $class_name => &$object) {
if ($i != 0) {
$class_tab_active = '';
}
$fields = [];
$html_tabnav .= '<li' . ($class_tab_active ? ' class="' . $class_tab_active . '"' : '') . '>
<a href="#availableListFieldsFor_' . $class_name . '"><i class="icon-caret-down"></i>&nbsp;' . Context::getContext()->getTranslator()->trans($class_name, [], 'AdminCountries') . '</a></li>';
foreach (AddressFormat::getValidateFields($class_name) as $name) {
$fields[] = '<a href="javascript:void(0);" class="addPattern btn btn-default btn-xs" id="' . ($class_name == 'Address' ? $name : $class_name . ':' . $name) . '">
<i class="icon-plus-sign"></i>&nbsp;' . ObjectModel::displayFieldName($name, $class_name) . '</a>';
}
$html_tabcontent .= '
<div class="tab-pane availableFieldsList panel ' . $class_tab_active . '" id="availableListFieldsFor_' . $class_name . '">
' . implode(' ', $fields) . '</div>';
unset($object);
++$i;
}
$html_tabnav .= '</ul>';
$html_tabcontent .= '</div>';
return $html_tabnav . $html_tabcontent;
}
public static function displayCallPrefix($prefix)
{
return (int) $prefix ? '+' . $prefix : '-';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,435 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AdminDashboardControllerCore extends AdminController
{
private const DASHBOARD_ALLOWED_HOOKS = ['dashboardData', 'dashboardZoneOne', 'dashboardZoneTwo', 'displayDashboardToolbarIcons', 'displayDashboardToolbarTopMenu', 'displayDashboardTop'];
public function __construct()
{
$this->bootstrap = true;
$this->display = 'view';
parent::__construct();
if (Tools::isSubmit('profitability_conf') || Tools::isSubmit('submitOptionsconfiguration')) {
$this->fields_options = $this->getOptionFields();
}
}
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryUI('ui.datepicker');
$this->addJS([
_PS_JS_DIR_ . 'vendor/d3.v3.min.js',
__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/vendor/nv.d3.min.js',
_PS_JS_DIR_ . '/admin/dashboard.js',
]);
$this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/vendor/nv.d3.css');
}
public function initPageHeaderToolbar()
{
$this->page_header_toolbar_title = $this->trans('Dashboard', [], 'Admin.Dashboard.Feature');
$this->page_header_toolbar_btn['switch_demo'] = [
'desc' => $this->trans('Demo mode', [], 'Admin.Dashboard.Feature'),
'icon' => 'process-icon-toggle-' . (Configuration::get('PS_DASHBOARD_SIMULATION') ? 'on' : 'off'),
'help' => $this->trans('This mode displays sample data so you can try your dashboard without real numbers.', [], 'Admin.Dashboard.Help'),
];
parent::initPageHeaderToolbar();
// Remove the last element on this controller to match the title with the rule of the others
array_pop($this->meta_title);
}
protected function getOptionFields()
{
$currency = Currency::getDefaultCurrency();
$carriers = Carrier::getCarriers((int) $this->context->language->id, true, false, false, null, Carrier::ALL_CARRIERS);
$modules = Module::getModulesOnDisk(true);
$forms = [
'payment' => ['title' => $this->trans('Average bank fees per payment method', [], 'Admin.Dashboard.Feature'), 'id' => 'payment'],
'carriers' => ['title' => $this->trans('Average shipping fees per shipping method', [], 'Admin.Dashboard.Feature'), 'id' => 'carriers'],
'other' => ['title' => $this->trans('Other settings', [], 'Admin.Dashboard.Feature'), 'id' => 'other'],
];
foreach ($forms as &$form) {
$form['icon'] = 'tab-preferences';
$form['fields'] = [];
$form['submit'] = ['title' => $this->trans('Save', [], 'Admin.Actions')];
}
foreach ($modules as $module) {
if (isset($module->tab) && $module->tab == 'payments_gateways' && $module->id) {
$moduleClass = Module::getInstanceByName($module->name);
if (!$moduleClass->isEnabledForShopContext()) {
continue;
}
$forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_FIXED'] = [
'title' => $module->displayName,
'desc' => $this->trans(
'Choose a fixed fee for each order placed in %currency% with %module%.',
[
'%currency' => $currency->iso_code,
'%module%' => $module->displayName,
],
'Admin.Dashboard.Help'
),
'validation' => 'isPrice',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => $currency->iso_code,
];
$forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_VAR'] = [
'title' => $module->displayName,
'desc' => $this->trans(
'Choose a variable fee for each order placed in %currency% with %module%. It will be applied on the total paid with taxes.',
[
'%currency' => $currency->iso_code,
'%module%' => $module->displayName,
],
'Admin.Dashboard.Help'
),
'validation' => 'isPercentage',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => '%',
];
if (Currency::isMultiCurrencyActivated()) {
$forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_FIXED_FOREIGN'] = [
'title' => $module->displayName,
'desc' => $this->trans(
'Choose a fixed fee for each order placed with a foreign currency with %module%.',
[
'%module%' => $module->displayName,
],
'Admin.Dashboard.Help'
),
'validation' => 'isPrice',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => $currency->iso_code,
];
$forms['payment']['fields']['CONF_' . strtoupper($module->name) . '_VAR_FOREIGN'] = [
'title' => $module->displayName,
'desc' => $this->trans(
'Choose a variable fee for each order placed with a foreign currency with %module%. It will be applied on the total paid with taxes.',
['%module%' => $module->displayName],
'Admin.Dashboard.Help'
),
'validation' => 'isPercentage',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => '%',
];
}
}
}
foreach ($carriers as $carrier) {
$forms['carriers']['fields']['CONF_' . strtoupper($carrier['id_reference']) . '_SHIP'] = [
'title' => $carrier['name'],
'desc' => $this->trans(
'For the carrier named %s, indicate the domestic delivery costs in percentage of the price charged to customers.',
[
'%s' => $carrier['name'],
],
'Admin.Dashboard.Help'
),
'validation' => 'isPercentage',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => '%',
];
$forms['carriers']['fields']['CONF_' . strtoupper($carrier['id_reference']) . '_SHIP_OVERSEAS'] = [
'title' => $carrier['name'],
'desc' => $this->trans(
'For the carrier named %s, indicate the overseas delivery costs in percentage of the price charged to customers.',
[
'%s' => $carrier['name'],
],
'Admin.Dashboard.Help'
),
'validation' => 'isPercentage',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => '%',
];
}
$forms['carriers']['description'] = $this->trans('Method: Indicate the percentage of your carrier margin. For example, if you charge $10 of shipping fees to your customer for each shipment, but you really pay $4 to this carrier, then you should indicate "40" in the percentage field.', [], 'Admin.Dashboard.Help');
$forms['other']['fields']['CONF_AVERAGE_PRODUCT_MARGIN'] = [
'title' => $this->trans('Average gross margin percentage', [], 'Admin.Dashboard.Feature'),
'desc' => $this->trans('You should calculate this percentage as follows: ((total sales revenue) - (cost of goods sold)) / (total sales revenue) * 100. This value is only used to calculate the Dashboard approximate gross margin, if you do not specify the wholesale price for each product.', [], 'Admin.Dashboard.Help'),
'validation' => 'isPercentage',
'cast' => 'intval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => '%',
];
$forms['other']['fields']['CONF_ORDER_FIXED'] = [
'title' => $this->trans('Other fees per order', [], 'Admin.Dashboard.Feature'),
'desc' => $this->trans('You should calculate this value by making the sum of all of your additional costs per order.', [], 'Admin.Dashboard.Help'),
'validation' => 'isPrice',
'cast' => 'floatval',
'type' => 'text',
'defaultValue' => '0',
'suffix' => $currency->iso_code,
];
Media::addJsDef([
'dashboard_ajax_url' => $this->context->link->getAdminLink('AdminDashboard'),
'read_more' => '',
]);
return $forms;
}
public function renderView()
{
if (Tools::isSubmit('profitability_conf')) {
return parent::renderOptions();
}
$testStatsDateUpdate = $this->context->cookie->__get('stats_date_update');
if (!empty($testStatsDateUpdate) && $this->context->cookie->__get('stats_date_update') < strtotime(date('Y-m-d'))) {
switch ($this->context->employee->preselect_date_range) {
case 'day':
$date_from = date('Y-m-d');
$date_to = date('Y-m-d');
break;
case 'prev-day':
$date_from = date('Y-m-d', strtotime('-1 day'));
$date_to = date('Y-m-d', strtotime('-1 day'));
break;
case 'month':
default:
$date_from = date('Y-m-01');
$date_to = date('Y-m-d');
break;
case 'prev-month':
$date_from = date('Y-m-01', strtotime('-1 month'));
$date_to = date('Y-m-t', strtotime('-1 month'));
break;
case 'year':
$date_from = date('Y-01-01');
$date_to = date('Y-m-d');
break;
case 'prev-year':
$date_from = date('Y-m-01', strtotime('-1 year'));
$date_to = date('Y-12-t', strtotime('-1 year'));
break;
}
$this->context->employee->stats_date_from = $date_from;
$this->context->employee->stats_date_to = $date_to;
$this->context->employee->update();
$this->context->cookie->__set('stats_date_update', strtotime(date('Y-m-d')));
$this->context->cookie->write();
}
$calendar_helper = new HelperCalendar();
$calendar_helper->setDateFrom(Tools::getValue('date_from', $this->context->employee->stats_date_from));
$calendar_helper->setDateTo(Tools::getValue('date_to', $this->context->employee->stats_date_to));
$stats_compare_from = $this->context->employee->stats_compare_from;
$stats_compare_to = $this->context->employee->stats_compare_to;
if (null === $stats_compare_from || $stats_compare_from == '0000-00-00') {
$stats_compare_from = null;
}
if (null === $stats_compare_to || $stats_compare_to == '0000-00-00') {
$stats_compare_to = null;
}
$calendar_helper->setCompareDateFrom($stats_compare_from);
$calendar_helper->setCompareDateTo($stats_compare_to);
$calendar_helper->setCompareOption(Tools::getValue('compare_date_option', $this->context->employee->stats_compare_option));
$params = [
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to,
];
$this->tpl_view_vars = [
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to,
'hookDashboardZoneOne' => Hook::exec('dashboardZoneOne', $params),
'hookDashboardZoneTwo' => Hook::exec('dashboardZoneTwo', $params),
'hookDashboardZoneThree' => Hook::exec('dashboardZoneThree', $params),
'action' => '#',
'warning' => $this->getWarningDomainName(),
'calendar' => $calendar_helper->generate(),
'PS_DASHBOARD_SIMULATION' => Configuration::get('PS_DASHBOARD_SIMULATION'),
'datepickerFrom' => Tools::getValue('datepickerFrom', $this->context->employee->stats_date_from),
'datepickerTo' => Tools::getValue('datepickerTo', $this->context->employee->stats_date_to),
'preselect_date_range' => Tools::getValue('preselectDateRange', $this->context->employee->preselect_date_range),
];
return parent::renderView();
}
public function postProcess()
{
if (Tools::isSubmit('submitDateRange')) {
if (!Validate::isDate(Tools::getValue('date_from'))
|| !Validate::isDate(Tools::getValue('date_to'))) {
$this->errors[] = $this->trans('The selected date range is not valid.', [], 'Admin.Notifications.Error');
}
if (Tools::getValue('datepicker_compare')) {
if (!Validate::isDate(Tools::getValue('compare_date_from'))
|| !Validate::isDate(Tools::getValue('compare_date_to'))) {
$this->errors[] = $this->trans('The selected date range is not valid.', [], 'Admin.Notifications.Error');
}
}
if (!count($this->errors)) {
$this->context->employee->stats_date_from = Tools::getValue('date_from');
$this->context->employee->stats_date_to = Tools::getValue('date_to');
$this->context->employee->preselect_date_range = Tools::getValue('preselectDateRange');
if (Tools::getValue('datepicker_compare')) {
$this->context->employee->stats_compare_from = Tools::getValue('compare_date_from');
$this->context->employee->stats_compare_to = Tools::getValue('compare_date_to');
$this->context->employee->stats_compare_option = Tools::getValue('compare_date_option');
} else {
$this->context->employee->stats_compare_from = null;
$this->context->employee->stats_compare_to = null;
$this->context->employee->stats_compare_option = HelperCalendar::DEFAULT_COMPARE_OPTION;
}
$this->context->employee->update();
}
}
parent::postProcess();
}
protected function getWarningDomainName()
{
$warning = false;
if (Shop::isFeatureActive()) {
return;
}
$shop = Context::getContext()->shop;
if ($_SERVER['HTTP_HOST'] != $shop->domain && $_SERVER['HTTP_HOST'] != $shop->domain_ssl && Tools::getValue('ajax') == false) {
$warning = $this->trans('You are currently connected under the following domain name:', [], 'Admin.Dashboard.Notification') . ' <span style="color: #CC0000;">' . $_SERVER['HTTP_HOST'] . '</span><br />';
if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')) {
$warning .= $this->trans(
'This is different from the shop domain name set in the Multistore settings: "%s".',
[
'%s' => $shop->domain,
],
'Admin.Dashboard.Notification'
) . $this->trans(
'If this is your main domain, please {link}change it now{/link}.',
[
'{link}' => '<a href="' . $this->context->link->getAdminLink('AdminShopUrl', true, [], ['id_shop_url' => (int) $shop->id, 'updateshop_url' => 1]) . '">',
'{/link}' => '</a>',
],
'Admin.Dashboard.Notification'
);
} else {
$warning .= $this->trans('This is different from the domain name set in the "SEO & URLs" tab.', [], 'Admin.Dashboard.Notification') . '
' . $this->trans(
'If this is your main domain, please {link}change it now{/link}.',
[
'{link}' => '<a href="' . $this->context->link->getAdminLink('AdminMeta') . '#meta_fieldset_shop_url">',
'{/link}' => '</a>',
],
'Admin.Dashboard.Notification'
);
}
}
return $warning;
}
public function ajaxProcessRefreshDashboard()
{
$id_module = null;
if ($module = Tools::getValue('module')) {
$module_obj = Module::getInstanceByName($module);
if (Validate::isLoadedObject($module_obj)) {
$id_module = $module_obj->id;
}
}
$params = [
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to,
'compare_from' => $this->context->employee->stats_compare_from,
'compare_to' => $this->context->employee->stats_compare_to,
'extra' => (int) Tools::getValue('extra'),
];
// Hook called only for the module concerned
// An array [module_name => module_output] will be returned
die(json_encode(Hook::exec('dashboardData', $params, $id_module, true)));
}
public function ajaxProcessSetSimulationMode()
{
Configuration::updateValue('PS_DASHBOARD_SIMULATION', (int) Tools::getValue('PS_DASHBOARD_SIMULATION'));
die('k' . Configuration::get('PS_DASHBOARD_SIMULATION') . 'k');
}
public function ajaxProcessSaveDashConfig()
{
$return = ['has_errors' => false, 'errors' => []];
$module = Tools::getValue('module');
$hook = Tools::getValue('hook');
$configs = Tools::getValue('configs');
if (!in_array(lcfirst(str_replace('hook', '', $hook)), self::DASHBOARD_ALLOWED_HOOKS)) {
$return['has_errors'] = true;
$return['errors'][] = 'This hook is not allowed here.';
die(json_encode($return));
}
$params = [
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to,
];
$module_obj = Module::getInstanceByName($module);
if (Validate::isModuleName($module) && $module_obj) {
$return['errors'] = $module_obj->validateDashConfig($configs);
if (count($return['errors'])) {
$return['has_errors'] = true;
} else {
$return['has_errors'] = $module_obj->saveDashConfig($configs);
}
}
if (method_exists($module_obj, $hook)) {
$return['widget_html'] = $module_obj->$hook($params);
}
die(json_encode($return));
}
}

View File

@@ -0,0 +1,705 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Group $object
*/
class AdminGroupsControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'group';
$this->className = 'Group';
$this->list_id = 'group';
$this->lang = true;
parent::__construct();
$this->addRowAction('edit');
$this->addRowAction('view');
$this->addRowAction('delete');
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
'icon' => 'icon-trash',
],
];
$groups_to_keep = [
Configuration::get('PS_UNIDENTIFIED_GROUP'),
Configuration::get('PS_GUEST_GROUP'),
Configuration::get('PS_CUSTOMER_GROUP'),
];
$this->fields_list = [
'id_group' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Group name', [], 'Admin.Shopparameters.Feature'),
'filter_key' => 'b!name',
],
'reduction' => [
'title' => $this->trans('Discount (%)', [], 'Admin.Shopparameters.Feature'),
'align' => 'right',
'type' => 'percent',
],
'nb' => [
'title' => $this->trans('Members', [], 'Admin.Shopparameters.Feature'),
'align' => 'center',
'havingFilter' => true,
],
'show_prices' => [
'title' => $this->trans('Show prices', [], 'Admin.Shopparameters.Feature'),
'align' => 'center',
'type' => 'bool',
'orderby' => false,
],
'date_add' => [
'title' => $this->trans('Creation date', [], 'Admin.Shopparameters.Feature'),
'type' => 'date',
'align' => 'right',
],
];
$this->addRowActionSkipList('delete', $groups_to_keep);
$this->_select .= '(SELECT COUNT(jcg.`id_customer`)
FROM `' . _DB_PREFIX_ . 'customer_group` jcg
LEFT JOIN `' . _DB_PREFIX_ . 'customer` jc ON (jc.`id_customer` = jcg.`id_customer`)
WHERE jc.`deleted` != 1
' . Shop::addSqlRestriction(Shop::SHARE_CUSTOMER) . '
AND jcg.`id_group` = a.`id_group`) AS nb';
$this->_use_found_rows = false;
$groups = Group::getGroups(Context::getContext()->language->id, true);
if (Group::isFeatureActive()) {
$this->fields_options = [
'general' => [
'title' => $this->trans('Default groups options', [], 'Admin.Shopparameters.Feature'),
'fields' => [
'PS_UNIDENTIFIED_GROUP' => [
'title' => $this->trans('Visitors group', [], 'Admin.Shopparameters.Feature'),
'desc' => $this->trans('The group defined for your un-identified visitors.', [], 'Admin.Shopparameters.Help'),
'cast' => 'intval',
'type' => 'select',
'list' => $groups,
'identifier' => 'id_group',
],
'PS_GUEST_GROUP' => [
'title' => $this->trans('Guests group', [], 'Admin.Shopparameters.Feature'),
'desc' => $this->trans('The group defined for your identified guest customers (used in guest checkout).', [], 'Admin.Shopparameters.Help'),
'cast' => 'intval',
'type' => 'select',
'list' => $groups,
'identifier' => 'id_group',
],
'PS_CUSTOMER_GROUP' => [
'title' => $this->trans('Customers group', [], 'Admin.Shopparameters.Feature'),
'desc' => $this->trans('The group defined for your identified registered customers.', [], 'Admin.Shopparameters.Help'),
'cast' => 'intval',
'type' => 'select',
'list' => $groups,
'identifier' => 'id_group',
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
],
];
}
}
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryPlugin('fancybox');
$this->addJqueryUi('ui.sortable');
}
public function initToolbar()
{
if ($this->display == 'add' || $this->display == 'edit') {
$this->toolbar_btn['save-and-stay'] = [
'short' => 'SaveAndStay',
'href' => '#',
'desc' => $this->trans('Save, then add a category reduction.', [], 'Admin.Shopparameters.Feature'),
'force_desc' => true,
];
}
parent::initToolbar();
}
public function initPageHeaderToolbar()
{
if (Group::isFeatureActive() && empty($this->display)) {
$this->page_header_toolbar_btn['new_group'] = [
'href' => $this->context->link->getAdminLink('AdminGroups', true, [], ['addgroup' => 1]),
'desc' => $this->trans('Add new group', [], 'Admin.Shopparameters.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function initProcess()
{
$this->id_object = Tools::getValue('id_' . $this->table);
if (Tools::isSubmit('changeShowPricesVal') && $this->id_object) {
$this->action = 'change_show_prices_val';
}
if (Tools::getIsset('viewgroup')) {
$this->list_id = 'customer_group';
if (isset($_POST['submitReset' . $this->list_id])) {
$this->processResetFilters();
}
if (Tools::isSubmit('submitFilter')) {
self::$currentIndex .= '&id_group=' . (int) Tools::getValue('id_group') . '&viewgroup';
}
} else {
$this->list_id = 'group';
}
parent::initProcess();
// This is a composite page, we don't want the "options" display mode
if ($this->display == 'options') {
$this->display = '';
}
}
public function postProcess(): void
{
if (!Group::isFeatureActive()) {
return;
}
$tableCustomerGroup = 'customer_group';
if (!empty($_POST[$tableCustomerGroup . 'Box'])
&& is_array($_POST[$tableCustomerGroup . 'Box'])
&& (
Tools::isSubmit('submitBulkenableSelection' . $tableCustomerGroup)
|| Tools::isSubmit('submitBulkdisableSelection' . $tableCustomerGroup)
)
) {
$status = Tools::isSubmit('submitBulkenableSelection' . $tableCustomerGroup);
foreach ($_POST[$tableCustomerGroup . 'Box'] as $customerId) {
$customer = new Customer((int) $customerId);
$customer->setFieldsToUpdate(['active' => true]);
$customer->active = $status;
if (!$customer->update(false)) {
$this->errors[] = $this->trans('Failed to update the status', [], 'Admin.Notifications.Error');
break;
}
}
}
if (!count($this->errors)) {
parent::postProcess();
}
}
/**
* @return string|void
*/
public function renderView()
{
$this->context = Context::getContext();
if (!($group = $this->loadObject(true))) {
return;
}
$this->tpl_view_vars = [
'group' => $group,
'language' => $this->context->language,
// @phpstan-ignore-next-line
'customerList' => $this->renderCustomersList($group),
'categorieReductions' => $this->formatCategoryDiscountList($group->id),
];
return parent::renderView();
}
protected function renderCustomersList(Group $group)
{
$genders = [0 => '?'];
$genders_icon = ['default' => 'unknown.gif'];
foreach (Gender::getGenders() as $gender) {
/* @var Gender $gender */
$genders_icon[$gender->id] = '../genders/' . (int) $gender->id . '.jpg';
$genders[$gender->id] = $gender->name;
}
$this->table = 'customer_group';
$this->lang = false;
$this->list_id = 'customer_group';
$this->actions = [];
$this->addRowAction('edit');
$this->identifier = 'id_customer';
$this->bulk_actions = null;
$this->list_no_link = true;
$this->explicitSelect = true;
$this->list_skip_actions = [];
$this->fields_list = [
'id_customer' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'filter_key' => 'c!id_customer',
'class' => 'fixed-width-xs',
],
'id_gender' => [
'title' => $this->trans('Social title', [], 'Admin.Global'),
'icon' => $genders_icon,
'list' => $genders,
],
'firstname' => [
'title' => $this->trans('First name', [], 'Admin.Global'),
'maxlength' => 30,
],
'lastname' => [
'title' => $this->trans('Last name', [], 'Admin.Global'),
'maxlength' => 30,
],
'email' => [
'title' => $this->trans('Email address', [], 'Admin.Global'),
'filter_key' => 'c!email',
'orderby' => true,
'maxlength' => 50,
],
'birthday' => [
'title' => $this->trans('Date of birth', [], 'Admin.Global'),
'type' => 'date',
'class' => 'fixed-width-md',
'align' => 'center',
],
'date_add' => [
'title' => $this->trans('Registration date', [], 'Admin.Shopparameters.Feature'),
'type' => 'date',
'class' => 'fixed-width-md',
'align' => 'center',
],
'active' => [
'title' => $this->trans('Enabled', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-sm',
'type' => 'bool',
'search' => false,
'orderby' => false,
'filter_key' => 'c!active',
'callback' => 'printOptinIcon',
],
];
$this->_select = 'c.*, a.id_group';
$this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'customer` c ON (a.`id_customer` = c.`id_customer`)';
$this->_where = 'AND a.`id_group` = ' . (int) $group->id . ' AND c.`deleted` != 1';
$this->_where .= Shop::addSqlRestriction(Shop::SHARE_CUSTOMER, 'c');
self::$currentIndex = self::$currentIndex . '&id_group=' . (int) $group->id . '&viewgroup';
$this->processFilter();
return parent::renderList();
}
public function printOptinIcon($value, $customer)
{
return $value ? '<i class="icon-check"></i>' : '<i class="icon-remove"></i>';
}
/**
* @return string|void
*
* @throws PrestaShopException
* @throws SmartyException
*/
public function renderForm()
{
if (!($group = $this->loadObject(true))) {
return;
}
$this->fields_form = [
'legend' => [
'title' => $this->trans('Customer group', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-group',
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'required' => true,
'lang' => true,
'col' => 4,
'hint' => $this->trans('Forbidden characters:', [], 'Admin.Notifications.Info') . ' 0-9!&amp;lt;&amp;gt;,;?=+()@#"<22>{}_$%:',
],
[
'type' => 'text',
'label' => $this->trans('Discount', [], 'Admin.Global'),
'name' => 'reduction',
'suffix' => '%',
'col' => 3,
'hint' => $this->trans('Automatically apply this value as a discount on all products for members of this customer group.', [], 'Admin.Shopparameters.Help'),
],
[
'type' => 'select',
'label' => $this->trans('Price display method', [], 'Admin.Shopparameters.Feature'),
'name' => 'price_display_method',
'col' => 2,
'hint' => $this->trans('How prices are displayed in the order summary for this customer group.', [], 'Admin.Shopparameters.Help'),
'options' => [
'query' => [
[
'id_method' => PS_TAX_EXC,
'name' => $this->trans('Tax excluded', [], 'Admin.Global'),
],
[
'id_method' => PS_TAX_INC,
'name' => $this->trans('Tax included', [], 'Admin.Global'),
],
],
'id' => 'id_method',
'name' => 'name',
],
],
[
'type' => 'switch',
'label' => $this->trans('Show prices', [], 'Admin.Shopparameters.Feature'),
'name' => 'show_prices',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'show_prices_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'show_prices_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Customers in this group can view prices.', [], 'Admin.Shopparameters.Help'),
'desc' => $this->trans('Need to hide prices for all groups? Save time, enable catalog mode in Product Settings instead.', [], 'Admin.Shopparameters.Help'),
],
[
'type' => 'group_discount_category',
'label' => $this->trans('Category discount', [], 'Admin.Shopparameters.Feature'),
'name' => 'reduction',
'values' => ($group->id ? $this->formatCategoryDiscountList((int) $group->id) : []),
],
[
'type' => 'modules',
'label' => $this->trans('Modules authorization', [], 'Admin.Shopparameters.Feature'),
'name' => 'auth_modules',
'values' => $this->formatModuleListAuth($group->id),
],
],
];
if (Shop::isFeatureActive()) {
$this->fields_form['input'][] = [
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
];
}
if (Tools::getIsset('addgroup')) {
$this->fields_value['price_display_method'] = Configuration::get('PRICE_DISPLAY_METHOD');
}
$this->fields_value['reduction'] = isset($group->reduction) ? $group->reduction : 0;
$tree = new HelperTreeCategories('categories-tree');
$this->tpl_form_vars['categoryTreeView'] = $tree->setRootCategory((int) Category::getRootCategory()->id)->render();
return parent::renderForm();
}
protected function formatCategoryDiscountList(int $id_group)
{
$group_reductions = GroupReduction::getGroupReductions((int) $id_group, $this->context->language->id);
$category_reductions = [];
$category_reduction = Tools::getValue('category_reduction');
foreach ($group_reductions as $category) {
if (is_array($category_reduction) && array_key_exists($category['id_category'], $category_reduction)) {
$category['reduction'] = $category_reduction[$category['id_category']];
}
$category_reductions[(int) $category['id_category']] = [
'path' => Tools::getPath(Context::getContext()->link->getAdminLink('AdminCategories'), (int) $category['id_category']),
'reduction' => (float) $category['reduction'] * 100,
'id_category' => (int) $category['id_category'],
];
}
if (is_array($category_reduction)) {
foreach ($category_reduction as $key => $val) {
if (!array_key_exists($key, $category_reductions)) {
$category_reductions[(int) $key] = [
'path' => Tools::getPath(Context::getContext()->link->getAdminLink('AdminCategories'), $key),
'reduction' => (float) $val * 100,
'id_category' => (int) $key,
];
}
}
}
return $category_reductions;
}
public function formatModuleListAuth($id_group)
{
$modules = Module::getModulesInstalled();
$authorized_modules = '';
$auth_modules = [];
$unauth_modules = [];
$shops = Shop::getContextListShopID();
if ($id_group) {
$authorized_modules = Module::getAuthorizedModules($id_group, $shops);
}
if (is_array($authorized_modules)) {
foreach ($modules as $module) {
$authorized = false;
foreach ($authorized_modules as $auth_module) {
if ($module['id_module'] == $auth_module['id_module']) {
$authorized = true;
}
}
if ($authorized) {
$auth_modules[] = $module;
} else {
$unauth_modules[] = $module;
}
}
} else {
$auth_modules = $modules;
}
$auth_modules_tmp = [];
foreach ($auth_modules as $key => $val) {
if ($module = Module::getInstanceById($val['id_module'])) {
$auth_modules_tmp[] = $module;
}
}
$auth_modules = $auth_modules_tmp;
$unauth_modules_tmp = [];
foreach ($unauth_modules as $key => $val) {
if ($tmp_obj = Module::getInstanceById($val['id_module'])) {
$unauth_modules_tmp[] = $tmp_obj;
}
}
$unauth_modules = $unauth_modules_tmp;
return ['unauth_modules' => $unauth_modules, 'auth_modules' => $auth_modules];
}
public function processSave()
{
if (!$this->validateDiscount(Tools::getValue('reduction'))) {
$this->errors[] = $this->trans('The discount value is incorrect (must be a percentage).', [], 'Admin.Shopparameters.Notification');
} else {
$this->updateCategoryReduction();
$object = parent::processSave();
$this->updateRestrictions();
return $object;
}
}
protected function validateDiscount($reduction)
{
if (!Validate::isPrice($reduction) || $reduction > 100 || $reduction < 0) {
return false;
} else {
return true;
}
}
public function ajaxProcessAddCategoryReduction()
{
$category_reduction = Tools::getValue('category_reduction');
$id_category = Tools::getValue('id_category'); // no cast validation is done with Validate::isUnsignedId($id_category)
$result = [];
if (!Validate::isUnsignedId($id_category)) {
$result['errors'][] = $this->trans('Wrong category ID.', [], 'Admin.Shopparameters.Notification');
$result['hasError'] = true;
} elseif (!$this->validateDiscount($category_reduction)) {
$result['errors'][] = $this->trans('The discount value is incorrect (must be a percentage).', [], 'Admin.Shopparameters.Notification');
$result['hasError'] = true;
} else {
$result['id_category'] = (int) $id_category;
$result['catPath'] = Tools::getPath(self::$currentIndex . '?controller=AdminCategories', (int) $id_category);
$result['discount'] = $category_reduction;
$result['hasError'] = false;
}
die(json_encode($result));
}
/**
* Update (or create) restrictions for modules by group.
*/
protected function updateRestrictions()
{
$id_group = Tools::getValue('id_group');
$auth_modules = Tools::getValue('modulesBoxAuth');
$return = true;
if ($id_group) {
$shops = Shop::getContextListShopID();
if (is_array($auth_modules)) {
$return &= Group::addModulesRestrictions($id_group, $auth_modules, $shops);
}
}
// update module list by hook cache
Cache::clean(Hook::MODULE_LIST_BY_HOOK_KEY . '*');
return $return;
}
protected function updateCategoryReduction()
{
$category_reduction = Tools::getValue('category_reduction');
Db::getInstance()->execute(
'
DELETE FROM `' . _DB_PREFIX_ . 'group_reduction`
WHERE `id_group` = ' . (int) Tools::getValue('id_group')
);
Db::getInstance()->execute(
'
DELETE FROM `' . _DB_PREFIX_ . 'product_group_reduction_cache`
WHERE `id_group` = ' . (int) Tools::getValue('id_group')
);
if (is_array($category_reduction) && count($category_reduction)) {
if (!Configuration::getGlobalValue('PS_GROUP_FEATURE_ACTIVE')) {
Configuration::updateGlobalValue('PS_GROUP_FEATURE_ACTIVE', 1);
}
foreach ($category_reduction as $cat => $reduction) {
if (!Validate::isUnsignedId($cat) || !$this->validateDiscount($reduction)) {
$this->errors[] = $this->trans('The discount value is incorrect.', [], 'Admin.Shopparameters.Notification');
} else {
$category = new Category((int) $cat);
$category->addGroupsIfNoExist((int) Tools::getValue('id_group'));
$group_reduction = new GroupReduction();
$group_reduction->id_group = (int) Tools::getValue('id_group');
$group_reduction->reduction = (float) ($reduction / 100);
$group_reduction->id_category = (int) $cat;
if (!$group_reduction->save()) {
$this->errors[] = $this->trans('You cannot save group reductions.', [], 'Admin.Shopparameters.Notification');
}
}
}
}
}
/**
* Toggle show prices flag.
*/
public function processChangeShowPricesVal()
{
$group = new Group($this->id_object);
if (!Validate::isLoadedObject($group)) {
$this->errors[] = $this->trans('An error occurred while updating this group.', [], 'Admin.Shopparameters.Notification');
}
$update = Db::getInstance()->execute('UPDATE `' . _DB_PREFIX_ . 'group` SET show_prices = ' . ($group->show_prices ? 0 : 1) . ' WHERE `id_group` = ' . (int) $group->id);
if (!$update) {
$this->errors[] = $this->trans('An error occurred while updating this group.', [], 'Admin.Shopparameters.Notification');
}
Tools::clearSmartyCache();
Tools::redirectAdmin(self::$currentIndex . '&token=' . $this->token);
}
public function renderList()
{
$unidentified = new Group((int) Configuration::get('PS_UNIDENTIFIED_GROUP'));
$guest = new Group((int) Configuration::get('PS_GUEST_GROUP'));
$default = new Group((int) Configuration::get('PS_CUSTOMER_GROUP'));
$unidentified_group_information = $this->trans('%group_name% - All persons without a customer account or customers that are not logged in.', ['%group_name%' => '<b>' . htmlentities($unidentified->name[$this->context->language->id]) . '</b>'], 'Admin.Shopparameters.Help');
$guest_group_information = $this->trans('%group_name% - All persons who placed an order through Guest Checkout.', ['%group_name%' => '<b>' . htmlentities($guest->name[$this->context->language->id]) . '</b>'], 'Admin.Shopparameters.Help');
$default_group_information = $this->trans('%group_name% - All persons who created an account on this site.', ['%group_name%' => '<b>' . htmlentities($default->name[$this->context->language->id]) . '</b>'], 'Admin.Shopparameters.Help');
$this->displayInformation($this->trans('PrestaShop has three default customer groups:', [], 'Admin.Shopparameters.Help'));
$this->displayInformation($unidentified_group_information);
$this->displayInformation($guest_group_information);
$this->displayInformation($default_group_information);
return parent::renderList();
}
public function displayEditLink($token, $id)
{
$tpl = $this->createTemplate('helpers/list/list_action_edit.tpl');
if (!array_key_exists('Edit', self::$cache_lang)) {
self::$cache_lang['Edit'] = $this->trans('Edit', [], 'Admin.Actions');
}
$href = self::$currentIndex . '&' . $this->identifier . '=' . $id . '&update' . $this->table . '&token=' . ($token != null ? $token : $this->token);
if ($this->display == 'view') {
$href = Context::getContext()->link->getAdminLink('AdminCustomers', true, [], [
'id_customer' => $id,
'updatecustomer' => 1,
'back' => urlencode($href),
]);
}
$tpl->assign([
'href' => $href,
'action' => self::$cache_lang['Edit'],
'id' => $id,
]);
return $tpl->fetch();
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
if (!Group::isFeatureActive()) {
$adminPerformanceUrl = $this->context->link->getAdminLink('AdminPerformance');
$url = '<a href="' . $adminPerformanceUrl . '">' . $this->trans('Performance', [], 'Admin.Global') . '</a>';
$this->displayWarning($this->trans('This feature has been disabled. You can activate it here: %url%.', ['%url%' => $url], 'Admin.Catalog.Notification'));
$this->context->smarty->assign([
'content' => $this->content,
]);
return;
}
parent::initContent();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,550 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AdminModulesPositionsControllerCore extends AdminController
{
protected $display_key = 0;
public function __construct()
{
$this->bootstrap = true;
parent::__construct();
}
public function postProcess()
{
// Getting key value for display
if (Tools::getValue('show_modules') && (string) Tools::getValue('show_modules') != 'all') {
$this->display_key = (int) Tools::getValue('show_modules');
}
$this->addjQueryPlugin([
'select2',
]);
$this->addJS([
_PS_JS_DIR_ . 'admin/modules-position.js',
_PS_JS_DIR_ . 'jquery/plugins/select2/select2_locale_' . $this->context->language->iso_code . '.js',
]);
$baseUrl = $this->context->link->getAdminLink('AdminModulesPositions');
if (strpos($baseUrl, '?') === false) {
$baseUrl .= '?';
}
// Change position in hook
if (array_key_exists('changePosition', $_GET)) {
if ($this->access('edit')) {
$id_module = (int) Tools::getValue('id_module');
$id_hook = (int) Tools::getValue('id_hook');
$module = Module::getInstanceById($id_module);
if (Validate::isLoadedObject($module)) {
$module->updatePosition($id_hook, (bool) Tools::getValue('direction'));
Tools::redirectAdmin($baseUrl . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
} else {
$this->errors[] = $this->trans('This module cannot be loaded.', [], 'Admin.Modules.Notification');
}
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('submitAddToHook')) {
// Add new module in hook
if ($this->access('add')) {
// Getting vars...
$id_module = (int) Tools::getValue('id_module');
$module = Module::getInstanceById($id_module);
$id_hook = (int) Tools::getValue('id_hook');
$hook = new Hook($id_hook);
if (!$id_module || !Validate::isLoadedObject($module)) {
$this->errors[] = $this->trans('This module cannot be loaded.', [], 'Admin.Modules.Notification');
} elseif (!$id_hook || !Validate::isLoadedObject($hook)) {
$this->errors[] = $this->trans('Hook cannot be loaded.', [], 'Admin.Modules.Notification');
} elseif (Hook::getModulesFromHook($id_hook, $id_module)) {
$this->errors[] = $this->trans('This module has already been transplanted to this hook.', [], 'Admin.Modules.Notification');
} elseif (!$module->isHookableOn($hook->name)) {
$this->errors[] = $this->trans('This module cannot be transplanted to this hook.', [], 'Admin.Modules.Notification');
} else {
// Adding vars...
if (!$module->registerHook($hook->name, Shop::getContextListShopID())) {
$this->errors[] = $this->trans('An error occurred while transplanting the module to its hook.', [], 'Admin.Modules.Notification');
} else {
$exceptions = Tools::getValue('exceptions');
$exceptions = (isset($exceptions[0])) ? $exceptions[0] : [];
$exceptions = explode(',', str_replace(' ', '', $exceptions));
$exceptions = array_unique($exceptions);
foreach ($exceptions as $key => $except) {
if (empty($except)) {
unset($exceptions[$key]);
} elseif (!Validate::isFileName($except)) {
$this->errors[] = $this->trans('No valid value for field exceptions has been defined.', [], 'Admin.Notifications.Error');
}
}
if (!$this->errors && !$module->registerExceptions($id_hook, $exceptions, Shop::getContextListShopID())) {
$this->errors[] = $this->trans('An error occurred while transplanting the module to its hook.', [], 'Admin.Notifications.Error');
}
}
if (!$this->errors) {
Tools::redirectAdmin($baseUrl . '&conf=16' . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
}
}
} else {
$this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('submitEditGraft')) {
// Edit module from hook
if ($this->access('add')) {
// Getting vars...
$id_module = (int) Tools::getValue('id_module');
$module = Module::getInstanceById($id_module);
$id_hook = (int) Tools::getValue('id_hook');
$new_hook = (int) Tools::getValue('new_hook');
$hook = new Hook($new_hook);
if (!$id_module || !Validate::isLoadedObject($module)) {
$this->errors[] = $this->trans('This module cannot be loaded.', [], 'Admin.Modules.Notification');
} elseif (!$id_hook || !Validate::isLoadedObject($hook)) {
$this->errors[] = $this->trans('Hook cannot be loaded.', [], 'Admin.Modules.Notification');
} else {
if ($new_hook !== $id_hook) {
/* Connect module to a newer hook */
if (!$module->registerHook($hook->name, Shop::getContextListShopID())) {
$this->errors[] = $this->trans('An error occurred while transplanting the module to its hook.', [], 'Admin.Modules.Notification');
}
/* Unregister module from hook & exceptions linked to module */
if (!$module->unregisterHook($id_hook, Shop::getContextListShopID())
|| !$module->unregisterExceptions($id_hook, Shop::getContextListShopID())) {
$this->errors[] = $this->trans('An error occurred while deleting the module from its hook.', [], 'Admin.Modules.Notification');
}
$id_hook = $new_hook;
}
$exceptions = Tools::getValue('exceptions');
if (is_array($exceptions)) {
foreach ($exceptions as $id => $exception) {
$exception = explode(',', str_replace(' ', '', $exception));
$exception = array_unique($exception);
// Check files name
foreach ($exception as $except) {
if (!empty($except) && !Validate::isFileName($except)) {
$this->errors[] = $this->trans('No valid value for field exceptions has been defined.', [], 'Admin.Notifications.Error');
}
}
$exceptions[$id] = $exception;
}
// Add files exceptions
if (!$module->editExceptions($id_hook, $exceptions)) {
$this->errors[] = $this->trans('An error occurred while transplanting the module to its hook.', [], 'Admin.Modules.Notification');
}
if (!$this->errors) {
Tools::redirectAdmin($baseUrl . '&conf=16' . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
}
} else {
$exceptions = explode(',', str_replace(' ', '', $exceptions));
$exceptions = array_unique($exceptions);
// Check files name
foreach ($exceptions as $except) {
if (!empty($except) && !Validate::isFileName($except)) {
$this->errors[] = $this->trans('No valid value for field exceptions has been defined.', [], 'Admin.Notifications.Error');
}
}
// Add files exceptions
if (!$module->editExceptions($id_hook, $exceptions)) {
$this->errors[] = $this->trans('An error occurred while transplanting the module to its hook.', [], 'Admin.Modules.Notification');
} else {
Tools::redirectAdmin($baseUrl . '&conf=16' . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
}
}
}
} else {
$this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error');
}
} elseif (array_key_exists('deleteGraft', $_GET)) {
// Delete module from hook
if ($this->access('delete')) {
$id_module = (int) Tools::getValue('id_module');
$module = Module::getInstanceById($id_module);
$id_hook = (int) Tools::getValue('id_hook');
$hook = new Hook($id_hook);
if (!Validate::isLoadedObject($module)) {
$this->errors[] = $this->trans('This module cannot be loaded.', [], 'Admin.Modules.Notification');
} elseif (!$id_hook || !Validate::isLoadedObject($hook)) {
$this->errors[] = $this->trans('Hook cannot be loaded.', [], 'Admin.Modules.Notification');
} else {
if (!$module->unregisterHook($id_hook, Shop::getContextListShopID())
|| !$module->unregisterExceptions($id_hook, Shop::getContextListShopID())) {
$this->errors[] = $this->trans('An error occurred while deleting the module from its hook.', [], 'Admin.Modules.Notification');
} else {
Tools::redirectAdmin($baseUrl . '&conf=17' . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
}
}
} else {
$this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('unhookform')) {
if (!($unhooks = Tools::getValue('unhooks')) || !is_array($unhooks)) {
$this->errors[] = $this->trans('Please select a module to unhook.', [], 'Admin.Modules.Notification');
} else {
foreach ($unhooks as $unhook) {
$explode = explode('_', $unhook);
$id_hook = $explode[0];
$id_module = $explode[1];
$module = Module::getInstanceById((int) $id_module);
$hook = new Hook((int) $id_hook);
if (!Validate::isLoadedObject($module)) {
$this->errors[] = $this->trans('This module cannot be loaded.', [], 'Admin.Modules.Notification');
} elseif (!$id_hook || !Validate::isLoadedObject($hook)) {
$this->errors[] = $this->trans('Hook cannot be loaded.', [], 'Admin.Modules.Notification');
} else {
if (!$module->unregisterHook((int) $id_hook) || !$module->unregisterExceptions((int) $id_hook)) {
$this->errors[] = $this->trans('An error occurred while deleting the module from its hook.', [], 'Admin.Modules.Notification');
}
}
}
if (!count($this->errors)) {
Tools::redirectAdmin($baseUrl . '&conf=17' . ($this->display_key ? '&show_modules=' . $this->display_key : '') . '&token=' . $this->token);
}
}
} else {
parent::postProcess();
}
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
$this->addjqueryPlugin('sortable');
if (array_key_exists('addToHook', $_GET) || array_key_exists('editGraft', $_GET) || (Tools::isSubmit('submitAddToHook') && $this->errors)) {
$this->display = 'edit';
$this->content .= $this->renderForm();
}
$this->context->smarty->assign([
'content' => $this->content,
]);
}
public function initPageHeaderToolbar()
{
return parent::initPageHeaderToolbar();
}
public function renderForm()
{
// Init toolbar
$this->initToolbarTitle();
// toolbar (save, cancel, new, ..)
$this->initToolbar();
$id_module = (int) Tools::getValue('id_module');
$id_hook = (int) Tools::getValue('id_hook');
$show_modules = (int) Tools::getValue('show_modules');
if (Tools::isSubmit('editGraft')) {
// Check auth for this page
if (!$id_module || !$id_hook) {
Tools::redirectAdmin(self::$currentIndex . '&token=' . $this->token);
}
$sql = 'SELECT id_module
FROM ' . _DB_PREFIX_ . 'hook_module
WHERE id_module = ' . $id_module . '
AND id_hook = ' . $id_hook . '
AND id_shop IN(' . implode(', ', Shop::getContextListShopID()) . ')';
if (!Db::getInstance()->getValue($sql)) {
Tools::redirectAdmin(self::$currentIndex . '&token=' . $this->token);
}
$sl_module = Module::getInstanceById($id_module);
$excepts_list = $sl_module->getExceptions($id_hook, true);
$excepts_diff = false;
$excepts = '';
if ($excepts_list) {
$first = current($excepts_list);
foreach ($excepts_list as $k => $v) {
if (array_diff($v, $first) || array_diff($first, $v)) {
$excepts_diff = true;
}
}
if (!$excepts_diff) {
$excepts = implode(', ', $first);
}
}
} else {
$excepts_diff = false;
$excepts_list = Tools::getValue('exceptions', [[]]);
}
$modules = Module::getModulesInstalled(0);
$instances = [];
foreach ($modules as $module) {
if ($tmp_instance = Module::getInstanceById($module['id_module'])) {
$instances[$tmp_instance->displayName] = $tmp_instance;
}
}
ksort($instances);
$modules = $instances;
$hooks = [];
if ($show_modules || (Tools::getValue('id_hook') > 0)) {
$module_instance = Module::getInstanceById((int) Tools::getValue('id_module', $show_modules));
$hooks = $module_instance->getPossibleHooksList();
}
$exception_list_diff = [];
foreach ($excepts_list as $shop_id => $file_list) {
$exception_list_diff[] = $this->displayModuleExceptionList($file_list, $shop_id);
}
$tpl = $this->createTemplate('form.tpl');
$tpl->assign([
'url_submit' => self::$currentIndex . '&token=' . $this->token,
'edit_graft' => Tools::isSubmit('editGraft'),
'id_module' => (int) Tools::getValue('id_module'),
'id_hook' => (int) Tools::getValue('id_hook'),
'show_modules' => $show_modules,
'hooks' => $hooks,
'exception_list' => $this->displayModuleExceptionList(array_shift($excepts_list), 0),
'exception_list_diff' => $exception_list_diff,
'except_diff' => $excepts_diff,
'display_key' => $this->display_key,
'modules' => $modules,
'show_toolbar' => true,
'toolbar_btn' => $this->toolbar_btn,
'toolbar_scroll' => $this->toolbar_scroll,
'title' => $this->toolbar_title,
'table' => 'hook_module',
]);
return $tpl->fetch();
}
public function displayModuleExceptionList($file_list, $shop_id)
{
if (!is_array($file_list)) {
$file_list = ($file_list) ? [$file_list] : [];
}
$content = '<p><input type="text" name="exceptions[' . $shop_id . ']" value="' . implode(', ', $file_list) . '" id="em_text_' . $shop_id . '" placeholder="' . $this->trans('E.g. address, addresses, attachment', [], 'Admin.Design.Help') . '"/></p>';
if ($shop_id) {
$shop = new Shop($shop_id);
$content .= ' (' . $shop->name . ')';
}
$content .= '<p>
<select size="25" id="em_list_' . $shop_id . '" multiple="multiple">
<option disabled="disabled">'
. $this->trans('___________ CUSTOM ___________', [], 'Admin.Design.Feature')
. '</option>';
$controllers = Dispatcher::getControllersPhpselfList(_PS_FRONT_CONTROLLER_DIR_);
asort($controllers);
foreach ($file_list as $k => $v) {
if (!in_array($v, $controllers)) {
$content .= '<option value="' . $v . '">' . $v . '</option>';
}
}
$content .= '<option disabled="disabled">' . $this->trans('____________ CORE ____________', [], 'Admin.Design.Feature') . '</option>';
foreach ($controllers as $k => $v) {
$content .= '<option value="' . $v . '">' . $v . '</option>';
}
$modules_controllers_type = ['admin' => $this->trans('Admin modules controller', [], 'Admin.Design.Feature'), 'front' => $this->trans('Front modules controller', [], 'Admin.Design.Feature')];
foreach ($modules_controllers_type as $type => $label) {
$content .= '<option disabled="disabled">____________ ' . $label . ' ____________</option>';
$all_modules_controllers = Dispatcher::getModuleControllers($type);
foreach ($all_modules_controllers as $module => $modules_controllers) {
foreach ($modules_controllers as $cont) {
$content .= '<option value="module-' . $module . '-' . $cont . '">module-' . $module . '-' . $cont . '</option>';
}
}
}
$content .= '</select>
</p>';
return $content;
}
public function ajaxProcessUpdatePositions()
{
if ($this->access('edit')) {
$id_module = (int) Tools::getValue('id_module');
$id_hook = (int) Tools::getValue('id_hook');
$way = (bool) Tools::getValue('way');
$positions = Tools::getValue((string) $id_hook);
$position = (is_array($positions)) ? array_search($id_hook . '_' . $id_module, $positions) : null;
$module = Module::getInstanceById($id_module);
if (Validate::isLoadedObject($module)) {
if ($module->updatePosition($id_hook, $way, $position)) {
die(true);
} else {
die('{"hasError" : true, "errors" : "Cannot update module position."}');
}
} else {
die('{"hasError" : true, "errors" : "This module cannot be loaded."}');
}
}
}
public function ajaxProcessGetHookableList()
{
if ($this->access('view')) {
/* PrestaShop demo mode */
if (_PS_MODE_DEMO_) {
die('{"hasError" : true, "errors" : ["Live Edit: This functionality has been disabled."]}');
}
if (!count(Tools::getValue('hooks_list'))) {
die('{"hasError" : true, "errors" : ["Live Edit: no module on this page."]}');
}
$modules_list = Tools::getValue('modules_list');
$hooks_list = Tools::getValue('hooks_list');
$hookableList = [];
foreach ($modules_list as $module) {
$module = trim($module);
if (!$module) {
continue;
}
if (!Validate::isModuleName($module)) {
die('{"hasError" : true, "errors" : ["Live Edit: module is invalid."]}');
}
$moduleInstance = Module::getInstanceByName($module);
foreach ($hooks_list as $hook_name) {
$hook_name = trim($hook_name);
if (!$hook_name) {
continue;
}
if (!array_key_exists($hook_name, $hookableList)) {
$hookableList[$hook_name] = [];
}
if ($moduleInstance->isHookableOn($hook_name)) {
$hookableList[$hook_name][] = str_replace('_', '-', $module);
}
}
}
$hookableList['hasError'] = false;
die(json_encode($hookableList));
}
}
public function ajaxProcessGetHookableModuleList()
{
if ($this->access('view')) {
/* PrestaShop demo mode */
if (_PS_MODE_DEMO_) {
die('{"hasError" : true, "errors" : ["Live Edit: This functionality has been disabled."]}');
}
/* PrestaShop demo mode */
$hook_name = Tools::getValue('hook');
$hookableModulesList = [];
$modules = Db::getInstance()->executeS('SELECT id_module, name FROM `' . _DB_PREFIX_ . 'module` ');
foreach ($modules as $module) {
if (!Validate::isModuleName($module['name'])) {
continue;
}
if (file_exists(_PS_MODULE_DIR_ . $module['name'] . '/' . $module['name'] . '.php')) {
include_once _PS_MODULE_DIR_ . $module['name'] . '/' . $module['name'] . '.php';
/** @var Module $mod */
$mod = new $module['name']();
if ($mod->isHookableOn($hook_name)) {
$hookableModulesList[] = ['id' => (int) $mod->id, 'name' => $mod->displayName, 'display' => Hook::exec($hook_name, [], (int) $mod->id)];
}
}
}
die(json_encode($hookableModulesList));
}
}
public function ajaxProcessSaveHook()
{
if ($this->access('edit')) {
/* PrestaShop demo mode */
if (_PS_MODE_DEMO_) {
die('{"hasError" : true, "errors" : ["Live Edit: This functionality has been disabled."]}');
}
$hooks_list = explode(',', Tools::getValue('hooks_list'));
$id_shop = (int) Tools::getValue('id_shop');
if (!$id_shop) {
$id_shop = Context::getContext()->shop->id;
}
$res = true;
$hookableList = [];
// $_POST['hook'] is an array of id_module
$hooks_list = Tools::getValue('hook');
foreach ($hooks_list as $id_hook => $modules) {
// 1st, drop all previous hooked modules
$sql = 'DELETE FROM `' . _DB_PREFIX_ . 'hook_module` WHERE `id_hook` = ' . (int) $id_hook . ' AND id_shop = ' . (int) $id_shop;
$res &= Db::getInstance()->execute($sql);
$i = 1;
$value = '';
$ids = [];
// then prepare sql query to rehook all chosen modules(id_module, id_shop, id_hook, position)
// position is i (autoincremented)
if (is_array($modules) && count($modules)) {
foreach ($modules as $id_module) {
if ($id_module && !in_array($id_module, $ids)) {
$ids[] = (int) $id_module;
$value .= '(' . (int) $id_module . ', ' . (int) $id_shop . ', ' . (int) $id_hook . ', ' . (int) $i . '),';
}
++$i;
}
if ($value) {
$value = rtrim($value, ',');
$res &= Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'hook_module` (id_module, id_shop, id_hook, position) VALUES ' . $value);
}
}
}
if ($res) {
$hasError = true;
} else {
$hasError = false;
}
die('{"hasError" : false, "errors" : ""}');
}
}
/**
* Return a json array containing the possible hooks for a module.
*/
public function ajaxProcessGetPossibleHookingListForModule()
{
if ($this->access('view')) {
$module_id = (int) Tools::getValue('module_id');
if ($module_id == 0) {
die('{"hasError" : true, "errors" : ["Wrong module ID."]}');
}
$module_instance = Module::getInstanceById($module_id);
die(json_encode($module_instance->getPossibleHooksList()));
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AdminNotFoundControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
parent::__construct();
}
public function checkAccess()
{
return true;
}
public function viewAccess($disable = false)
{
return true;
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
$this->errors[] = $this->trans('Page not found', [], 'Admin.Notifications.Error');
$tpl_vars['controller'] = Tools::getValue('controllerUri', Tools::getValue('controller'));
$this->context->smarty->assign($tpl_vars);
parent::initContent();
}
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
class AdminPdfControllerCore extends AdminController
{
public function postProcess()
{
parent::postProcess();
// We want to be sure that displaying PDF is the last thing this controller will do
exit;
}
public function initProcess()
{
parent::initProcess();
$this->checkCacheFolder();
$access = Profile::getProfileAccess($this->context->employee->id_profile, (int) Tab::getIdFromClassName('AdminOrders'));
if ($access['view'] === '1' && ($action = Tools::getValue('submitAction'))) {
$this->action = $action;
} else {
$this->errors[] = $this->trans('You do not have permission to view this.', [], 'Admin.Notifications.Error');
}
}
public function checkCacheFolder()
{
if (!is_dir(_PS_CACHE_DIR_ . 'tcpdf/')) {
mkdir(_PS_CACHE_DIR_ . 'tcpdf/');
}
}
public function processGenerateInvoicePdf()
{
if (Tools::isSubmit('id_order')) {
$sfContainer = SymfonyContainer::getInstance();
$sfRouter = $sfContainer->get('router');
Tools::redirectAdmin($sfRouter->generate(
'admin_orders_generate_invoice_pdf',
['orderId' => (int) Tools::getValue('id_order')]
));
} elseif (Tools::isSubmit('id_order_invoice')) {
$this->generateInvoicePDFByIdOrderInvoice(Tools::getValue('id_order_invoice'));
} else {
die($this->trans('The order ID -- or the invoice order ID -- is missing.', [], 'Admin.Orderscustomers.Notification'));
}
}
public function processGenerateOrderSlipPDF()
{
$order_slip = new OrderSlip((int) Tools::getValue('id_order_slip'));
$order = new Order((int) $order_slip->id_order);
if (!Validate::isLoadedObject($order)) {
die($this->trans('The order cannot be found within your database.', [], 'Admin.Orderscustomers.Notification'));
}
$this->generatePDF($order_slip, PDF::TEMPLATE_ORDER_SLIP);
}
public function processGenerateDeliverySlipPDF()
{
$sfContainer = SymfonyContainer::getInstance();
$sfRouter = $sfContainer->get('router');
if (Tools::isSubmit('id_order')) {
Tools::redirectAdmin($sfRouter->generate(
'admin_orders_generate_delivery_slip_pdf',
['orderId' => (int) Tools::getValue('id_order')]
));
} elseif (Tools::isSubmit('id_order_invoice')) {
$this->generateDeliverySlipPDFByIdOrderInvoice((int) Tools::getValue('id_order_invoice'));
} elseif (Tools::isSubmit('id_delivery')) {
$order = Order::getByDelivery((int) Tools::getValue('id_delivery'));
Tools::redirectAdmin($sfRouter->generate(
'admin_orders_generate_delivery_slip_pdf',
['orderId' => (int) $order->id]
));
} else {
die($this->trans('The order ID -- or the invoice order ID -- is missing.', [], 'Admin.Orderscustomers.Notification'));
}
}
public function processGenerateInvoicesPDF()
{
$order_invoice_collection = OrderInvoice::getByDateInterval(Tools::getValue('date_from'), Tools::getValue('date_to'));
if (!count($order_invoice_collection)) {
die($this->trans('No invoice was found.', [], 'Admin.Orderscustomers.Notification'));
}
$this->generatePDF($order_invoice_collection, PDF::TEMPLATE_INVOICE);
}
public function processGenerateInvoicesPDF2()
{
$order_invoice_collection = [];
foreach (explode('-', Tools::getValue('id_order_state')) as $id_order_state) {
if (is_array($order_invoices = OrderInvoice::getByStatus((int) $id_order_state))) {
$order_invoice_collection = array_merge($order_invoices, $order_invoice_collection);
}
}
if (!count($order_invoice_collection)) {
die($this->trans('No invoice was found.', [], 'Admin.Orderscustomers.Notification'));
}
$this->generatePDF($order_invoice_collection, PDF::TEMPLATE_INVOICE);
}
public function processGenerateOrderSlipsPDF()
{
$id_order_slips_list = OrderSlip::getSlipsIdByDate(Tools::getValue('date_from'), Tools::getValue('date_to'));
if (!count($id_order_slips_list)) {
die($this->trans('No order slips were found.', [], 'Admin.Orderscustomers.Notification'));
}
$order_slips = [];
foreach ($id_order_slips_list as $id_order_slips) {
$order_slips[] = new OrderSlip((int) $id_order_slips);
}
$this->generatePDF($order_slips, PDF::TEMPLATE_ORDER_SLIP);
}
public function processGenerateDeliverySlipsPDF()
{
$order_invoice_collection = OrderInvoice::getByDeliveryDateInterval(Tools::getValue('date_from'), Tools::getValue('date_to'));
if (!count($order_invoice_collection)) {
die($this->trans('No invoice was found.', [], 'Admin.Orderscustomers.Notification'));
}
$this->generatePDF($order_invoice_collection, PDF::TEMPLATE_DELIVERY_SLIP);
}
/**
* @deprecated Since 9.0 and will be removed in 10.0
*/
public function processGenerateSupplyOrderFormPDF()
{
@trigger_error(sprintf(
'%s is deprecated since 9.0 and will be removed in 10.0.',
__METHOD__
), E_USER_DEPRECATED);
die;
}
public function generateDeliverySlipPDFByIdOrderInvoice($id_order_invoice)
{
$order_invoice = new OrderInvoice((int) $id_order_invoice);
if (!Validate::isLoadedObject($order_invoice)) {
throw new PrestaShopException('Can\'t load Order Invoice object');
}
$this->generatePDF($order_invoice, PDF::TEMPLATE_DELIVERY_SLIP);
}
public function generateInvoicePDFByIdOrderInvoice($id_order_invoice)
{
$order_invoice = new OrderInvoice((int) $id_order_invoice);
if (!Validate::isLoadedObject($order_invoice)) {
die($this->trans('The order invoice cannot be found within your database.', [], 'Admin.Orderscustomers.Notification'));
}
Hook::exec('actionPDFInvoiceRender', ['order_invoice_list' => [$order_invoice]]);
$this->generatePDF($order_invoice, PDF::TEMPLATE_INVOICE);
}
public function generatePDF($object, $template)
{
$pdf = new PDF($object, $template, Context::getContext()->smarty);
$pdf->render();
}
}

View File

@@ -0,0 +1,221 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property QuickAccess $object
*/
class AdminQuickAccessesControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'quick_access';
$this->className = 'QuickAccess';
$this->lang = true;
$this->addRowAction('edit');
$this->addRowAction('delete');
parent::__construct();
if (!Tools::getValue('realedit')) {
$this->deleted = false;
}
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
'icon' => 'icon-trash',
],
];
$this->fields_list = [
'id_quick_access' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
],
'link' => [
'title' => $this->trans('Link', [], 'Admin.Navigation.Header'),
],
'new_window' => [
'title' => $this->trans('New window', [], 'Admin.Navigation.Header'),
'align' => 'center',
'type' => 'bool',
'active' => 'new_window',
'class' => 'fixed-width-sm',
],
];
$this->fields_form = [
'legend' => [
'title' => $this->trans('Quick Access menu', [], 'Admin.Navigation.Header'),
'icon' => 'icon-align-justify',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'lang' => true,
'maxlength' => 32,
'required' => true,
'hint' => $this->trans('Forbidden characters:', [], 'Admin.Notifications.Info') . ' &lt;&gt;{}',
],
[
'type' => 'text',
'label' => $this->trans('URL', [], 'Admin.Global'),
'name' => 'link',
'maxlength' => 128,
'required' => true,
'hint' => $this->trans('If it\'s a URL that comes from your back office, you MUST remove the security token.', [], 'Admin.Navigation.Header'),
],
[
'type' => 'switch',
'label' => $this->trans('Open in new window', [], 'Admin.Navigation.Header'),
'name' => 'new_window',
'required' => false,
'values' => [
[
'id' => 'new_window_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'new_window_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
}
public function getTabSlug()
{
return 'ROLE_MOD_TAB_ADMINACCESS_';
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_quick_access'] = [
'href' => self::$currentIndex . '&addquick_access&token=' . $this->token,
'desc' => $this->trans('Add new quick access', [], 'Admin.Navigation.Header'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function initProcess()
{
if ((isset($_GET['new_window' . $this->table]) || isset($_GET['new_window'])) && Tools::getValue($this->identifier)) {
if ($this->access('edit')) {
$this->action = 'newWindow';
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
}
parent::initProcess();
}
public function getQuickAccessesList()
{
$links = QuickAccess::getQuickAccessesWithToken($this->context->language->id, (int) $this->context->employee->id);
return json_encode($links);
}
public function addQuickLink()
{
if (empty($this->className)) {
return false;
}
$this->validateRules();
if (count($this->errors) <= 0) {
$this->object = new $this->className();
$this->copyFromPost($this->object, $this->table);
$exists = Db::getInstance()->getValue('SELECT id_quick_access FROM ' . _DB_PREFIX_ . 'quick_access WHERE link = "' . pSQL($this->object->link) . '"');
if ($exists) {
return true;
}
$this->beforeAdd($this->object);
if (method_exists($this->object, 'add') && !$this->object->add()) {
$this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error') .
' <b>' . $this->table . ' (' . Db::getInstance()->getMsgError() . ')</b>';
} elseif (($_POST[$this->identifier] = $this->object->id) && $this->postImage($this->object->id) && empty($this->errors) && $this->_redirect) {
// voluntary do affectation here
PrestaShopLogger::addLog($this->trans('%class_name% addition', ['%class_name%' => $this->className], 'Admin.Advparameters.Feature'), 1, null, $this->className, (int) $this->object->id, true, (int) $this->context->employee->id);
$this->afterAdd($this->object);
}
}
$this->errors = array_unique($this->errors);
if (!empty($this->errors)) {
$this->errors['has_errors'] = true;
$this->ajaxRender(json_encode($this->errors));
return false;
}
return $this->getQuickAccessesList();
}
public function processDelete()
{
parent::processDelete();
return $this->getQuickAccessesList();
}
public function ajaxProcessGetUrl()
{
if (Tools::strtolower(Tools::getValue('method')) === 'add') {
$params['new_window'] = 0;
$params['name_' . (int) Configuration::get('PS_LANG_DEFAULT')] = Tools::getValue('name');
$params['link'] = Tools::getValue('url');
$params['submitAddquick_access'] = 1;
unset($_POST['name']);
$_POST = array_merge($_POST, $params);
die($this->addQuickLink());
} elseif (Tools::strtolower(Tools::getValue('method')) === 'remove') {
$params['deletequick_access'] = 1;
$_POST = array_merge($_POST, $params);
die($this->processDelete());
}
}
public function processNewWindow()
{
if (Validate::isLoadedObject($object = $this->loadObject())) {
/** @var QuickAccess $object */
if ($object->toggleNewWindow()) {
$this->redirect_after = self::$currentIndex . '&conf=5&token=' . $this->token;
} else {
$this->errors[] = $this->trans('An error occurred while updating new window property.', [], 'Admin.Navigation.Notification');
}
} else {
$this->errors[] = $this->trans('An error occurred while updating the new window property for this object.', [], 'Admin.Navigation.Notification') .
' <b>' . $this->table . '</b> ' .
$this->trans('(cannot load object)', [], 'Admin.Notifications.Error');
}
return $object;
}
}

View File

@@ -0,0 +1,287 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property OrderReturn $object
*/
class AdminReturnControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'order_return';
$this->className = 'OrderReturn';
$this->colorOnBackground = true;
parent::__construct();
$this->_select = 'ors.color, orsl.`name`, o.`id_shop`';
$this->_join = 'LEFT JOIN ' . _DB_PREFIX_ . 'order_return_state ors ON (ors.`id_order_return_state` = a.`state`)';
$this->_join .= 'LEFT JOIN ' . _DB_PREFIX_ . 'order_return_state_lang orsl ON (orsl.`id_order_return_state` = a.`state` AND orsl.`id_lang` = ' . (int) $this->context->language->id . ')';
$this->_join .= ' LEFT JOIN ' . _DB_PREFIX_ . 'orders o ON (o.`id_order` = a.`id_order`)';
$this->_orderBy = 'id_order_return';
$this->_orderWay = 'DESC';
$this->fields_list = [
'id_order_return' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'align' => 'center', 'width' => 25],
'id_order' => ['title' => $this->trans('Order ID', [], 'Admin.Orderscustomers.Feature'), 'width' => 100, 'align' => 'center', 'filter_key' => 'a!id_order', 'havingFilter' => true],
'name' => ['title' => $this->trans('Status', [], 'Admin.Global'), 'color' => 'color', 'width' => 'auto', 'align' => 'left'],
'date_add' => ['title' => $this->trans('Date issued', [], 'Admin.Orderscustomers.Feature'), 'width' => 150, 'type' => 'date', 'align' => 'right', 'filter_key' => 'a!date_add'],
];
$this->fields_options = [
'general' => [
'title' => $this->trans('Merchandise return (RMA) options', [], 'Admin.Orderscustomers.Feature'),
'fields' => [
'PS_ORDER_RETURN' => [
'title' => $this->trans('Enable returns', [], 'Admin.Orderscustomers.Feature'),
'desc' => $this->trans('Would you like to allow merchandise returns in your shop?', [], 'Admin.Orderscustomers.Help'),
'cast' => 'intval', 'type' => 'bool', ],
'PS_ORDER_RETURN_NB_DAYS' => [
'title' => $this->trans('Time limit of validity', [], 'Admin.Orderscustomers.Feature'),
'desc' => $this->trans('How many days after the delivery date does the customer have to return a product?', [], 'Admin.Orderscustomers.Help'),
'cast' => 'intval',
'type' => 'text',
'size' => '2', ],
'PS_RETURN_PREFIX' => [
'title' => $this->trans('Returns prefix', [], 'Admin.Orderscustomers.Feature'),
'desc' => $this->trans('Prefix used for return name (e.g. RE00001).', [], 'Admin.Orderscustomers.Help'),
'size' => 6,
'type' => 'textLang',
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
];
$this->_where = Shop::addSqlRestriction(false, 'o');
$this->_use_found_rows = false;
}
public function renderForm()
{
$this->fields_form = [
'legend' => [
'title' => $this->trans('Return Merchandise Authorization (RMA)', [], 'Admin.Orderscustomers.Feature'),
'icon' => 'icon-clipboard',
],
'input' => [
[
'type' => 'hidden',
'name' => 'id_order',
],
[
'type' => 'hidden',
'name' => 'id_customer',
],
[
'type' => 'text_customer',
'label' => $this->trans('Customer', [], 'Admin.Global'),
'name' => '',
'size' => '',
'required' => false,
],
[
'type' => 'text_order',
'label' => $this->trans('Order', [], 'Admin.Global'),
'name' => '',
'size' => '',
'required' => false,
],
[
'type' => 'free',
'label' => $this->trans('Customer explanation', [], 'Admin.Orderscustomers.Feature'),
'name' => 'question',
'size' => '',
'required' => false,
],
[
'type' => 'select',
'label' => $this->trans('Status', [], 'Admin.Global'),
'name' => 'state',
'required' => false,
'options' => [
'query' => OrderReturnState::getOrderReturnStates($this->context->language->id),
'id' => 'id_order_return_state',
'name' => 'name',
],
'desc' => $this->trans('Merchandise return (RMA) status.', [], 'Admin.Orderscustomers.Help'),
],
[
'type' => 'list_products',
'label' => $this->trans('Products', [], 'Admin.Global'),
'name' => '',
'size' => '',
'required' => false,
'desc' => $this->trans('List of products in return package.', [], 'Admin.Orderscustomers.Help'),
],
[
'type' => 'pdf_order_return',
'label' => $this->trans('Returns form', [], 'Admin.Orderscustomers.Feature'),
'name' => '',
'size' => '',
'required' => false,
'desc' => $this->trans('The link is only available after validation and before the parcel gets delivered.', [], 'Admin.Orderscustomers.Help'),
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
'buttons' => [
'save-and-stay' => [
'title' => $this->trans('Save and stay', [], 'Admin.Actions'),
'name' => 'submitAdd' . $this->table . 'AndStay',
'type' => 'submit',
'class' => 'btn btn-default pull-right',
'icon' => 'process-icon-save',
],
],
];
$order = new Order($this->object->id_order);
$quantity_displayed = [];
// Customized products */
if ($returned_customizations = OrderReturn::getReturnedCustomizedProducts((int) $this->object->id_order)) {
foreach ($returned_customizations as $returned_customization) {
$quantity_displayed[(int) $returned_customization['id_order_detail']] = isset($quantity_displayed[(int) $returned_customization['id_order_detail']]) ? $quantity_displayed[(int) $returned_customization['id_order_detail']] + (int) $returned_customization['product_quantity'] : (int) $returned_customization['product_quantity'];
}
}
// Classic products
$products = OrderReturn::getOrdersReturnProducts($this->object->id, $order);
// Prepare customer explanation for display
$this->object->question = '<span class="normal-text">' . nl2br($this->object->question) . '</span>';
$parameters = ['vieworder' => 1, 'id_order' => (int) $order->id];
$orderUrl = $this->context->link->getAdminLink('AdminOrders', true, [], $parameters);
$this->tpl_form_vars = [
'customer' => new Customer($this->object->id_customer),
'url_customer' => $this->context->link->getAdminLink('AdminCustomers', true, [], [
'id_customer' => $this->object->id_customer,
'viewcustomer' => 1,
]),
'text_order' => $this->trans(
'Order #%id% from %date%',
[
'%id%' => $order->id,
'%date%' => Tools::displayDate($order->date_upd),
],
'Admin.Orderscustomers.Feature'
),
'url_order' => $orderUrl,
'picture_folder' => _THEME_PROD_PIC_DIR_,
'returnedCustomizations' => $returned_customizations,
'customizedDatas' => Product::getAllCustomizedDatas((int) $order->id_cart),
'products' => $products,
'quantityDisplayed' => $quantity_displayed,
'id_order_return' => $this->object->id,
'state_order_return' => $this->object->state,
];
return parent::renderForm();
}
public function initToolbar()
{
// If display list, we don't want the "add" button
if (!$this->display || $this->display == 'list') {
return;
} elseif ($this->display != 'options') {
$this->toolbar_btn['save-and-stay'] = [
'short' => 'SaveAndStay',
'href' => '#',
'desc' => $this->trans('Save and stay', [], 'Admin.Actions'),
'force_desc' => true,
];
}
parent::initToolbar();
}
public function postProcess()
{
$this->context = Context::getContext();
if (Tools::isSubmit('deleteorder_return_detail')) {
if ($this->access('delete')) {
if (($id_order_detail = (int) Tools::getValue('id_order_detail')) && Validate::isUnsignedId($id_order_detail)) {
if (($id_order_return = (int) Tools::getValue('id_order_return')) && Validate::isUnsignedId($id_order_return)) {
$orderReturn = new OrderReturn($id_order_return);
if (!Validate::isLoadedObject($orderReturn)) {
throw new PrestaShopException(sprintf('Order return with ID "%s" could not be loaded.', $id_order_return));
}
if ((int) $orderReturn->countProduct() > 1) {
if (OrderReturn::deleteOrderReturnDetail($id_order_return, $id_order_detail, (int) Tools::getValue('id_customization', 0))) {
Tools::redirectAdmin(self::$currentIndex . '&updateorder_return&id_order_return=' . $id_order_return . '&conf=4&token=' . $this->token);
} else {
$this->errors[] = $this->trans('An error occurred while deleting the details of your order return.', [], 'Admin.Orderscustomers.Notification');
}
} else {
$this->errors[] = $this->trans('You need at least one product.', [], 'Admin.Orderscustomers.Notification');
}
} else {
$this->errors[] = $this->trans('The order return is invalid.', [], 'Admin.Orderscustomers.Notification');
}
} else {
$this->errors[] = $this->trans('The order return content is invalid.', [], 'Admin.Orderscustomers.Notification');
}
} else {
$this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('submitAddorder_return') || Tools::isSubmit('submitAddorder_returnAndStay')) {
if ($this->access('edit')) {
if (($id_order_return = (int) Tools::getValue('id_order_return')) && Validate::isUnsignedId($id_order_return)) {
$orderReturn = new OrderReturn($id_order_return);
$order = new Order($orderReturn->id_order);
$customer = new Customer($orderReturn->id_customer);
$orderLanguage = new Language((int) $order->id_lang);
$orderReturn->state = (int) Tools::getValue('state');
if ($orderReturn->save()) {
$orderReturnState = new OrderReturnState($orderReturn->state);
$vars = [
'{lastname}' => $customer->lastname,
'{firstname}' => $customer->firstname,
'{id_order_return}' => $id_order_return,
'{state_order_return}' => (isset($orderReturnState->name[(int) $order->id_lang]) ? $orderReturnState->name[(int) $order->id_lang] : $orderReturnState->name[(int) Configuration::get('PS_LANG_DEFAULT')]),
];
Mail::Send(
(int) $order->id_lang,
'order_return_state',
$this->trans(
'Your order return status has changed',
[],
'Emails.Subject',
$orderLanguage->locale
),
$vars,
$customer->email,
$customer->firstname . ' ' . $customer->lastname,
null,
null,
null,
null,
_PS_MAIL_DIR_,
true,
(int) $order->id_shop
);
if (Tools::isSubmit('submitAddorder_returnAndStay')) {
Tools::redirectAdmin(self::$currentIndex . '&conf=4&token=' . $this->token . '&updateorder_return&id_order_return=' . (int) $id_order_return);
} else {
Tools::redirectAdmin(self::$currentIndex . '&conf=4&token=' . $this->token);
}
}
} else {
$this->errors[] = $this->trans('No order return ID has been specified.', [], 'Admin.Orderscustomers.Notification');
}
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
}
parent::postProcess();
}
}

View File

@@ -0,0 +1,479 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Alias $object
*/
class AdminSearchConfControllerCore extends AdminController
{
/** @var bool */
protected $toolbar_scroll = false;
public function __construct()
{
$this->bootstrap = true;
$this->table = 'alias';
$this->className = 'Alias';
$this->lang = false;
parent::__construct();
$params = [
'action' => 'searchCron',
'ajax' => 1,
'full' => 1,
'token' => $this->getTokenForCron(),
];
if (Shop::getContext() == Shop::CONTEXT_SHOP) {
$params['id_shop'] = (int) Context::getContext()->shop->id;
}
// Search options
$cron_url = Context::getContext()->link->getAdminLink(
'AdminSearch',
false,
[],
$params
);
list($total, $indexed) = Db::getInstance()->getRow('SELECT COUNT(*) as "0", SUM(product_shop.indexed) as "1" FROM ' . _DB_PREFIX_ . 'product p ' . Shop::addSqlAssociation('product', 'p') . ' WHERE product_shop.`visibility` IN ("both", "search") AND product_shop.`active` = 1');
$this->fields_options = [
'indexation' => [
'title' => $this->trans('Indexing', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-cogs',
'info' => '<p>
' . $this->trans('The "indexed" products have been analyzed by PrestaShop and will appear in the results of a front office search.', [], 'Admin.Shopparameters.Feature') . '<br />
' . $this->trans('Indexed products', [], 'Admin.Shopparameters.Feature') . ' <strong>' . (int) $indexed . ' / ' . (int) $total . '</strong>.
</p>
<p>
' . $this->trans('Building the product index may take a few minutes.', [], 'Admin.Shopparameters.Feature') . '
' . $this->trans('If your server stops before the process ends, you can resume the indexing by clicking "%add_missing_products_label%".', ['%add_missing_products_label%' => $this->trans('Add missing products to the index', [], 'Admin.Shopparameters.Feature')], 'Admin.Shopparameters.Feature') . '
</p>
<a href="' . Context::getContext()->link->getAdminLink('AdminSearch', false) . '&action=searchCron&ajax=1&token=' . $this->getTokenForCron() . '&amp;redirect=1' . (Shop::getContext() == Shop::CONTEXT_SHOP ? '&id_shop=' . (int) Context::getContext()->shop->id : '') . '" class="btn-link">
<i class="icon-external-link-sign"></i>
' . $this->trans('Add missing products to the index', [], 'Admin.Shopparameters.Feature') . '
</a><br />
<a href="' . Context::getContext()->link->getAdminLink('AdminSearch', false) . '&action=searchCron&ajax=1&full=1&amp;token=' . $this->getTokenForCron() . '&amp;redirect=1' . (Shop::getContext() == Shop::CONTEXT_SHOP ? '&id_shop=' . (int) Context::getContext()->shop->id : '') . '" class="btn-link">
<i class="icon-external-link-sign"></i>
' . $this->trans('Re-build the entire index', [], 'Admin.Shopparameters.Feature') . '
</a><br /><br />
<p>
' . $this->trans('You can set a cron job that will rebuild your index using the following URL:', [], 'Admin.Shopparameters.Feature') . '<br />
<a href="' . Tools::safeOutput($cron_url) . '">
<i class="icon-external-link-sign"></i>
' . Tools::safeOutput($cron_url) . '
</a>
</p><br />',
'fields' => [
'PS_SEARCH_INDEXATION' => [
'title' => $this->trans('Indexing', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isBool',
'type' => 'bool',
'cast' => 'intval',
'desc' => $this->trans('Enable the automatic indexing of products. If you enable this feature, the products will be indexed in the search automatically when they are saved. If the feature is disabled, you will have to index products manually by using the links provided in the field set.', [], 'Admin.Shopparameters.Help'),
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
'search' => [
'title' => $this->trans('Search', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-search',
'fields' => [
'PS_SEARCH_START' => [
'title' => $this->trans('Search within word', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isBool',
'cast' => 'intval',
'type' => 'bool',
'desc' => $this->trans(
'By default, to search for “blouse”, you have to enter “blous”, “blo”, etc (beginning of the word) but not “lous” (within the word).',
[],
'Admin.Shopparameters.Help'
) . '<br/>' .
$this->trans(
'With this option enabled, it also gives the good result if you search for “lous”, “ouse”, or anything contained in the word.',
[],
'Admin.Shopparameters.Help'
),
'hint' => [
$this->trans(
'Enable search within a whole word, rather than from its beginning only.',
[],
'Admin.Shopparameters.Help'
),
$this->trans(
'It checks if the searched term is contained in the indexed word. This may be resource-consuming.',
[],
'Admin.Shopparameters.Help'
),
],
],
'PS_SEARCH_END' => [
'title' => $this->trans('Search exact end match', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isBool',
'cast' => 'intval',
'type' => 'bool',
'desc' => $this->trans(
'By default, if you search "book", you will have "book", "bookcase" and "bookend".',
[],
'Admin.Shopparameters.Help'
) . '<br/>' .
$this->trans(
'With this option enabled, it only gives one result “book”, as exact end of the indexed word is matching.',
[],
'Admin.Shopparameters.Help'
),
'hint' => [
$this->trans(
'Enable more precise search with the end of the word.',
[],
'Admin.Shopparameters.Help'
),
$this->trans(
'It checks if the searched term is the exact end of the indexed word.',
[],
'Admin.Shopparameters.Help'
),
],
],
'PS_SEARCH_FUZZY' => [
'title' => $this->trans('Fuzzy search', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isBool',
'cast' => 'intval',
'type' => 'bool',
'desc' => $this->trans(
'By default, the fuzzy search is enabled. It means spelling errors are allowed, e.g. you can search for "bird" with words like "burd", "bard" or "beerd".',
[],
'Admin.Shopparameters.Help'
) . '<br/>' .
$this->trans(
'Disabling this option will require exact spelling for the search to match results.',
[],
'Admin.Shopparameters.Help'
),
'hint' => [
$this->trans(
'Enable approximate string matching.',
[],
'Admin.Shopparameters.Help'
),
],
],
'PS_SEARCH_FUZZY_MAX_LOOP' => [
'title' => $this->trans(
'Maximum approximate words allowed by fuzzy search',
[],
'Admin.Shopparameters.Feature'
),
'hint' => $this->trans(
'Note that this option is resource-consuming: the more you search, the longer it takes.',
[],
'Admin.Shopparameters.Help'
),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_FUZZY_MAX_DIFFERENCE' => [
'title' => $this->trans(
'Maximum acceptable word difference',
[],
'Admin.Shopparameters.Feature'
),
'desc' => $this->trans(
'This option defines how much different can the alternative words found by fuzzy search be. Or, how many characters can be different/missing/added. The default value is 5.',
[],
'Admin.Shopparameters.Help'
),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_MAX_WORD_LENGTH' => [
'title' => $this->trans(
'Maximum word length (in characters)',
[],
'Admin.Shopparameters.Feature'
),
'hint' => $this->trans(
'Only words fewer or equal to this maximum length will be searched.',
[],
'Admin.Shopparameters.Help'
),
'desc' => $this->trans(
'This parameter will only be used if the fuzzy search is activated: the lower the value, the more tolerant your search will be.',
[],
'Admin.Shopparameters.Help'
),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
'required' => true,
],
'PS_SEARCH_MINWORDLEN' => [
'title' => $this->trans(
'Minimum word length (in characters)',
[],
'Admin.Shopparameters.Feature'
),
'hint' => $this->trans(
'Only words this size or larger will be indexed.',
[],
'Admin.Shopparameters.Help'
),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_BLACKLIST' => [
'title' => $this->trans('Blacklisted words', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isGenericName',
'hint' => $this->trans(
'Please enter the index words separated by a "|".',
[],
'Admin.Shopparameters.Help'
),
'type' => 'textareaLang',
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
'relevance' => [
'title' => $this->trans('Weight', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-cogs',
'info' => $this->trans(
'The "weight" represents its importance and relevance for the ranking of the products when completing a new search.',
[],
'Admin.Shopparameters.Feature'
) . '<br />
' . $this->trans(
'A word with a weight of eight will have four times more value than a word with a weight of two.',
[],
'Admin.Shopparameters.Feature'
) . '<br /><br />
' . $this->trans(
'We advise you to set a greater weight for words which appear in the name or reference of a product. This will allow the search results to be as precise and relevant as possible.',
[],
'Admin.Shopparameters.Feature'
) . '<br /><br />
' . $this->trans(
'Setting a weight to 0 will exclude that field from search index. Re-build of the entire index is required when changing to or from 0',
[],
'Admin.Shopparameters.Feature'
),
'fields' => [
'PS_SEARCH_WEIGHT_PNAME' => [
'title' => $this->trans('Product name weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_REF' => [
'title' => $this->trans('Reference weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_SHORTDESC' => [
'title' => $this->trans(
'Short description weight',
[],
'Admin.Shopparameters.Feature'
),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_DESC' => [
'title' => $this->trans('Description weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_CNAME' => [
'title' => $this->trans('Category weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_MNAME' => [
'title' => $this->trans('Brand weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_TAG' => [
'title' => $this->trans('Tags weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_ATTRIBUTE' => [
'title' => $this->trans('Attributes weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
'PS_SEARCH_WEIGHT_FEATURE' => [
'title' => $this->trans('Features weight', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isUnsignedInt',
'type' => 'text',
'cast' => 'intval',
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
];
}
public function initPageHeaderToolbar()
{
if (empty($this->display) || $this->display == 'list') {
$this->page_header_toolbar_btn['new_alias'] = [
'href' => self::$currentIndex . '&addalias&token=' . $this->token,
'desc' => $this->trans('Add new alias', [], 'Admin.Shopparameters.Feature'),
'icon' => 'process-icon-new',
];
}
$this->identifier_name = 'alias';
parent::initPageHeaderToolbar();
if ($this->can_import) {
$this->toolbar_btn['import'] = [
'href' => $this->context->link->getAdminLink('AdminImport', true) . '&import_type=alias',
'desc' => $this->trans('Import', [], 'Admin.Actions'),
];
}
}
public function initProcess()
{
parent::initProcess();
// This is a composite page, we don't want the "options" display mode
if ($this->display == 'options') {
$this->display = '';
}
}
/**
* Function used to render the options for this controller.
*
* @return string|void
*/
public function renderOptions()
{
if ($this->fields_options && is_array($this->fields_options)) {
$helper = new HelperOptions();
$this->setHelperDisplay($helper);
$helper->toolbar_scroll = true;
$helper->toolbar_btn = ['save' => [
'href' => '#',
'desc' => $this->trans('Save', [], 'Admin.Actions'),
]];
$helper->id = $this->id;
$helper->tpl_vars = $this->tpl_option_vars;
$options = $helper->generateOptions($this->fields_options);
return $options;
}
}
public function renderForm()
{
$this->fields_form = [
'legend' => [
'title' => $this->trans('Aliases', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-search',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Alias', [], 'Admin.Shopparameters.Feature'),
'name' => 'alias',
'required' => true,
'hint' => [
$this->trans('Enter each alias separated by a comma (e.g. \'prestshop,preztashop,prestasohp\').', [], 'Admin.Shopparameters.Help'),
$this->trans('Forbidden characters: &lt;&gt;{}', [], 'Admin.Shopparameters.Help'),
],
],
[
'type' => 'text',
'label' => $this->trans('Result', [], 'Admin.Shopparameters.Feature'),
'name' => 'search',
'required' => true,
'hint' => $this->trans('Search this word instead.', [], 'Admin.Shopparameters.Help'),
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
$this->fields_value = ['alias' => $this->object->getAliases()];
return parent::renderForm();
}
public function processSave()
{
$search = (string) Tools::getValue('search');
$string = (string) Tools::getValue('alias');
$aliases = explode(',', $string);
if (empty($search) || empty($string)) {
$this->errors[] = $this->trans('Aliases and results are both required.', [], 'Admin.Shopparameters.Notification');
}
if (!Validate::isValidSearch($search)) {
$this->errors[] = Tools::safeOutput($search) . ' ' . $this->trans('Is not a valid result', [], 'Admin.Shopparameters.Notification');
}
foreach ($aliases as $alias) {
if (!Validate::isValidSearch($alias)) {
$this->errors[] = Tools::safeOutput($alias) . ' ' . $this->trans('Is not a valid alias', [], 'Admin.Shopparameters.Notification');
}
}
if (!count($this->errors)) {
// Search existing aliases
$alias = new Alias();
$alias->search = trim($search);
$existingAliases = explode(',', $alias->getAliases());
// New alias
$newAliases = array_diff($aliases, $existingAliases);
foreach ($newAliases as $alias) {
$obj = new Alias(null, trim($alias), trim($search));
$obj->save();
}
// Removed alias
$removedAliases = array_diff($existingAliases, $aliases);
foreach ($removedAliases as $alias) {
$obj = new Alias(null, trim($alias), trim($search));
$obj->delete();
}
}
if (empty($this->errors)) {
if (Tools::getValue('id_alias')) {
$this->confirmations[] = $this->trans('Update successful', [], 'Admin.Notifications.Success');
} else {
$this->confirmations[] = $this->trans('Successful creation', [], 'Admin.Notifications.Success');
}
}
}
/**
* Retrieve a part of the cookie key for token check. (needs to be static).
*
* @return string Token
*/
private function getTokenForCron()
{
return substr(
_COOKIE_KEY_,
AdminSearchController::TOKEN_CHECK_START_POS,
AdminSearchController::TOKEN_CHECK_LENGTH
);
}
}

View File

@@ -0,0 +1,570 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Core\Search\SearchPanel;
use PrestaShop\PrestaShop\Core\Search\SearchPanelInterface;
use PrestaShop\PrestaShop\Core\Security\Permission;
class AdminSearchControllerCore extends AdminController
{
public const TOKEN_CHECK_START_POS = 34;
public const TOKEN_CHECK_LENGTH = 8;
/**
* @var string
*/
public $query;
public function __construct()
{
$this->bootstrap = true;
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function init()
{
if ($this->isCronTask()
&& substr(
_COOKIE_KEY_,
static::TOKEN_CHECK_START_POS,
static::TOKEN_CHECK_LENGTH
) === Tools::getValue('token')
) {
$this->setAllowAnonymous(true);
}
parent::init();
}
public function getTabSlug()
{
return 'ROLE_MOD_TAB_ADMINSEARCHCONF_';
}
public function postProcess()
{
$this->context = Context::getContext();
$this->query = trim(Tools::getValue('bo_query'));
$searchType = (int) Tools::getValue('bo_search_type');
/* 1.6 code compatibility, as we use HelperList, we need to handle click to go to product */
$action = Tools::getValue('action');
if ($action == 'redirectToProduct') {
$id_product = (int) Tools::getValue('id_product');
if (Tools::getIsset('statusproduct')) {
$product = new Product($id_product);
}
if (isset($product) && Validate::isLoadedObject($product)) {
if ($product->toggleStatus()) {
$this->confirmations[] = $this->trans('The status has been updated successfully.', [], 'Admin.Notifications.Success');
} else {
$this->errors[] = $this->trans('An error occurred while updating the status.', [], 'Admin.Notifications.Error');
}
} else {
$link = $this->context->link->getAdminLink('AdminProducts', false, ['id_product' => $id_product]);
Tools::redirectAdmin($link);
}
}
/* Handle empty search field */
if (!empty($this->query)) {
if (!$searchType && strlen($this->query) > 1) {
$this->searchFeatures();
}
/* Product research */
if (!$searchType || $searchType == 1) {
/* Handle product ID */
if ($searchType == 1 && Validate::isUnsignedInt((int) $this->query)) {
$product = new Product((int) $this->query);
if (Validate::isLoadedObject($product)) {
Tools::redirectAdmin($this->context->link->getAdminLink('AdminProducts', true, ['id_product' => (int) $product->id, 'updateproduct' => '1']));
}
}
/* Normal catalog search */
$this->searchCatalog();
}
/* Customer */
if (!$searchType || $searchType == 2 || $searchType == 6) {
if (!$searchType || $searchType == 2) {
/* Handle customer ID */
if ($searchType && Validate::isUnsignedInt((int) $this->query)) {
$customer = new Customer((int) $this->query);
if (Validate::isLoadedObject($customer)) {
Tools::redirectAdmin($this->context->link->getAdminLink(
'AdminCustomers',
true,
[],
[
'id_customer' => $customer->id,
'viewcustomer' => 1,
]
));
}
}
/* Normal customer search */
$this->searchCustomer();
}
if ($searchType == 6) {
$this->searchIP();
}
}
/* Order */
if (!$searchType || $searchType == 3) {
if (Validate::isUnsignedInt(trim($this->query)) && Validate::isLoadedObject($order = new Order((int) $this->query))) {
if ($searchType == 3) {
Tools::redirectAdmin($this->context->link->getAdminLink('AdminOrders', true, [], ['id_order' => (int) $order->id, 'vieworder' => '1']));
} else {
$row = get_object_vars($order);
$row['id_order'] = $row['id'];
$customer = $order->getCustomer();
$row['customer'] = $customer->firstname . ' ' . $customer->lastname;
$order_state = $order->getCurrentOrderState();
$row['osname'] = $order_state->name[$this->context->language->id];
$this->_list['orders'] = [$row];
}
} else {
$orders = Order::getByReference($this->query);
$nb_orders = count($orders);
if ($nb_orders == 1 && $searchType == 3) {
Tools::redirectAdmin($this->context->link->getAdminLink('AdminOrders', true, [], ['id_order' => (int) $orders[0]->id, 'vieworder' => '1']));
} elseif ($nb_orders) {
$this->_list['orders'] = [];
foreach ($orders as $order) {
/** @var Order $order */
$row = get_object_vars($order);
$row['id_order'] = $row['id'];
$customer = $order->getCustomer();
$row['customer'] = $customer->firstname . ' ' . $customer->lastname;
$order_state = $order->getCurrentOrderState();
$row['osname'] = $order_state->name[$this->context->language->id];
$this->_list['orders'][] = $row;
}
} elseif ($searchType == 3) {
$this->errors[] = $this->trans('No order was found with this ID:', [], 'Admin.Orderscustomers.Notification') . ' ' . Tools::htmlentitiesUTF8($this->query);
}
}
}
/* Invoices */
if ($searchType == 4) {
if ($invoice = OrderInvoice::getInvoiceByNumber($this->query)) {
Tools::redirectAdmin(
$this->context->link->getAdminLink(
'AdminPdf',
true,
[
'route' => 'admin_orders_generate_invoice_pdf',
'orderId' => (int) $invoice->id_order,
]
)
);
}
$this->errors[] = $this->trans('No invoice was found with this ID:', [], 'Admin.Orderscustomers.Notification') . ' ' . Tools::htmlentitiesUTF8($this->query);
}
/* Cart */
if ($searchType == 5) {
if (Validate::isUnsignedInt((int) $this->query) && Validate::isLoadedObject($cart = new Cart((int) $this->query))) {
Tools::redirectAdmin($this->context->link->getAdminLink('AdminOrders', true, [], ['id_cart' => (int) $cart->id, 'viewcart' => 1]));
}
$this->errors[] = $this->trans('No cart was found with this ID:', [], 'Admin.Orderscustomers.Notification') . ' ' . Tools::htmlentitiesUTF8($this->query);
}
/* IP */
// 6 - but it is included in the customer block
/* Module search */
if (!$searchType || $searchType == 7) {
/* Handle module name */
if ($searchType == 7 && Validate::isModuleName($this->query) && ($module = Module::getInstanceByName($this->query)) && Validate::isLoadedObject($module)) {
// @todo redirect directly to module manager with search prefilled, because this won't work anymore
Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, [], ['tab_module' => $module->tab, 'module_name' => $module->name, 'anchor' => ucfirst($module->name)]));
}
/* Normal catalog search */
$this->searchModule();
}
}
$this->display = 'view';
}
public function searchIP()
{
if (!ip2long(trim($this->query))) {
$this->errors[] = $this->trans('This is not a valid IP address:', [], 'Admin.Shopparameters.Notification') . ' ' . Tools::htmlentitiesUTF8($this->query);
return;
}
$this->_list['customers'] = Customer::searchByIp($this->query);
}
/**
* Search a specific string in the products and categories.
*/
public function searchCatalog()
{
$this->context = Context::getContext();
$this->_list['products'] = Product::searchByName($this->context->language->id, $this->query);
$this->_list['categories'] = Category::searchByName($this->context->language->id, $this->query);
}
/**
* Search a specific name in the customers.
*/
public function searchCustomer()
{
$this->_list['customers'] = Customer::searchByName($this->query);
}
public function searchModule()
{
$this->_list['modules'] = [];
$all_modules = Module::getModulesOnDisk(true, Context::getContext()->employee->id);
foreach ($all_modules as $module) {
if (
(isset($module->name) && stripos($module->name, $this->query) !== false)
|| (isset($module->displayName) && stripos($module->displayName, $this->query) !== false)
|| (isset($module->description) && stripos($module->description, $this->query) !== false)
) {
$module->linkto = Context::getContext()->link->getAdminLink('ADMINMODULESSF') . '&find=' . $module->name;
$this->_list['modules'][] = $module;
}
}
}
/**
* Search a feature in all store.
*/
public function searchFeatures()
{
$this->_list['features'] = [];
$sql = sprintf(
'SELECT class_name, name, route_name FROM %stab t INNER JOIN %stab_lang tl ON (t.id_tab = tl.id_tab AND tl.id_lang = %d) WHERE active = 1',
_DB_PREFIX_,
_DB_PREFIX_,
(int) $this->context->employee->id_lang
);
$result = Db::getInstance()->executeS($sql);
$mainControllers = Dispatcher::getControllers([
_PS_ADMIN_CONTROLLER_DIR_,
_PS_OVERRIDE_DIR_ . 'controllers/admin/',
]);
foreach ($result as $row) {
// Search pages with the query need
if (stripos($row['name'], $this->query) === false) {
continue;
}
// Remove pages without access
if (!Access::isGranted(Permission::PREFIX_TAB . strtoupper($row['class_name']) . '_READ', $this->context->employee->id_profile)) {
continue;
}
$tab = Tab::getInstanceFromClassName($row['class_name']);
if (!Validate::isLoadedObject($tab)) {
continue;
}
// Check if it's not a parent tab
if (!isset($mainControllers[strtolower($row['class_name'])])) {
$tabs = Tab::getTabs(Context::getContext()->language->id, $tab->id);
if (isset($tabs[0])) {
continue;
}
}
$sfRouteParams = (!empty($row['route_name'])) ? ['route' => $row['route_name']] : [];
$params = ['bo_query' => $this->query];
$this->_list['features'][$row['name']][] = [
'link' => Context::getContext()->link->getAdminLink((string) $row['class_name'], true, $sfRouteParams, $params),
];
}
}
protected function initOrderList()
{
$this->fields_list['orders'] = [
'reference' => ['title' => $this->trans('Reference', [], 'Admin.Global'), 'align' => 'center', 'width' => 65],
'id_order' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'align' => 'center', 'width' => 25],
'customer' => ['title' => $this->trans('Customer', [], 'Admin.Global')],
'total_paid_tax_incl' => ['title' => $this->trans('Total', [], 'Admin.Global'), 'width' => 70, 'align' => 'right', 'type' => 'price', 'currency' => true],
'payment' => ['title' => $this->trans('Payment', [], 'Admin.Global'), 'width' => 100],
'osname' => ['title' => $this->trans('Status', [], 'Admin.Global'), 'width' => 280],
'date_add' => ['title' => $this->trans('Date', [], 'Admin.Global'), 'width' => 130, 'align' => 'right', 'type' => 'datetime'],
];
}
protected function initCustomerList()
{
$genders_icon = ['default' => 'unknown.gif'];
$genders = [0 => $this->trans('?', [], 'Admin.Global')];
foreach (Gender::getGenders() as $gender) {
/* @var Gender $gender */
$genders_icon[$gender->id] = '../genders/' . (int) $gender->id . '.jpg';
$genders[$gender->id] = $gender->name;
}
$this->fields_list['customers'] = [
'id_customer' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'align' => 'center', 'width' => 25],
'id_gender' => ['title' => $this->trans('Social title', [], 'Admin.Global'), 'align' => 'center', 'icon' => $genders_icon, 'list' => $genders, 'width' => 25],
'firstname' => ['title' => $this->trans('First name', [], 'Admin.Global'), 'align' => 'left', 'width' => 150],
'lastname' => ['title' => $this->trans('Name', [], 'Admin.Global'), 'align' => 'left', 'width' => 'auto'],
'email' => ['title' => $this->trans('Email address', [], 'Admin.Global'), 'align' => 'left', 'width' => 250],
'company' => ['title' => $this->trans('Company', [], 'Admin.Global'), 'align' => 'left', 'width' => 150],
'birthday' => ['title' => $this->trans('Birth date', [], 'Admin.Global'), 'align' => 'center', 'type' => 'date', 'width' => 75],
'date_add' => ['title' => $this->trans('Registration date', [], 'Admin.Shopparameters.Feature'), 'align' => 'center', 'type' => 'date', 'width' => 75],
'orders' => ['title' => $this->trans('Orders', [], 'Admin.Global'), 'align' => 'center', 'width' => 50],
'active' => ['title' => $this->trans('Enabled', [], 'Admin.Global'), 'align' => 'center', 'active' => 'status', 'type' => 'bool', 'width' => 25],
];
}
protected function initProductList()
{
$this->show_toolbar = false;
$this->fields_list['products'] = [
'id_product' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'width' => 25],
'manufacturer_name' => ['title' => $this->trans('Brand', [], 'Admin.Global'), 'align' => 'center', 'width' => 200],
'reference' => ['title' => $this->trans('Reference', [], 'Admin.Global'), 'align' => 'center', 'width' => 150],
'name' => ['title' => $this->trans('Name', [], 'Admin.Global'), 'width' => 'auto'],
'price_tax_excl' => ['title' => $this->trans('Price (tax excl.)', [], 'Admin.Catalog.Feature'), 'align' => 'right', 'type' => 'price', 'width' => 60],
'price_tax_incl' => ['title' => $this->trans('Price (tax incl.)', [], 'Admin.Catalog.Feature'), 'align' => 'right', 'type' => 'price', 'width' => 60],
'active' => ['title' => $this->trans('Active', [], 'Admin.Global'), 'width' => 70, 'active' => 'status', 'align' => 'center', 'type' => 'bool'],
];
}
public function setMedia($isNewTheme = false)
{
parent::setMedia($isNewTheme);
$this->addJqueryPlugin('highlight');
}
/* Override because we don't want any buttons */
public function initToolbar()
{
}
public function initToolbarTitle()
{
$this->toolbar_title = $this->trans('Search results', [], 'Admin.Global');
}
public function renderView()
{
$searchedExpression = Tools::safeOutput($this->query);
$this->tpl_view_vars['query'] = $searchedExpression;
$this->tpl_view_vars['show_toolbar'] = true;
if (!count($this->errors)) {
$nb_results = 0;
foreach ($this->_list as $list) {
if ($list != false) {
$nb_results += count($list);
}
}
$this->tpl_view_vars['nb_results'] = $nb_results;
if ($this->isCountableAndNotEmpty($this->_list, 'features')) {
$this->tpl_view_vars['features'] = $this->_list['features'];
}
if ($this->isCountableAndNotEmpty($this->_list, 'categories')) {
$categories = [];
foreach ($this->_list['categories'] as $category) {
$categories[] = Tools::getPath(
$this->context->link->getAdminLink('AdminCategories', false),
$category['id_category']
);
}
$this->tpl_view_vars['categories'] = $categories;
}
if ($this->isCountableAndNotEmpty($this->_list, 'products')) {
$view = '';
$this->initProductList();
$helper = new HelperList();
$helper->shopLinkType = '';
$helper->simple_header = true;
$helper->identifier = 'id_product';
$helper->actions = ['edit'];
$helper->show_toolbar = false;
$helper->table = 'product';
/* 1.6 code compatibility, as we use HelperList, we need to handle click to go to product, a better way need to be find */
$helper->currentIndex = $this->context->link->getAdminLink('AdminSearch', false);
$helper->currentIndex .= '&action=redirectToProduct';
$query = trim(Tools::getValue('bo_query'));
$searchType = (int) Tools::getValue('bo_search_type');
if ($query) {
$helper->currentIndex .= '&bo_query=' . $query . '&bo_search_type=' . $searchType;
}
$helper->token = Tools::getAdminTokenLite('AdminSearch');
if ($this->_list['products']) {
$view = $helper->generateList($this->_list['products'], $this->fields_list['products']);
}
$this->tpl_view_vars['products'] = $view;
$this->tpl_view_vars['productsCount'] = count($this->_list['products']);
}
if ($this->isCountableAndNotEmpty($this->_list, 'customers')) {
$view = '';
$this->initCustomerList();
$helper = new HelperList();
$helper->shopLinkType = '';
$helper->simple_header = true;
$helper->identifier = 'id_customer';
$helper->actions = ['edit', 'view'];
$helper->show_toolbar = false;
$helper->table = 'customer';
$helper->currentIndex = $this->context->link->getAdminLink('AdminCustomers', false);
$helper->token = Tools::getAdminTokenLite('AdminCustomers');
foreach ($this->_list['customers'] as $key => $val) {
$this->_list['customers'][$key]['orders'] = Order::getCustomerNbOrders((int) $val['id_customer']);
}
$view = $helper->generateList($this->_list['customers'], $this->fields_list['customers']);
$this->tpl_view_vars['customers'] = $view;
$this->tpl_view_vars['customerCount'] = count($this->_list['customers']);
}
if ($this->isCountableAndNotEmpty($this->_list, 'orders')) {
$this->initOrderList();
$helper = new HelperList();
$helper->shopLinkType = '';
$helper->simple_header = true;
$helper->identifier = 'id_order';
$helper->actions = ['view'];
$helper->show_toolbar = false;
$helper->table = 'order';
$helper->currentIndex = $this->context->link->getAdminLink('AdminOrders', false);
$helper->token = Tools::getAdminTokenLite('AdminOrders');
$this->tpl_view_vars['orders'] = $helper->generateList($this->_list['orders'], $this->fields_list['orders']);
$this->tpl_view_vars['orderCount'] = count($this->_list['orders']);
}
if ($this->isCountableAndNotEmpty($this->_list, 'modules')) {
$this->tpl_view_vars['modules'] = $this->_list['modules'];
}
}
$this->getSearchPanels($searchedExpression);
return parent::renderView();
}
protected function getSearchPanels(string $searchedExpression): void
{
// Build native search panels
$searchPanels = [];
$searchPanels[] = new SearchPanel(
$this->trans('Search docs.prestashop-project.org', [], 'Admin.Navigation.Search'),
$this->trans('Go to the documentation', [], 'Admin.Navigation.Search'),
'https://docs.prestashop-project.org/welcome/',
[
'q' => $searchedExpression,
]
);
// Get additional search panels from hooks
// An array [module_name => module_output] will be returned
$alternativeSearchPanelsFromModules = Hook::exec(
'actionGetAlternativeSearchPanels',
[
'previous_search_panels' => $searchPanels,
'bo_query' => $searchedExpression,
],
null,
true
);
foreach ($alternativeSearchPanelsFromModules as $alternativeSearchPanelsFromModule) {
foreach ($alternativeSearchPanelsFromModule as $alternativeSearchPanel) {
if ($alternativeSearchPanel instanceof SearchPanelInterface) {
$searchPanels[] = $alternativeSearchPanel;
}
}
}
// Transform the search panels and inject them to the view
$this->tpl_view_vars['searchPanels'] = [];
foreach ($searchPanels as $searchPanel) {
$this->tpl_view_vars['searchPanels'][] = [
'title' => $searchPanel->getTitle(),
'button_label' => $searchPanel->getButtonLabel(),
'link' => $searchPanel->getLink(),
'is_external_link' => $searchPanel->isExternalLink(),
];
}
}
/**
* Check if key is present in array, is countable and has data.
*
* @param array $array Array
* @param string $key Key
*
* @return bool
*/
protected function isCountableAndNotEmpty(array $array, string $key)
{
return isset($array[$key])
&& is_countable($array[$key])
&& count($array[$key]);
}
/**
* Request triggering the search indexation.
*
* Kept as GET request for backward compatibility purpose, but should be modified as POST when migrated.
* NOTE the token is different for that method, check the method checkToken() for more details.
*/
public function displayAjaxSearchCron()
{
if (!Tools::getValue('id_shop')) {
Context::getContext()->shop->setContext(Shop::CONTEXT_ALL);
} else {
Context::getContext()->shop->setContext(Shop::CONTEXT_SHOP, (int) Tools::getValue('id_shop'));
}
// Considering the indexing task can be really long, we ask the PHP process to not stop before 2 hours.
ini_set('max_execution_time', '7200');
Search::indexation(Tools::getValue('full'));
/*
* If redirect parameter was specified in the URL and we have the URL the user came from,
* we will redirect him back. This is used in backoffice so the user lands back to the config form.
*
* In CRON or CLI (referer is missing), it will die here. Even if redirect is in the URL.
*/
if (Tools::getValue('redirect') && !empty($_SERVER['HTTP_REFERER'])) {
Tools::redirectAdmin($_SERVER['HTTP_REFERER'] . '&conf=4');
}
}
/**
* Check if a task is a cron task
*
* @return bool
*/
protected function isCronTask()
{
return Tools::isSubmit('action') && 'searchCron' === Tools::getValue('action');
}
}

View File

@@ -0,0 +1,919 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Core\Addon\Theme\ThemeManagerBuilder;
use PrestaShop\PrestaShop\Core\Domain\CmsPageCategory\ValueObject\CmsPageCategoryId;
use PrestaShopBundle\Utils\Tree;
/**
* @property Shop|null $object
*/
class AdminShopControllerCore extends AdminController
{
/** @var int */
public $id_shop;
/** @var int|null */
public $id_shop_group;
public function __construct()
{
$this->bootstrap = true;
$this->table = 'shop';
$this->className = 'Shop';
$this->multishop_context = Shop::CONTEXT_ALL;
parent::__construct();
$this->id_shop_group = (int) Tools::getValue('id_shop_group');
/* if $_GET['id_shop'] is transmitted, virtual url can be loaded in config.php, so we wether transmit shop_id in herfs */
if ($this->id_shop = (int) Tools::getValue('shop_id')) {
$_GET['id_shop'] = $this->id_shop;
}
$this->list_skip_actions['delete'] = [(int) Configuration::get('PS_SHOP_DEFAULT')];
$this->fields_list = [
'id_shop' => [
'title' => $this->trans('Shop ID', [], 'Admin.Shopparameters.Feature'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Store name', [], 'Admin.Shopparameters.Feature'),
'filter_key' => 'a!name',
'width' => 200,
],
'shop_group_name' => [
'title' => $this->trans('Shop group', [], 'Admin.Shopparameters.Feature'),
'width' => 150,
'filter_key' => 'gs!name',
],
'category_name' => [
'title' => $this->trans('Root category', [], 'Admin.Shopparameters.Feature'),
'width' => 150,
'filter_key' => 'cl!name',
],
'url' => [
'title' => $this->trans('Main URL for this shop', [], 'Admin.Shopparameters.Feature'),
'havingFilter' => 'url',
],
];
}
public function getTabSlug()
{
return 'ROLE_MOD_TAB_ADMINSHOPGROUP_';
}
public function viewAccess($disable = false)
{
return Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE');
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
if (!$this->display) {
if ($this->id_object) {
$this->loadObject();
}
if (!$this->id_shop_group && is_object($this->object) && $this->object->id_shop_group) {
$this->id_shop_group = $this->object->id_shop_group;
}
$this->page_header_toolbar_btn['edit'] = [
'desc' => $this->trans('Edit this shop group', [], 'Admin.Shopparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShopGroup') . '&updateshop_group&id_shop_group='
. $this->id_shop_group,
];
$this->page_header_toolbar_btn['new'] = [
'desc' => $this->trans('Add new shop', [], 'Admin.Shopparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShop') . '&add' . $this->table . '&id_shop_group='
. $this->id_shop_group,
];
}
}
public function initToolbar()
{
parent::initToolbar();
if ($this->display != 'edit' && $this->display != 'add') {
if ($this->id_object) {
$this->loadObject();
}
if (!$this->id_shop_group && $this->object && $this->object->id_shop_group) {
$this->id_shop_group = $this->object->id_shop_group;
}
$this->toolbar_btn['new'] = [
'desc' => $this->trans('Add new shop', [], 'Admin.Shopparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShop') . '&add' . $this->table . '&id_shop_group='
. $this->id_shop_group,
];
}
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
parent::initContent();
$this->addJqueryPlugin('cooki-plugin');
$data = Shop::getTree();
foreach ($data as $group_key => $group) {
foreach ($group['shops'] as $shop_key => $shop) {
$current_shop = new Shop($shop['id_shop']);
$urls = $current_shop->getUrls();
foreach ($urls as $url) {
$title = $url['domain'] . $url['physical_uri'] . $url['virtual_uri'];
if (strlen($title) > 23) {
$title = substr($title, 0, 23) . '...';
}
$url['name'] = $title;
$data[$group_key][$shop_key]['urls'][$url['id_shop_url']] = $url;
}
}
}
$shops_tree = new HelperTreeShops('shops-tree', $this->trans('Multistore tree', [], 'Admin.Shopparameters.Feature'));
$shops_tree->setNodeFolderTemplate('shop_tree_node_folder.tpl')->setNodeItemTemplate('shop_tree_node_item.tpl')
->setHeaderTemplate('shop_tree_header.tpl')->setActions([
new TreeToolbarLink(
'Collapse all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'collapseAll\'); return false;',
'icon-collapse-alt'
),
new TreeToolbarLink(
'Expand all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'expandAll\'); return false;',
'icon-expand-alt'
),
])
->setAttribute('url_shop_group', $this->context->link->getAdminLink('AdminShopGroup'))
->setAttribute('url_shop', $this->context->link->getAdminLink('AdminShop'))
->setAttribute('url_shop_url', $this->context->link->getAdminLink('AdminShopUrl'))
->setData($data);
$shops_tree = $shops_tree->render(null, false, false);
if ($this->display == 'edit') {
$this->toolbar_title[] = $this->object->name;
} elseif (!$this->display && $this->id_shop_group) {
$group = new ShopGroup($this->id_shop_group);
$this->toolbar_title[] = $group->name;
}
$this->context->smarty->assign([
'toolbar_scroll' => 1,
'toolbar_btn' => $this->toolbar_btn,
'title' => $this->toolbar_title,
'shops_tree' => $shops_tree,
]);
}
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = 'gs.name shop_group_name, cl.name category_name, CONCAT(\'http://\', su.domain, su.physical_uri, su.virtual_uri) AS url';
$this->_join = '
LEFT JOIN `' . _DB_PREFIX_ . 'shop_group` gs
ON (a.id_shop_group = gs.id_shop_group)
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON (a.id_category = cl.id_category AND cl.id_lang=' . (int) $this->context->language->id . ')
LEFT JOIN ' . _DB_PREFIX_ . 'shop_url su
ON a.id_shop = su.id_shop AND su.main = 1
';
$this->_group = 'GROUP BY a.id_shop';
if ($id_shop_group = (int) Tools::getValue('id_shop_group')) {
$this->_where = 'AND a.id_shop_group = ' . $id_shop_group;
}
return parent::renderList();
}
public function displayAjaxGetCategoriesFromRootCategory()
{
if (Tools::isSubmit('id_category')) {
$getId = function ($category) {
return (int) $category['id_category'];
};
$languageId = $this->context->language->id;
$getChildren = function (array $category) use ($languageId, $getId) {
return Category::getChildren($getId($category), $languageId);
};
// selected categories ids.
$selectedCategories = Tree::extractChildrenId(
[
['id_category' => (int) Tools::getValue('id_category')],
],
$getChildren,
$getId
);
$helper = new HelperTreeCategories('categories-tree', null, (int) Tools::getValue('id_category'), null, false);
$this->content = $helper->setSelectedCategories($selectedCategories)->setUseSearch(true)->setUseCheckBox(true)
->render();
}
parent::displayAjax();
}
public function postProcess()
{
if (Tools::isSubmit('id_category_default')) {
$_POST['id_category'] = Tools::getValue('id_category_default');
}
if (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) {
$shop_group = new ShopGroup((int) Tools::getValue('id_shop_group'));
if ($shop_group->shopNameExists(Tools::getValue('name'), (int) Tools::getValue('id_shop'))) {
$this->errors[] = $this->trans('You cannot have two stores with the same name in the same group.', [], 'Admin.Advparameters.Notification');
}
}
if (count($this->errors)) {
return false;
}
/** @var Shop|bool $result */
$result = parent::postProcess();
if ($result != false && (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) && (int) $result->id_category != (int) Configuration::get('PS_HOME_CATEGORY', null, null, (int) $result->id)) {
Configuration::updateValue('PS_HOME_CATEGORY', (int) $result->id_category, false, null, (int) $result->id);
}
if ($this->redirect_after) {
$this->redirect_after .= '&id_shop_group=' . $this->id_shop_group;
}
return $result;
}
/**
* @return bool
*
* @throws PrestaShopException
*/
public function processDelete()
{
if (!Validate::isLoadedObject($object = $this->loadObject())) {
$this->errors[] = $this->trans('Unable to load this store.', [], 'Admin.Advparameters.Notification');
} elseif (!Shop::hasDependency($object->id)) {
$result = Category::deleteCategoriesFromShop($object->id) && parent::processDelete();
Tools::generateHtaccess();
return $result;
} else {
$this->errors[] = $this->trans('You cannot delete this shop (customer and/or order dependency).', [], 'Admin.Shopparameters.Notification');
}
return false;
}
/**
* @param Shop $new_shop
*
* @return ObjectModel|bool
*/
protected function afterAdd($new_shop)
{
$import_data = Tools::getValue('importData', []);
// The root category should be at least imported
$new_shop->copyShopData((int) Tools::getValue('importFromShop'), $import_data);
$useImportData = Tools::getValue('useImportData');
$db = Db::getInstance(_PS_USE_SQL_SLAVE_);
// copy default data
if (!$useImportData || (is_array($import_data) && !isset($import_data['group']))) {
$sql = 'INSERT INTO `' . _DB_PREFIX_ . 'group_shop` (`id_shop`, `id_group`)
VALUES
(' . (int) $new_shop->id . ', ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP') . '),
(' . (int) $new_shop->id . ', ' . (int) Configuration::get('PS_GUEST_GROUP') . '),
(' . (int) $new_shop->id . ', ' . (int) Configuration::get('PS_CUSTOMER_GROUP') . ')
';
$db->execute($sql);
}
if (!$useImportData || (is_array($import_data) && !isset($import_data['cms']))) {
$allLangs = Language::getLanguages(false);
$db->insert(
'cms_category_shop',
[
'id_cms_category' => CmsPageCategoryId::ROOT_CMS_PAGE_CATEGORY_ID,
'id_shop' => (int) $new_shop->id,
]
);
foreach ($allLangs as $lang) {
$db->insert(
'cms_category_lang',
[
'id_cms_category' => CmsPageCategoryId::ROOT_CMS_PAGE_CATEGORY_ID,
'id_lang' => (int) $lang['id_lang'],
'id_shop' => (int) $new_shop->id,
'name' => 'Home',
'link_rewrite' => 'home',
]
);
}
}
return parent::afterAdd($new_shop);
}
/**
* @param Shop $new_shop
*
* @return bool
*/
protected function afterUpdate($new_shop)
{
$categories = Tools::getValue('categoryBox');
if (!is_array($categories)) {
$this->errors[] = $this->trans('Please create some sub-categories for this root category.', [], 'Admin.Shopparameters.Notification');
return false;
}
array_unshift($categories, Configuration::get('PS_ROOT_CATEGORY'));
if (!Category::updateFromShop($categories, $new_shop->id)) {
$this->errors[] = $this->trans('You need to select at least the root category.', [], 'Admin.Shopparameters.Notification');
}
if (Tools::getValue('useImportData') && ($import_data = Tools::getValue('importData')) && is_array($import_data)) {
$new_shop->copyShopData((int) Tools::getValue('importFromShop'), $import_data);
}
if (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) {
$this->redirect_after = self::$currentIndex . '&shop_id=' . (int) $new_shop->id . '&conf=4&token=' . $this->token;
}
return parent::afterUpdate($new_shop);
}
public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false)
{
if (Shop::getContext() == Shop::CONTEXT_GROUP) {
$this->_where .= ' AND a.id_shop_group = ' . (int) Shop::getContextShopGroupID();
}
parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop);
$shop_delete_list = [];
// don't allow to remove shop which have dependencies (customers / orders / ... )
foreach ($this->_list as $shop) {
if (Shop::hasDependency($shop['id_shop'])) {
$shop_delete_list[] = $shop['id_shop'];
}
}
$this->context->smarty->assign('shops_having_dependencies', $shop_delete_list);
}
/**
* @return string|void
*
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
* @throws SmartyException
*/
public function renderForm()
{
if (!($obj = $this->loadObject(true))) {
return;
}
/* @var Shop $obj */
$this->fields_form = [
'legend' => [
'title' => $this->trans('Store', [], 'Admin.Global'),
'icon' => 'icon-shopping-cart',
],
'identifier' => 'shop_id',
'input' => [
[
'type' => 'text',
'label' => $this->trans('Store name', [], 'Admin.Shopparameters.Feature'),
'desc' => [
$this->trans('This field does not refer to the shop name visible in the front office.', [], 'Admin.Shopparameters.Help'),
$this->trans('Follow [1]this link[/1] to edit the shop name used on the front office.', [
'[1]' => '<a href="' . $this->context->link->getAdminLink('AdminStores') . '#store_fieldset_general">',
'[/1]' => '</a>',
], 'Admin.Shopparameters.Help'), ],
'name' => 'name',
'required' => true,
],
],
];
$display_group_list = true;
if ($this->display == 'edit') {
$group = new ShopGroup($obj->id_shop_group);
if ($group->share_customer || $group->share_order || $group->share_stock) {
$display_group_list = false;
}
}
$this->fields_form['input'][] = [
'type' => 'color',
'label' => $this->trans('Color', [], 'Admin.Catalog.Feature'),
'name' => 'color',
'desc' => [
$this->trans('It will only be applied to the multistore header to highlight your shop context.', [], 'Admin.Shopparameters.Feature'),
],
'hint' => $this->trans('Choose a color with the color picker, or enter an HTML color (e.g. "lightblue", "#CC6600").', [], 'Admin.Catalog.Help'),
];
if ($display_group_list) {
$options = [];
foreach (ShopGroup::getShopGroups() as $group) {
/** @var ShopGroup $group */
if ($this->display == 'edit' && ($group->share_customer || $group->share_order || $group->share_stock) && ShopGroup::hasDependency($group->id)) {
continue;
}
$options[] = [
'id_shop_group' => $group->id,
'name' => $group->name,
];
}
if ($this->display == 'add') {
$group_desc = $this->trans('Warning: You won\'t be able to change the group of this shop if this shop belongs to a group with one of these options activated: Share Customers, Share Quantities or Share Orders.', [], 'Admin.Shopparameters.Notification');
} else {
$group_desc = $this->trans('You can only move your shop to a shop group with all "share" options disabled -- or to a shop group with no customers/orders.', [], 'Admin.Shopparameters.Notification');
}
$this->fields_form['input'][] = [
'type' => 'select',
'label' => $this->trans('Shop group', [], 'Admin.Shopparameters.Feature'),
'desc' => $group_desc,
'name' => 'id_shop_group',
'options' => [
'query' => $options,
'id' => 'id_shop_group',
'name' => 'name',
],
];
} else {
$this->fields_form['input'][] = [
'type' => 'hidden',
'name' => 'id_shop_group',
'default' => isset($group) ? $group->name : '',
];
$this->fields_form['input'][] = [
'type' => 'textShopGroup',
'label' => $this->trans('Shop group', [], 'Admin.Shopparameters.Feature'),
'desc' => $this->trans('You can\'t edit the shop group because the current shop belongs to a group with the "share" option enabled.', [], 'Admin.Shopparameters.Help'),
'name' => 'id_shop_group',
'value' => isset($group) ? $group->name : '',
];
}
$categories = Category::getRootCategories($this->context->language->id);
$this->fields_form['input'][] = [
'type' => 'select',
'label' => $this->trans('Category root', [], 'Admin.Catalog.Feature'),
'desc' => $this->trans('This is the root category of the store that you\'ve created. To define a new root category for your store, [1]please click here[/1].', [
'_raw' => true,
'[1]' => '<a href="' . $this->context->link->getAdminLink('AdminCategories') . '&addcategoryroot" target="_blank">',
'[/1]' => '</a>',
], 'Admin.Shopparameters.Help'),
'name' => 'id_category',
'options' => [
'query' => $categories,
'id' => 'id_category',
'name' => 'name',
],
];
if (Tools::isSubmit('id_shop')) {
$shop = new Shop((int) Tools::getValue('id_shop'));
$id_root = $shop->id_category;
} else {
$id_root = $categories[0]['id_category'];
}
$id_shop = (int) Tools::getValue('id_shop');
self::$currentIndex = self::$currentIndex . '&id_shop_group=' . (int) (Tools::getValue('id_shop_group') ?
Tools::getValue('id_shop_group') : (isset($obj->id_shop_group) ? $obj->id_shop_group : Shop::getContextShopGroupID()));
$shop = new Shop($id_shop);
$selected_cat = Shop::getCategories($id_shop);
if (empty($selected_cat)) {
// get first category root and preselect all these children
$root_categories = Category::getRootCategories();
$root_category = new Category($root_categories[0]['id_category']);
$children = $root_category->getAllChildren($this->context->language->id);
$selected_cat[] = $root_categories[0]['id_category'];
foreach ($children as $child) {
$selected_cat[] = $child->id;
}
}
if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) {
$root_category = new Category($shop->id_category);
} else {
$root_category = new Category($id_root);
}
$this->fields_form['input'][] = [
'type' => 'categories',
'name' => 'categoryBox',
'label' => $this->trans('Associated categories', [], 'Admin.Catalog.Feature'),
'tree' => [
'id' => 'categories-tree',
'selected_categories' => $selected_cat,
'root_category' => $root_category->id,
'use_search' => true,
'use_checkbox' => true,
],
'desc' => $this->trans('By selecting associated categories, you are choosing to share the categories between shops. Once associated between shops, any alteration of this category will impact every shop.', [], 'Admin.Shopparameters.Help'),
];
$themes = (new ThemeManagerBuilder($this->context, Db::getInstance()))
->buildRepository()
->getList();
$this->fields_form['input'][] = [
'type' => 'theme',
'label' => $this->trans('Theme', [], 'Admin.Design.Feature'),
'name' => 'theme',
'values' => $themes,
];
$this->fields_form['submit'] = [
'title' => $this->trans('Save', [], 'Admin.Actions'),
];
if (Shop::getTotalShops() > 1 && $obj->id) {
$disabled = ['active' => false];
} else {
$disabled = false;
}
$import_data = [
'carrier' => $this->trans('Carriers', [], 'Admin.Shipping.Feature'),
'cms' => $this->trans('Pages', [], 'Admin.Design.Feature'),
'contact' => $this->trans('Contact information', [], 'Admin.Advparameters.Feature'),
'country' => $this->trans('Countries', [], 'Admin.Global'),
'currency' => $this->trans('Currencies', [], 'Admin.Global'),
'discount' => $this->trans('Discount prices', [], 'Admin.Advparameters.Feature'),
'employee' => $this->trans('Employees', [], 'Admin.Advparameters.Feature'),
'image' => $this->trans('Images', [], 'Admin.Global'),
'lang' => $this->trans('Languages', [], 'Admin.Global'),
'manufacturer' => $this->trans('Brands', [], 'Admin.Global'),
'module' => $this->trans('Modules', [], 'Admin.Global'),
'hook_module' => $this->trans('Module hooks', [], 'Admin.Advparameters.Feature'),
'meta_lang' => $this->trans('Meta information', [], 'Admin.Advparameters.Feature'),
'product' => $this->trans('Products', [], 'Admin.Global'),
'product_attribute' => $this->trans('Product combinations', [], 'Admin.Advparameters.Feature'),
'stock_available' => $this->trans('Available quantities for sale', [], 'Admin.Advparameters.Feature'),
'store' => $this->trans('Stores', [], 'Admin.Global'),
'webservice_account' => $this->trans('Webservice accounts', [], 'Admin.Advparameters.Feature'),
'attribute_group' => $this->trans('Attribute groups', [], 'Admin.Advparameters.Feature'),
'feature' => $this->trans('Features', [], 'Admin.Global'),
'group' => $this->trans('Customer groups', [], 'Admin.Advparameters.Feature'),
'tax_rules_group' => $this->trans('Tax rules groups', [], 'Admin.Advparameters.Feature'),
'supplier' => $this->trans('Suppliers', [], 'Admin.Global'),
'zone' => $this->trans('Zones', [], 'Admin.International.Feature'),
'cart_rule' => $this->trans('Cart rules', [], 'Admin.Advparameters.Feature'),
];
// Hook for duplication of shop data
$modules_list = Hook::getHookModuleExecList('actionShopDataDuplication');
if (is_array($modules_list) && count($modules_list) > 0) {
foreach ($modules_list as $m) {
$import_data['Module' . ucfirst($m['module'])] = Module::getModuleName($m['module']);
}
}
asort($import_data);
if (!$this->object->id) {
$fields_import_form = [
'radio' => [
'type' => 'radio',
'label' => $this->trans('Import data', [], 'Admin.Advparameters.Feature'),
'name' => 'useImportData',
'value' => 1,
],
'select' => [
'type' => 'select',
'name' => 'importFromShop',
'label' => $this->trans('Choose the source store', [], 'Admin.Advparameters.Feature'),
'options' => [
'query' => Shop::getShops(false),
'name' => 'name',
],
],
'allcheckbox' => [
'type' => 'checkbox',
'label' => $this->trans('Choose data to import', [], 'Admin.Advparameters.Feature'),
'values' => $import_data,
],
'desc' => $this->trans('Use this option to associate data (products, modules, etc.) the same way for each selected shop.', [], 'Admin.Advparameters.Help'),
];
}
if (!$obj->theme_name) {
$themes = (new ThemeManagerBuilder($this->context, Db::getInstance()))
->buildRepository()
->getList();
$theme = array_pop($themes);
$theme_name = $theme->getName();
} else {
$theme_name = $obj->theme_name;
}
$this->fields_value = [
'id_shop_group' => (
Tools::getValue('id_shop_group')
? Tools::getValue('id_shop_group')
: (
isset($obj->id_shop_group)
? $obj->id_shop_group
: Shop::getContextShopGroupID()
)
),
'id_category' => (
Tools::getValue('id_category')
? Tools::getValue('id_category')
: (
isset($obj->id_category)
? $obj->id_category
: (int) Configuration::get('PS_HOME_CATEGORY')
)
),
'theme_name' => $theme_name,
];
$ids_category = [];
$shops = Shop::getShops(false);
foreach ($shops as $shop) {
$ids_category[$shop['id_shop']] = $shop['id_category'];
}
$this->tpl_form_vars = [
'disabled' => $disabled,
'checked' => (Tools::getValue('addshop') !== false) ? true : false,
'defaultShop' => (int) Configuration::get('PS_SHOP_DEFAULT'),
'ids_category' => $ids_category,
];
if (isset($fields_import_form)) {
$this->tpl_form_vars = array_merge($this->tpl_form_vars, ['form_import' => $fields_import_form]);
}
return parent::renderForm();
}
/**
* Object creation
*
* @return Shop|void
*
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
*/
public function processAdd()
{
if (!Tools::getValue('categoryBox') || !in_array(Tools::getValue('id_category'), Tools::getValue('categoryBox'))) {
$this->errors[] = $this->trans('You need to select at least the root category.', [], 'Admin.Advparameters.Notification');
}
if (Tools::isSubmit('id_category_default')) {
$_POST['id_category'] = (int) Tools::getValue('id_category_default');
}
/* Checking fields validity */
$this->validateRules();
$this->errors = array_unique($this->errors);
if (!count($this->errors)) {
/** @var Shop $object */
$object = new $this->className();
$this->copyFromPost($object, $this->table);
$this->beforeAdd($object);
if (!$object->add()) {
$this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error') .
' <b>' . $this->table . ' (' . Db::getInstance()->getMsgError() . ')</b>';
} elseif (($_POST[$this->identifier] = $object->id) && $this->postImage($object->id) && empty($this->errors) && $this->_redirect) {
// voluntary do affectation here
$parent_id = (int) Tools::getValue('id_parent', 1);
$this->afterAdd($object);
$this->updateAssoShop($object->id);
// Save and stay on same form
if (Tools::isSubmit('submitAdd' . $this->table . 'AndStay')) {
$this->redirect_after = self::$currentIndex . '&shop_id=' . (int) $object->id . '&conf=3&update' . $this->table . '&token=' . $this->token;
}
// Save and back to parent
if (Tools::isSubmit('submitAdd' . $this->table . 'AndBackToParent')) {
$this->redirect_after = self::$currentIndex . '&shop_id=' . (int) $parent_id . '&conf=3&token=' . $this->token;
}
// Default behavior (save and back)
if (empty($this->redirect_after)) {
$this->redirect_after = self::$currentIndex . ($parent_id ? '&shop_id=' . $object->id : '') . '&conf=3&token=' . $this->token;
}
}
} else {
$this->display = 'add';
return;
}
$object->associateSuperAdmins();
$categories = Tools::getValue('categoryBox');
array_unshift($categories, Configuration::get('PS_ROOT_CATEGORY'));
Category::updateFromShop($categories, $object->id);
if (Tools::getValue('useImportData') && ($import_data = Tools::getValue('importData')) && is_array($import_data) && isset($import_data['product'])) {
ini_set('max_execution_time', '7200');
Search::indexation(true);
}
return $object;
}
public function displayEditLink($token, $id, $name = null)
{
if ($this->access('edit')) {
$tpl = $this->createTemplate('helpers/list/list_action_edit.tpl');
if (!array_key_exists('Edit', self::$cache_lang)) {
self::$cache_lang['Edit'] = $this->trans('Edit', [], 'Admin.Actions');
}
$tpl->assign([
'href' => $this->context->link->getAdminLink('AdminShop') . '&shop_id=' . (int) $id . '&update' . $this->table,
'action' => self::$cache_lang['Edit'],
'id' => $id,
]);
return $tpl->fetch();
} else {
return;
}
}
public function initCategoriesAssociation($id_root = null)
{
if (null === $id_root) {
$id_root = Configuration::get('PS_ROOT_CATEGORY');
}
$id_shop = (int) Tools::getValue('id_shop');
$shop = new Shop($id_shop);
$selected_cat = Shop::getCategories($id_shop);
if (empty($selected_cat)) {
// get first category root and preselect all these children
$root_categories = Category::getRootCategories();
$root_category = new Category($root_categories[0]['id_category']);
$children = $root_category->getAllChildren($this->context->language->id);
$selected_cat[] = $root_categories[0]['id_category'];
foreach ($children as $child) {
$selected_cat[] = $child->id;
}
}
if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) {
$root_category = new Category($shop->id_category);
} else {
$root_category = new Category($id_root);
}
$root_category = ['id_category' => $root_category->id, 'name' => $root_category->name[$this->context->language->id]];
$helper = new Helper();
return $helper->renderCategoryTree($root_category, $selected_cat, 'categoryBox', false, true);
}
public function ajaxProcessTree()
{
$tree = [];
$sql = 'SELECT g.id_shop_group, g.name as group_name, s.id_shop, s.name as shop_name, u.id_shop_url, u.domain, u.physical_uri, u.virtual_uri
FROM ' . _DB_PREFIX_ . 'shop_group g
LEFT JOIN ' . _DB_PREFIX_ . 'shop s ON g.id_shop_group = s.id_shop_group
LEFT JOIN ' . _DB_PREFIX_ . 'shop_url u ON u.id_shop = s.id_shop
ORDER BY g.name, s.name, u.domain';
$results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
foreach ($results as $row) {
$id_shop_group = $row['id_shop_group'];
$id_shop = $row['id_shop'];
$id_shop_url = $row['id_shop_url'];
// Group list
if (!isset($tree[$id_shop_group])) {
$tree[$id_shop_group] = [
'data' => [
'title' => '<b>' . $this->trans('Group', [], 'Admin.Global') . '</b> ' . $row['group_name'],
'icon' => 'themes/' . $this->context->employee->bo_theme . '/img/tree-multishop-groups.png',
'attr' => [
'href' => $this->context->link->getAdminLink('AdminShop') . '&id_shop_group=' . $id_shop_group,
'title' => $this->trans('Click here to display the shops in the %name% shop group', ['%name%' => $row['group_name']], 'Admin.Advparameters.Help'),
],
],
'attr' => [
'id' => 'tree-group-' . $id_shop_group,
],
'children' => [],
];
}
// Shop list
if (!$id_shop) {
continue;
}
if (!isset($tree[$id_shop_group]['children'][$id_shop])) {
$tree[$id_shop_group]['children'][$id_shop] = [
'data' => [
'title' => $row['shop_name'],
'icon' => 'themes/' . $this->context->employee->bo_theme . '/img/tree-multishop-shop.png',
'attr' => [
'href' => $this->context->link->getAdminLink('AdminShopUrl') . '&shop_id=' . (int) $id_shop,
'title' => $this->trans('Click here to display the URLs of the %name% shop', ['%name%' => $row['shop_name']], 'Admin.Advparameters.Help'),
],
],
'attr' => [
'id' => 'tree-shop-' . $id_shop,
],
'children' => [],
];
}
// Url list
if (!$id_shop_url) {
continue;
}
if (!isset($tree[$id_shop_group]['children'][$id_shop]['children'][$id_shop_url])) {
$url = $row['domain'] . $row['physical_uri'] . $row['virtual_uri'];
if (strlen($url) > 23) {
$url = substr($url, 0, 23) . '...';
}
$tree[$id_shop_group]['children'][$id_shop]['children'][$id_shop_url] = [
'data' => [
'title' => $url,
'icon' => 'themes/' . $this->context->employee->bo_theme . '/img/tree-multishop-url.png',
'attr' => [
'href' => $this->context->link->getAdminLink('AdminShopUrl') . '&updateshop_url&id_shop_url=' . $id_shop_url,
'title' => $row['domain'] . $row['physical_uri'] . $row['virtual_uri'],
],
],
'attr' => [
'id' => 'tree-url-' . $id_shop_url,
],
];
}
}
// jstree need to have children as array and not object, so we use sort to get clean keys
// DO NOT REMOVE this code, even if it seems really strange ;)
sort($tree);
foreach ($tree as &$groups) {
sort($groups['children']);
foreach ($groups['children'] as &$shops) {
sort($shops['children']);
}
}
$tree = [[
'data' => [
'title' => '<b>' . $this->trans('Store groups list', [], 'Admin.Advparameters.Feature') . '</b>',
'icon' => 'themes/' . $this->context->employee->bo_theme . '/img/tree-multishop-root.png',
'attr' => [
'href' => $this->context->link->getAdminLink('AdminShopGroup'),
'title' => $this->trans('Click here to display the list of shop groups', [], 'Admin.Advparameters.Help'),
],
],
'attr' => [
'id' => 'tree-root',
],
'state' => 'open',
'children' => $tree,
]];
die(json_encode($tree));
}
}

View File

@@ -0,0 +1,383 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property ShopGroup $object
*/
class AdminShopGroupControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'shop_group';
$this->className = 'ShopGroup';
$this->lang = false;
$this->multishop_context = Shop::CONTEXT_ALL;
$this->addRowAction('edit');
$this->addRowAction('delete');
parent::__construct();
if (!Tools::getValue('realedit')) {
$this->deleted = false;
}
$this->show_toolbar = false;
$this->fields_list = [
'id_shop_group' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Store group', [], 'Admin.Advparameters.Feature'),
'width' => 'auto',
'filter_key' => 'a!name',
],
];
$this->fields_options = [
'general' => [
'title' => $this->trans('Multistore options', [], 'Admin.Advparameters.Feature'),
'fields' => [
'PS_SHOP_DEFAULT' => [
'title' => $this->trans('Default store', [], 'Admin.Advparameters.Feature'),
'cast' => 'intval',
'type' => 'select',
'identifier' => 'id_shop',
'list' => Shop::getShops(),
'visibility' => Shop::CONTEXT_ALL,
],
],
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
],
];
}
public function viewAccess($disable = false)
{
return Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE');
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
parent::initContent();
$this->addJqueryPlugin('cooki-plugin');
$data = Shop::getTree();
foreach ($data as $key_group => &$group) {
foreach ($group['shops'] as $key_shop => &$shop) {
$current_shop = new Shop($shop['id_shop']);
$urls = $current_shop->getUrls();
foreach ($urls as $key_url => &$url) {
$title = $url['domain'] . $url['physical_uri'] . $url['virtual_uri'];
if (strlen($title) > 23) {
$title = substr($title, 0, 23) . '...';
}
$url['name'] = $title;
$shop['urls'][$url['id_shop_url']] = $url;
}
}
}
$shops_tree = new HelperTreeShops('shops-tree', $this->trans('Multistore tree', [], 'Admin.Advparameters.Feature'));
$shops_tree->setNodeFolderTemplate('shop_tree_node_folder.tpl')->setNodeItemTemplate('shop_tree_node_item.tpl')
->setHeaderTemplate('shop_tree_header.tpl')->setActions([
new TreeToolbarLink(
'Collapse all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'collapseAll\'); return false;',
'icon-collapse-alt'
),
new TreeToolbarLink(
'Expand all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'expandAll\'); return false;',
'icon-expand-alt'
),
])
->setAttribute('url_shop_group', $this->context->link->getAdminLink('AdminShopGroup'))
->setAttribute('url_shop', $this->context->link->getAdminLink('AdminShop'))
->setAttribute('url_shop_url', $this->context->link->getAdminLink('AdminShopUrl'))
->setData($data);
$shops_tree = $shops_tree->render(null, false, false);
if ($this->display == 'edit') {
$this->toolbar_title[] = $this->object->name;
}
$this->context->smarty->assign([
'toolbar_scroll' => 1,
'toolbar_btn' => $this->toolbar_btn,
'title' => $this->toolbar_title,
'shops_tree' => $shops_tree,
]);
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
if ($this->display != 'add' && $this->display != 'edit') {
$this->page_header_toolbar_btn['new'] = [
'desc' => $this->trans('Add a new group of stores', [], 'Admin.Advparameters.Feature'),
'href' => self::$currentIndex . '&add' . $this->table . '&token=' . $this->token,
];
$this->page_header_toolbar_btn['new_2'] = [
'desc' => $this->trans('Add a new store', [], 'Admin.Advparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShop') . '&addshop',
'imgclass' => 'new_2',
'icon' => 'process-icon-new',
];
}
}
public function initToolbar()
{
parent::initToolbar();
if ($this->display != 'add' && $this->display != 'edit') {
$this->toolbar_btn['new'] = [
'desc' => $this->trans('Add a new group of stores', [], 'Admin.Advparameters.Feature'),
'href' => self::$currentIndex . '&add' . $this->table . '&token=' . $this->token,
];
}
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderForm()
{
$this->fields_form = [
'legend' => [
'title' => $this->trans('Store group', [], 'Admin.Advparameters.Feature'),
'icon' => 'icon-shopping-cart',
],
'description' => $this->trans('Warning: Enabling the "share customers" and "share orders" options is not recommended. Once activated and orders are created, you will not be able to disable these options. If you need these options, we recommend using several categories rather than several shops.', [], 'Admin.Advparameters.Help'),
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name of the store group', [], 'Admin.Advparameters.Feature'),
'name' => 'name',
'required' => true,
],
[
'type' => 'color',
'label' => $this->trans('Color', [], 'Admin.Catalog.Feature'),
'name' => 'color',
'desc' => [
$this->trans('It will only be applied to this group of shops, each store will keep its individual color.', [], 'Admin.Shopparameters.Feature'),
],
'hint' => $this->trans('Choose a color with the color picker, or enter an HTML color (e.g. "lightblue", "#CC6600").', [], 'Admin.Catalog.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Share customers', [], 'Admin.Advparameters.Feature'),
'name' => 'share_customer',
'required' => true,
'class' => 't',
'is_bool' => true,
'disabled' => ($this->id_object && $this->display == 'edit' && ShopGroup::hasDependency($this->id_object, 'customer')) ? true : false,
'values' => [
[
'id' => 'share_customer_on',
'value' => 1,
],
[
'id' => 'share_customer_off',
'value' => 0,
],
],
'desc' => $this->trans('Once this option is enabled, the shops in this group will share customers. If a customer registers in any one of these shops, the account will automatically be available in the others shops of this group.', [], 'Admin.Advparameters.Help') . '<br/>' . $this->trans('Warning: you will not be able to disable this option once you have registered customers.', [], 'Admin.Advparameters.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Share available quantities for sale', [], 'Admin.Advparameters.Feature'),
'name' => 'share_stock',
'required' => true,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'share_stock_on',
'value' => 1,
],
[
'id' => 'share_stock_off',
'value' => 0,
],
],
'desc' => $this->trans('Share available quantities between shops of this group. When changing this option, all available products quantities will be reset to 0.', [], 'Admin.Advparameters.Feature'),
],
[
'type' => 'switch',
'label' => $this->trans('Share orders', [], 'Admin.Advparameters.Feature'),
'name' => 'share_order',
'required' => true,
'class' => 't',
'is_bool' => true,
'disabled' => ($this->id_object && $this->display == 'edit' && ShopGroup::hasDependency($this->id_object, 'order')) ? true : false,
'values' => [
[
'id' => 'share_order_on',
'value' => 1,
],
[
'id' => 'share_order_off',
'value' => 0,
],
],
'desc' => $this->trans('Once this option is enabled (which is only possible if customers and available quantities are shared among shops), the customer\'s cart will be shared by all shops in this group. This way, any purchase started in one shop will be able to be completed in another shop from the same group.', [], 'Admin.Advparameters.Help') . '<br/>' . $this->trans('Warning: You will not be able to disable this option once you\'ve started to accept orders.', [], 'Admin.Advparameters.Help'),
],
[
'type' => 'switch',
'label' => $this->trans('Status', [], 'Admin.Global'),
'name' => 'active',
'required' => true,
'class' => 't',
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
],
[
'id' => 'active_off',
'value' => 0,
],
],
'desc' => $this->trans('Enable or disable this shop group?', [], 'Admin.Advparameters.Help'),
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
if (!($obj = $this->loadObject(true))) {
return;
}
if (Shop::getTotalShops() > 1 && $obj->id) {
$disabled = [
'share_customer' => true,
'share_stock' => true,
'share_order' => true,
'active' => false,
];
} else {
$disabled = false;
}
$default_shop = new Shop((int) Configuration::get('PS_SHOP_DEFAULT'));
$this->tpl_form_vars = [
'disabled' => $disabled,
'checked' => (Tools::getValue('addshop_group') !== false) ? true : false,
'defaultGroup' => $default_shop->id_shop_group,
];
$this->fields_value = [
'active' => $obj->id ? (bool) $obj->active : true,
];
return parent::renderForm();
}
public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false)
{
parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop);
$shop_group_delete_list = [];
// test store authorized to remove
foreach ($this->_list as $shop_group) {
$shops = Shop::getShops(true, $shop_group['id_shop_group']);
if (!empty($shops)) {
$shop_group_delete_list[] = $shop_group['id_shop_group'];
}
}
$this->addRowActionSkipList('delete', $shop_group_delete_list);
}
public function postProcess()
{
if (Tools::isSubmit('delete' . $this->table) || Tools::isSubmit('status') || Tools::isSubmit('status' . $this->table)) {
/** @var ShopGroup $object */
$object = $this->loadObject();
if (ShopGroup::getTotalShopGroup() == 1) {
$this->errors[] = $this->trans('You cannot delete or disable the last shop group.', [], 'Admin.Notifications.Error');
} elseif ($object->haveShops()) {
$this->errors[] = $this->trans('You cannot delete or disable a shop group in use.', [], 'Admin.Notifications.Error');
}
if (count($this->errors)) {
return false;
}
}
return parent::postProcess();
}
public function beforeUpdateOptions()
{
if (!(new Shop((int) Tools::getValue('PS_SHOP_DEFAULT')))->getBaseURL()) {
$this->errors[] = $this->trans('You must configure this store\'s URL before setting it as default.', [], 'Admin.Advparameters.Notification');
}
}
/**
* @param ShopGroup $new_shop_group
*
* @return bool|void
*/
protected function afterAdd($new_shop_group)
{
// Reset available quantitites
StockAvailable::resetProductFromStockAvailableByShopGroup($new_shop_group);
}
/**
* @param ShopGroup $new_shop_group
*
* @return bool|void
*/
protected function afterUpdate($new_shop_group)
{
// Reset available quantitites
StockAvailable::resetProductFromStockAvailableByShopGroup($new_shop_group);
}
/**
* @return string|void
*/
public function renderOptions()
{
if ($this->fields_options && is_array($this->fields_options)) {
$this->display = 'options';
$this->show_toolbar = false;
$helper = new HelperOptions();
$this->setHelperDisplay($helper);
$helper->id = $this->id;
$helper->tpl_vars = $this->tpl_option_vars;
$options = $helper->generateOptions($this->fields_options);
return $options;
}
}
}

View File

@@ -0,0 +1,576 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property ShopUrl|null $object
*/
class AdminShopUrlControllerCore extends AdminController
{
/**
* @var int
*/
public $id_shop;
/**
* @var bool
*/
public $redirect_shop_url;
public function __construct()
{
$this->bootstrap = true;
$this->table = 'shop_url';
$this->className = 'ShopUrl';
$this->lang = false;
$this->multishop_context = Shop::CONTEXT_ALL;
$this->bulk_actions = [];
parent::__construct();
/* if $_GET['id_shop'] is transmitted, virtual url can be loaded in config.php, so we wether transmit shop_id in herfs */
if ($this->id_shop = (int) Tools::getValue('shop_id')) {
$_GET['id_shop'] = $this->id_shop;
} else {
$this->id_shop = (int) Tools::getValue('id_shop');
}
if (!Tools::getValue('realedit')) {
$this->deleted = false;
}
$this->fields_list = [
'id_shop_url' => [
'title' => $this->trans('Store URL ID', [], 'Admin.Advparameters.Feature'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'shop_name' => [
'title' => $this->trans('Store name', [], 'Admin.Advparameters.Feature'),
'filter_key' => 's!name',
],
'url' => [
'title' => $this->trans('URL', [], 'Admin.Global'),
'filter_key' => 'url',
'havingFilter' => true,
'remove_onclick' => true,
],
'main' => [
'title' => $this->trans('Is it the main URL?', [], 'Admin.Advparameters.Feature'),
'align' => 'center',
'activeVisu' => 'main',
'active' => 'main',
'type' => 'bool',
'orderby' => false,
'filter_key' => 'main',
'class' => 'fixed-width-md',
],
'active' => [
'title' => $this->trans('Enabled', [], 'Admin.Global'),
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'orderby' => false,
'filter_key' => 'active',
'class' => 'fixed-width-md',
],
];
}
public function viewAccess($disable = false)
{
return Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE');
}
public function renderList()
{
// We will hide "delete" action for all URLs that are set as main ones for the store
$this->addRowActionSkipList('delete', $this->getUnremovableUrls());
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = 's.name AS shop_name, CONCAT(\'http://\', a.domain, a.physical_uri, a.virtual_uri) AS url';
$this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'shop` s ON (s.id_shop = a.id_shop)';
if ($id_shop = (int) Tools::getValue('id_shop')) {
$this->_where = 'AND a.id_shop = ' . $id_shop;
}
$this->_use_found_rows = false;
return parent::renderList();
}
/**
* Returns a list of URLs that are selected as main ones for some store.
*
* @return array of URLs that are selected as main
*/
protected function getUnremovableUrls()
{
return array_column(
Db::getInstance()->executeS('SELECT id_shop_url FROM ' . _DB_PREFIX_ . 'shop_url WHERE main = 1'),
'id_shop_url'
);
}
/**
* @return string|void
*
* @throws PrestaShopException
* @throws SmartyException
*/
public function renderForm()
{
$update_htaccess = Tools::modRewriteActive() && ((file_exists('.htaccess') && is_writable('.htaccess')) || is_writable(dirname('.htaccess')));
$this->multiple_fieldsets = true;
if (!$update_htaccess) {
$desc_virtual_uri = [
'<span class="warning_mod_rewrite">' . $this->trans('If you want to add a virtual URL, you need to activate URL rewriting on your web server and enable Friendly URL option.', [], 'Admin.Advparameters.Help') . '</span>',
];
} else {
$desc_virtual_uri = [
$this->trans('You can use this option if you want to create a store with a URL that doesn\'t exist on your server (e.g. if you want your store to be available with the URL www.example.com/my-store/shoes/, you have to set shoes/ in this field, assuming that my-store/ is your Physical URL).', [], 'Admin.Advparameters.Help'),
'<strong>' . $this->trans('URL rewriting must be activated on your server to use this feature.', [], 'Admin.Advparameters.Help') . '</strong>',
];
}
$this->fields_form = [
[
'form' => [
'legend' => [
'title' => $this->trans('URL options', [], 'Admin.Advparameters.Feature'),
'icon' => 'icon-cogs',
],
'input' => [
[
'type' => 'select',
'label' => $this->trans('Store', [], 'Admin.Global'),
'name' => 'id_shop',
'onchange' => 'checkMainUrlInfo(this.value);',
'options' => [
'optiongroup' => [
'query' => Shop::getTree(),
'label' => 'name',
],
'options' => [
'query' => 'shops',
'id' => 'id_shop',
'name' => 'name',
],
],
],
[
'type' => 'switch',
'label' => $this->trans('Is it the main URL for this store?', [], 'Admin.Advparameters.Feature'),
'name' => 'main',
'is_bool' => true,
'class' => 't',
'values' => [
[
'id' => 'main_on',
'value' => 1,
],
[
'id' => 'main_off',
'value' => 0,
],
],
'desc' => [
$this->trans('If you set this URL as the Main URL for the selected shop, all URLs set to this shop will be redirected to this URL (you can only have one Main URL per shop).', [], 'Admin.Advparameters.Help'),
[
'text' => $this->trans('Since the selected shop has no main URL, you have to set this URL as the Main URL.', [], 'Admin.Advparameters.Help'),
'id' => 'mainUrlInfo',
],
[
'text' => $this->trans('The selected shop already has a Main URL. Therefore, if you set this one as the Main URL, the older Main URL will be set as a regular URL.', [], 'Admin.Advparameters.Help'),
'id' => 'mainUrlInfoExplain',
],
],
],
[
'type' => 'switch',
'label' => $this->trans('Enabled', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'is_bool' => true,
'class' => 't',
'values' => [
[
'id' => 'active_on',
'value' => 1,
],
[
'id' => 'active_off',
'value' => 0,
],
],
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
],
],
[
'form' => [
'legend' => [
'title' => $this->trans('Store URL', [], 'Admin.Advparameters.Feature'),
'icon' => 'icon-shopping-cart',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Domain', [], 'Admin.Advparameters.Feature'),
'name' => 'domain',
'size' => 50,
],
[
'type' => 'text',
'label' => $this->trans('SSL Domain', [], 'Admin.Advparameters.Feature'),
'name' => 'domain_ssl',
'size' => 50,
],
[
'type' => 'text',
'label' => $this->trans('Physical URL', [], 'Admin.Advparameters.Feature'),
'name' => 'physical_uri',
'desc' => $this->trans('This is the physical folder for your store on the web server. Leave this field empty if your store is installed on the root path. For instance, if your store is available at www.example.com/my-store/, you must input my-store/ in this field.', [], 'Admin.Advparameters.Help'),
'size' => 50,
],
[
'type' => 'text',
'label' => $this->trans('Virtual URL', [], 'Admin.Advparameters.Feature'),
'name' => 'virtual_uri',
'desc' => $desc_virtual_uri,
'size' => 50,
'hint' => (!$update_htaccess) ? $this->trans('Warning: URL rewriting (e.g. mod_rewrite for Apache) seems to be disabled. If your Virtual URL doesn\'t work, please check with your hosting provider on how to activate URL rewriting.', [], 'Admin.Advparameters.Help') : null,
],
[
'type' => 'text',
'label' => $this->trans('Final URL', [], 'Admin.Advparameters.Feature'),
'name' => 'final_url',
'size' => 76,
'readonly' => true,
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
],
],
];
if (!($obj = $this->loadObject(true))) {
return;
}
self::$currentIndex = self::$currentIndex . ($obj->id ? '&shop_id=' . (int) $obj->id_shop : '');
$current_shop = Shop::initialize();
$list_shop_with_url = [];
foreach (Shop::getShops(false, null, true) as $id) {
$list_shop_with_url[$id] = (bool) count(ShopUrl::getShopUrls($id));
}
$this->tpl_form_vars = [
'js_shop_url' => json_encode($list_shop_with_url),
];
$this->fields_value = [
'domain' => trim(Validate::isLoadedObject($obj) ? $this->getFieldValue($obj, 'domain') : $current_shop->domain),
'domain_ssl' => trim(Validate::isLoadedObject($obj) ? $this->getFieldValue($obj, 'domain_ssl') : $current_shop->domain_ssl),
'physical_uri' => trim(Validate::isLoadedObject($obj) ? $this->getFieldValue($obj, 'physical_uri') : $current_shop->physical_uri),
'active' => trim(Validate::isLoadedObject($obj) ? $this->getFieldValue($obj, 'active') : true),
];
return parent::renderForm();
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
if ($this->display != 'add' && $this->display != 'edit') {
if ($this->id_object) {
$this->loadObject();
}
if (!$this->id_shop && $this->object && $this->object->id_shop) {
$this->id_shop = $this->object->id_shop;
}
$this->page_header_toolbar_btn['edit'] = [
'desc' => $this->trans('Edit this store', [], 'Admin.Advparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShop') . '&updateshop&shop_id=' . (int) $this->id_shop,
];
$this->page_header_toolbar_btn['new'] = [
'desc' => $this->trans('Add a new URL', [], 'Admin.Advparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShopUrl') . '&add' . $this->table . '&shop_id=' . (int) $this->id_shop,
];
}
}
public function initToolbar()
{
parent::initToolbar();
if ($this->display != 'add' && $this->display != 'edit') {
if ($this->id_object) {
$this->loadObject();
}
if (!$this->id_shop && $this->object && $this->object->id_shop) {
$this->id_shop = $this->object->id_shop;
}
$this->toolbar_btn['new'] = [
'desc' => $this->trans('Add a new URL', [], 'Admin.Advparameters.Feature'),
'href' => $this->context->link->getAdminLink('AdminShopUrl') . '&add' . $this->table . '&shop_id=' . (int) $this->id_shop,
];
}
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
parent::initContent();
$this->addJqueryPlugin('cooki-plugin');
$data = Shop::getTree();
foreach ($data as $group_key => $group) {
foreach ($group['shops'] as $shop_key => $shop) {
$current_shop = new Shop($shop['id_shop']);
$urls = $current_shop->getUrls();
foreach ($urls as $url) {
$title = $url['domain'] . $url['physical_uri'] . $url['virtual_uri'];
if (strlen($title) > 23) {
$title = substr($title, 0, 23) . '...';
}
$url['name'] = $title;
$data[$group_key][$shop_key]['urls'][$url['id_shop_url']] = $url;
}
}
}
$shops_tree = new HelperTreeShops('shops-tree', $this->trans('Multistore tree', [], 'Admin.Advparameters.Feature'));
$shops_tree->setNodeFolderTemplate('shop_tree_node_folder.tpl')->setNodeItemTemplate('shop_tree_node_item.tpl')
->setHeaderTemplate('shop_tree_header.tpl')->setActions([
new TreeToolbarLink(
'Collapse all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'collapseAll\'); return false;',
'icon-collapse-alt'
),
new TreeToolbarLink(
'Expand all',
'#',
'$(\'#' . $shops_tree->getId() . '\').tree(\'expandAll\'); return false;',
'icon-expand-alt'
),
])
->setAttribute('url_shop_group', $this->context->link->getAdminLink('AdminShopGroup'))
->setAttribute('url_shop', $this->context->link->getAdminLink('AdminShop'))
->setAttribute('url_shop_url', $this->context->link->getAdminLink('AdminShopUrl'))
->setData($data);
$shops_tree = $shops_tree->render(null, false, false);
if (!$this->display && $this->id_shop) {
$shop = new Shop($this->id_shop);
$this->toolbar_title[] = $shop->name;
}
$this->context->smarty->assign([
'toolbar_scroll' => 1,
'toolbar_btn' => $this->toolbar_btn,
'title' => $this->toolbar_title,
'shops_tree' => $shops_tree,
]);
}
public function postProcess()
{
$token = Tools::getValue('token') ? Tools::getValue('token') : $this->token;
$result = true;
if ((Tools::isSubmit('status' . $this->table) || Tools::isSubmit('status')) && Tools::getValue($this->identifier)) {
if ($this->access('edit')) {
if (Validate::isLoadedObject($object = $this->loadObject())) {
/** @var ShopUrl $object */
if ($object->main) {
$this->errors[] = $this->trans('You cannot disable the Main URL.', [], 'Admin.Notifications.Error');
} elseif ($object->toggleStatus()) {
Tools::redirectAdmin(self::$currentIndex . '&conf=5&token=' . $token);
} else {
$this->errors[] = $this->trans('An error occurred while updating the status.', [], 'Admin.Notifications.Error');
}
} else {
$this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error');
}
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('main' . $this->table) && Tools::getValue($this->identifier)) {
if ($this->access('edit')) {
if (Validate::isLoadedObject($object = $this->loadObject())) {
/** @var ShopUrl $object */
if (!$object->main) {
$result = $object->setMain();
Tools::redirectAdmin(self::$currentIndex . '&conf=4&token=' . $token);
} else {
$this->errors[] = $this->trans('You cannot change a main URL to a non-main URL. You have to set another URL as your Main URL for the selected shop.', [], 'Admin.Notifications.Error');
}
} else {
$this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error');
}
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
} else {
$result = parent::postProcess();
}
if ($this->redirect_after) {
$this->redirect_after .= '&shop_id=' . (int) $this->id_shop;
}
return $result;
}
public function processSave()
{
/** @var ShopUrl $object */
$object = $this->loadObject(true);
if ($object->canAddThisUrl(Tools::getValue('domain'), Tools::getValue('domain_ssl'), Tools::getValue('physical_uri'), Tools::getValue('virtual_uri'))) {
$this->errors[] = $this->trans('A shop URL that uses this domain already exists.', [], 'Admin.Notifications.Error');
}
$unallowed = str_replace('/', '', Tools::getValue('virtual_uri'));
if ($unallowed == 'c' || $unallowed == 'img' || is_numeric($unallowed) || !preg_match('/^[a-z\d\-_]*$/i', $unallowed)) {
$this->errors[] = $this->trans(
'A shop virtual URL cannot be "%URL%"',
[
'%URL%' => $unallowed,
],
'Admin.Notifications.Error'
);
}
$return = parent::processSave();
if (!$this->errors) {
Tools::generateHtaccess();
Tools::generateRobotsFile();
Tools::clearSmartyCache();
Media::clearCache();
}
return $return;
}
public function processAdd()
{
/** @var ShopUrl $object */
$object = $this->loadObject(true);
if ($object->canAddThisUrl(Tools::getValue('domain'), Tools::getValue('domain_ssl'), Tools::getValue('physical_uri'), Tools::getValue('virtual_uri'))) {
$this->errors[] = $this->trans('A shop URL that uses this domain already exists.', [], 'Admin.Notifications.Error');
}
if (Tools::getValue('main') && !Tools::getValue('active')) {
$this->errors[] = $this->trans('You cannot disable the Main URL.', [], 'Admin.Notifications.Error');
}
return parent::processAdd();
}
public function processUpdate()
{
$this->redirect_shop_url = false;
$current_url = parse_url($_SERVER['REQUEST_URI']);
if (trim(dirname(dirname($current_url['path'])), '/') == trim($this->object->getBaseURI(), '/')) {
$this->redirect_shop_url = true;
}
/** @var ShopUrl $object */
$object = $this->loadObject(true);
if ($object->main && !Tools::getValue('main')) {
$this->errors[] = $this->trans('You cannot change a main URL to a non-main URL. You have to set another URL as your Main URL for the selected shop.', [], 'Admin.Notifications.Error');
}
if (($object->main || Tools::getValue('main')) && !Tools::getValue('active')) {
$this->errors[] = $this->trans('You cannot disable the Main URL.', [], 'Admin.Notifications.Error');
}
return parent::processUpdate();
}
/**
* @param ShopUrl $object
*
* @return void|bool
*/
protected function afterUpdate($object)
{
if ($object->id && Tools::getValue('main')) {
$object->setMain();
}
if ($this->redirect_shop_url) {
$this->redirect_after = $this->context->link->getAdminLink('AdminShopUrl');
}
}
/**
* @param string $token
* @param int $id
* @param string $name
*
* @return mixed
*/
public function displayDeleteLink($token, $id, $name = null)
{
$tpl = $this->createTemplate('helpers/list/list_action_delete.tpl');
if (!array_key_exists('Delete', self::$cache_lang)) {
self::$cache_lang['Delete'] = $this->trans('Delete', [], 'Admin.Actions');
}
if (!array_key_exists('DeleteItem', self::$cache_lang)) {
self::$cache_lang['DeleteItem'] = $this->trans('Delete selected item?', [], 'Admin.Notifications.Warning');
}
if (!array_key_exists('Name', self::$cache_lang)) {
self::$cache_lang['Name'] = $this->trans('Name:', [], 'Admin.Global');
}
if (null !== $name) {
$name = '\n\n' . self::$cache_lang['Name'] . ' ' . $name;
}
$data = [
$this->identifier => $id,
'href' => self::$currentIndex . '&' . $this->identifier . '=' . $id . '&delete' . $this->table . '&shop_id=' . (int) $this->id_shop . '&token=' . ($token != null ? $token : $this->token),
'action' => self::$cache_lang['Delete'],
];
if ($this->specificConfirmDelete !== false) {
$data['confirm'] = null !== $this->specificConfirmDelete ? '\r' . $this->specificConfirmDelete : self::$cache_lang['DeleteItem'] . $name;
}
$tpl->assign(array_merge($this->tpl_delete_link_vars, $data));
return $tpl->fetch();
}
}

View File

@@ -0,0 +1,381 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property SpecificPriceRule $object
*/
class AdminSpecificPriceRuleControllerCore extends AdminController
{
public $list_reduction_type;
/**
* @var int
*/
public $id_shop;
public function __construct()
{
$this->bootstrap = true;
$this->table = 'specific_price_rule';
$this->className = 'SpecificPriceRule';
$this->lang = false;
$this->multishop_context = Shop::CONTEXT_ALL;
parent::__construct();
/* if $_GET['id_shop'] is transmitted, virtual url can be loaded in config.php, so we wether transmit shop_id in hrefs */
if ($this->id_shop = (int) Tools::getValue('shop_id')) {
$_GET['id_shop'] = $this->id_shop;
$_POST['id_shop'] = $this->id_shop;
}
$this->list_reduction_type = [
'percentage' => $this->trans('Percentage', [], 'Admin.Global'),
'amount' => $this->trans('Amount', [], 'Admin.Global'),
];
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = 's.name shop_name, cul.name as currency_name, cl.name country_name, gl.name group_name';
$this->_join = 'LEFT JOIN ' . _DB_PREFIX_ . 'shop s ON (s.id_shop = a.id_shop)
LEFT JOIN ' . _DB_PREFIX_ . 'currency_lang cul ON (cul.id_currency = a.id_currency AND cul.id_lang=' . (int) $this->context->language->id . ')
LEFT JOIN ' . _DB_PREFIX_ . 'country_lang cl ON (cl.id_country = a.id_country AND cl.id_lang=' . (int) $this->context->language->id . ')
LEFT JOIN ' . _DB_PREFIX_ . 'group_lang gl ON (gl.id_group = a.id_group AND gl.id_lang=' . (int) $this->context->language->id . ')';
$this->_use_found_rows = false;
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
'icon' => 'icon-trash',
],
];
$this->fields_list = [
'id_specific_price_rule' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
'filter_key' => 'a!name',
'width' => 'auto',
],
'id_currency' => [
'title' => $this->trans('Currency', [], 'Admin.Global'),
'align' => 'center',
'filter_key' => 'cul!name',
],
'country_name' => [
'title' => $this->trans('Country', [], 'Admin.Global'),
'align' => 'center',
'filter_key' => 'cl!name',
],
'group_name' => [
'title' => $this->trans('Group', [], 'Admin.Global'),
'align' => 'center',
'filter_key' => 'gl!name',
],
'from_quantity' => [
'title' => $this->trans('From quantity', [], 'Admin.Catalog.Feature'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'reduction_type' => [
'title' => $this->trans('Reduction type', [], 'Admin.Catalog.Feature'),
'align' => 'center',
'type' => 'select',
'filter_key' => 'a!reduction_type',
'list' => $this->list_reduction_type,
],
'reduction' => [
'title' => $this->trans('Reduction', [], 'Admin.Catalog.Feature'),
'align' => 'center',
'type' => 'decimal',
'class' => 'fixed-width-xs',
],
'from' => [
'title' => $this->trans('Beginning', [], 'Admin.Catalog.Feature'),
'align' => 'right',
'type' => 'datetime',
'filter_key' => 'a!from',
'order_key' => 'a!from',
],
'to' => [
'title' => $this->trans('End', [], 'Admin.Catalog.Feature'),
'align' => 'right',
'type' => 'datetime',
'filter_key' => 'a!to',
'order_key' => 'a!to',
],
];
if (Shop::isFeatureActive()) {
$this->fields_list = Tools::arrayInsertElementAfterKey(
$this->fields_list,
'name',
'shop_name',
[
'title' => $this->trans('Store', [], 'Admin.Global'),
'filter_key' => 's!name',
]
);
}
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_specific_price_rule'] = [
'href' => self::$currentIndex . '&addspecific_price_rule&token=' . $this->token,
'desc' => $this->trans('Add new catalog price rule', [], 'Admin.Catalog.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false)
{
parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop);
foreach ($this->_list as $k => $list) {
if (null !== $this->_list[$k]['id_currency']) {
$currency = new Currency(
(int) $this->_list[$k]['id_currency'],
(int) $this->context->language->id,
(int) $this->context->shop->id
);
$this->_list[$k]['id_currency'] = Validate::isLoadedObject($currency) ? $currency->getName() : null;
}
if ($list['reduction_type'] == 'amount') {
$this->_list[$k]['reduction_type'] = $this->list_reduction_type['amount'];
} elseif ($list['reduction_type'] == 'percentage') {
$this->_list[$k]['reduction_type'] = $this->list_reduction_type['percentage'];
}
}
}
public function renderForm()
{
if (!$this->object->id) {
$this->object->price = -1;
}
$this->fields_form = [
'legend' => [
'title' => $this->trans('Catalog price rules', [], 'Admin.Catalog.Feature'),
'icon' => 'icon-dollar',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'maxlength' => 255,
'required' => true,
],
[
'type' => 'select',
'label' => $this->trans('Store', [], 'Admin.Global'),
'name' => 'shop_id',
'options' => [
'query' => Shop::getShops(),
'id' => 'id_shop',
'name' => 'name',
],
'condition' => Shop::isFeatureActive(),
'default_value' => Shop::getContextShopID(),
],
[
'type' => 'select',
'label' => $this->trans('Currency', [], 'Admin.Global'),
'name' => 'id_currency',
'options' => [
'query' => array_merge([0 => ['id_currency' => 0, 'name' => $this->trans('All currencies', [], 'Admin.Global')]], Currency::getCurrencies(false, true, true)),
'id' => 'id_currency',
'name' => 'name',
],
],
[
'type' => 'select',
'label' => $this->trans('Country', [], 'Admin.Global'),
'name' => 'id_country',
'options' => [
'query' => array_merge([0 => ['id_country' => 0, 'name' => $this->trans('All countries', [], 'Admin.Global')]], Country::getCountries((int) $this->context->language->id)),
'id' => 'id_country',
'name' => 'name',
],
],
[
'type' => 'select',
'label' => $this->trans('Group', [], 'Admin.Global'),
'name' => 'id_group',
'options' => [
'query' => array_merge([0 => ['id_group' => 0, 'name' => $this->trans('All groups', [], 'Admin.Global')]], Group::getGroups((int) $this->context->language->id)),
'id' => 'id_group',
'name' => 'name',
],
],
[
'type' => 'text',
'label' => $this->trans('From quantity', [], 'Admin.Catalog.Feature'),
'name' => 'from_quantity',
'maxlength' => 10,
'required' => true,
],
[
'type' => 'text',
'label' => $this->trans('Price (tax excl.)', [], 'Admin.Catalog.Feature'),
'name' => 'price',
'disabled' => ($this->object->price == -1 ? 1 : 0),
'maxlength' => 10,
'suffix' => $this->context->currency->getSign('right'),
],
[
'type' => 'checkbox',
'name' => 'leave_bprice',
'values' => [
'query' => [
[
'id' => 'on',
'name' => $this->trans('Leave initial price', [], 'Admin.Catalog.Feature'),
'val' => '1',
'checked' => '1',
],
],
'id' => 'id',
'name' => 'name',
],
],
[
'type' => 'datetime',
'label' => $this->trans('From', [], 'Admin.Global'),
'name' => 'from',
],
[
'type' => 'datetime',
'label' => $this->trans('To', [], 'Admin.Global'),
'name' => 'to',
],
[
'type' => 'select',
'label' => $this->trans('Reduction type', [], 'Admin.Catalog.Feature'),
'name' => 'reduction_type',
'options' => [
'query' => [['reduction_type' => 'amount', 'name' => $this->trans('Amount', [], 'Admin.Global')], ['reduction_type' => 'percentage', 'name' => $this->trans('Percentage', [], 'Admin.Global')]],
'id' => 'reduction_type',
'name' => 'name',
],
],
[
'type' => 'select',
'label' => $this->trans('Reduction with or without taxes', [], 'Admin.Catalog.Feature'),
'name' => 'reduction_tax',
'align' => 'center',
'options' => [
'query' => [
['lab' => $this->trans('Tax included', [], 'Admin.Global'), 'val' => 1],
['lab' => $this->trans('Tax excluded', [], 'Admin.Global'), 'val' => 0],
],
'id' => 'val',
'name' => 'lab',
],
],
[
'type' => 'text',
'label' => $this->trans('Reduction', [], 'Admin.Catalog.Feature'),
'name' => 'reduction',
'required' => true,
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
$value = $this->getFieldValue($this->object, 'price');
if ($value !== '' && $value != -1) {
$price = number_format($value, 6);
} else {
$price = '';
}
$this->fields_value = [
'price' => $price,
'from_quantity' => (($value = $this->getFieldValue($this->object, 'from_quantity')) ? $value : 1),
'reduction' => number_format(($value = $this->getFieldValue($this->object, 'reduction')) ? $value : 0, 6),
'leave_bprice_on' => $price ? 0 : 1,
'shop_id' => (($value = $this->getFieldValue($this->object, 'id_shop')) ? $value : 1),
];
$attribute_groups = [];
$attributes = ProductAttribute::getAttributes((int) $this->context->language->id);
foreach ($attributes as $attribute) {
if (!isset($attribute_groups[$attribute['id_attribute_group']])) {
$attribute_groups[$attribute['id_attribute_group']] = [
'id_attribute_group' => $attribute['id_attribute_group'],
'name' => $attribute['attribute_group'],
];
}
$attribute_groups[$attribute['id_attribute_group']]['attributes'][] = [
'id_attribute' => $attribute['id_attribute'],
'name' => $attribute['name'],
];
}
$features = Feature::getFeatures((int) $this->context->language->id);
foreach ($features as &$feature) {
$feature['values'] = FeatureValue::getFeatureValuesWithLang((int) $this->context->language->id, $feature['id_feature'], true);
}
$this->tpl_form_vars = [
'manufacturers' => Manufacturer::getManufacturers(false, (int) $this->context->language->id, true, false, false, false, true),
'suppliers' => Supplier::getSuppliers(),
'attributes_group' => $attribute_groups,
'features' => $features,
'categories' => Category::getSimpleCategories((int) $this->context->language->id),
'conditions' => $this->object->getConditions(),
'is_multishop' => Shop::isFeatureActive(),
];
return parent::renderForm();
}
public function processSave()
{
$_POST['price'] = Tools::getValue('leave_bprice_on') ? '-1' : Tools::getValue('price');
/** @var SpecificPriceRule $object */
$object = parent::processSave();
if (Validate::isLoadedObject($object)) {
$object->deleteConditions();
foreach ($_POST as $key => $values) {
if (preg_match('/^condition_group_([0-9]+)$/Ui', $key, $condition_group)) {
$conditions = [];
foreach ($values as $value) {
$condition = explode('_', $value);
$conditions[] = ['type' => $condition[0], 'value' => $condition[1]];
}
$object->addConditions($conditions);
}
}
$object->apply();
return $object;
}
}
public function postProcess()
{
Tools::clearSmartyCache();
return parent::postProcess();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,299 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
abstract class AdminStatsTabControllerCore extends AdminController
{
public function init()
{
parent::init();
$this->bootstrap = true;
$this->action = 'view';
$this->display = 'view';
}
/**
* AdminController::initContent() override.
*
* @see AdminController::initContent()
*/
public function initContent()
{
if ($this->ajax) {
return;
}
$this->toolbar_title = $this->trans('Stats', [], 'Admin.Stats.Feature');
if ($this->display == 'view') {
// Some controllers use the view action without an object
if ($this->className) {
$this->loadObject(true);
}
$this->content .= $this->renderView();
}
$this->content .= $this->displayMenu();
$this->content .= $this->displayCalendar();
$this->content .= $this->displayStats();
$this->context->smarty->assign([
'content' => $this->content,
]);
}
public function initPageHeaderToolbar()
{
parent::initPageHeaderToolbar();
unset($this->page_header_toolbar_btn['back']);
}
public function displayCalendar()
{
return AdminStatsTabController::displayCalendarForm([
'Calendar' => $this->trans('Calendar', [], 'Admin.Global'),
'Day' => $this->trans('Day', [], 'Admin.Global'),
'Month' => $this->trans('Month', [], 'Admin.Global'),
'Year' => $this->trans('Year', [], 'Admin.Global'),
'From' => $this->trans('From:', [], 'Admin.Global'),
'To' => $this->trans('To:', [], 'Admin.Global'),
'Save' => $this->trans('Save', [], 'Admin.Global'),
], $this->token);
}
public static function displayCalendarForm($translations, $token, $action = null, $table = null, $identifier = null, $id = null)
{
$context = Context::getContext();
$tpl = $context->controller->createTemplate('calendar.tpl');
$context->controller->addJqueryUI('ui.datepicker');
if ($identifier === null && Tools::getValue('module')) {
$identifier = 'module';
$id = Tools::getValue('module');
}
$action = Context::getContext()->link->getAdminLink('AdminStats');
$action .= ($action && $table ? '&' . Tools::safeOutput($action) : '');
$action .= ($identifier && $id ? '&' . Tools::safeOutput($identifier) . '=' . (int) $id : '');
$module = Tools::getValue('module');
$action .= ($module ? '&module=' . Tools::safeOutput($module) : '');
$action .= (($id_product = Tools::getValue('id_product')) ? '&id_product=' . Tools::safeOutput($id_product) : '');
$tpl->assign([
'current' => self::$currentIndex,
'token' => $token,
'action' => $action,
'table' => $table,
'identifier' => $identifier,
'id' => $id,
'translations' => $translations,
'datepickerFrom' => Tools::getValue('datepickerFrom', $context->employee->stats_date_from),
'datepickerTo' => Tools::getValue('datepickerTo', $context->employee->stats_date_to),
]);
return $tpl->fetch();
}
/* Not used anymore, but still work */
protected function displayEngines()
{
$tpl = $this->createTemplate('engines.tpl');
$autoclean_period = [
'never' => $this->trans('Never', [], 'Admin.Global'),
'week' => $this->trans('Week', [], 'Admin.Global'),
'month' => $this->trans('Month', [], 'Admin.Global'),
'year' => $this->trans('Year', [], 'Admin.Global'),
];
$tpl->assign([
'current' => self::$currentIndex,
'token' => $this->token,
'graph_engine' => Configuration::get('PS_STATS_RENDER'),
'grid_engine' => Configuration::get('PS_STATS_GRID_RENDER'),
'auto_clean' => Configuration::get('PS_STATS_OLD_CONNECT_AUTO_CLEAN'),
'array_graph_engines' => ModuleGraphEngine::getGraphEngines(),
'array_grid_engines' => ModuleGridEngine::getGridEngines(),
'array_auto_clean' => $autoclean_period,
]);
return $tpl->fetch();
}
public function displayMenu()
{
$tpl = $this->createTemplate('menu.tpl');
$modules = $this->getModules();
$module_instance = [];
foreach ($modules as $m => $module) {
if ($module_instance[$module['name']] = Module::getInstanceByName($module['name'])) {
$modules[$m]['displayName'] = $module_instance[$module['name']]->displayName;
} else {
unset(
$module_instance[$module['name']],
$modules[$m]
);
}
}
uasort($modules, [$this, 'checkModulesNames']);
$tpl->assign([
'current' => self::$currentIndex,
'current_module_name' => Tools::getValue('module', 'statsforecast'),
'token' => $this->token,
'modules' => $modules,
'module_instance' => $module_instance,
]);
return $tpl->fetch();
}
public function checkModulesNames($a, $b): int
{
return strcasecmp($a['displayName'], $b['displayName']);
}
protected function getModules()
{
$moduleList = Hook::getHookModuleExecList('displayAdminStatsModules');
if (true === is_array($moduleList)) {
return array_map(
function ($moduleArray) {
return ['name' => $moduleArray['module']];
},
$moduleList
);
}
return [];
}
public function displayStats()
{
$tpl = $this->createTemplate('stats.tpl');
if ((!($module_name = Tools::getValue('module')) || !Validate::isModuleName($module_name)) && ($module_instance = Module::getInstanceByName('statsforecast')) && $module_instance->active) {
$module_name = 'statsforecast';
}
if ($module_name) {
$_GET['module'] = $module_name;
if (!isset($module_instance)) {
$module_instance = Module::getInstanceByName($module_name);
}
if ($module_instance && $module_instance->active) {
// Hook called only for the module concerned
$hook = Hook::exec('displayAdminStatsModules', [], $module_instance->id);
}
}
$tpl->assign([
'module_name' => $module_name,
'module_instance' => isset($module_instance) ? $module_instance : null,
'hook' => isset($hook) ? $hook : null,
]);
return $tpl->fetch();
}
public function postProcess()
{
$this->context = Context::getContext();
$this->processDateRange();
if (Tools::getValue('submitSettings')) {
if ($this->access('edit')) {
self::$currentIndex .= '&module=' . Tools::getValue('module');
Configuration::updateValue('PS_STATS_RENDER', Tools::getValue('PS_STATS_RENDER', Configuration::get('PS_STATS_RENDER')));
Configuration::updateValue('PS_STATS_GRID_RENDER', Tools::getValue('PS_STATS_GRID_RENDER', Configuration::get('PS_STATS_GRID_RENDER')));
Configuration::updateValue('PS_STATS_OLD_CONNECT_AUTO_CLEAN', Tools::getValue('PS_STATS_OLD_CONNECT_AUTO_CLEAN', Configuration::get('PS_STATS_OLD_CONNECT_AUTO_CLEAN')));
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
}
}
public function processDateRange()
{
if (Tools::isSubmit('submitDatePicker')) {
if ((!Validate::isDate($from = Tools::getValue('datepickerFrom')) || !Validate::isDate($to = Tools::getValue('datepickerTo'))) || (strtotime($from) > strtotime($to))) {
$this->errors[] = $this->trans('The specified date is invalid.', [], 'Admin.Stats.Notification');
}
}
if (Tools::isSubmit('submitDateDay')) {
$from = date('Y-m-d');
$to = date('Y-m-d');
}
if (Tools::isSubmit('submitDateDayPrev')) {
$yesterday = time() - 60 * 60 * 24;
$from = date('Y-m-d', $yesterday);
$to = date('Y-m-d', $yesterday);
}
if (Tools::isSubmit('submitDateMonth')) {
$from = date('Y-m-01');
$to = date('Y-m-t');
}
if (Tools::isSubmit('submitDateMonthPrev')) {
$m = (date('m') == 1 ? 12 : date('m') - 1);
$y = ($m == 12 ? date('Y') - 1 : date('Y'));
$from = $y . '-' . $m . '-01';
$to = $y . '-' . $m . date('-t', mktime(12, 0, 0, $m, 15, $y));
}
if (Tools::isSubmit('submitDateYear')) {
$from = date('Y-01-01');
$to = date('Y-12-31');
}
if (Tools::isSubmit('submitDateYearPrev')) {
$from = (date('Y') - 1) . date('-01-01');
$to = (date('Y') - 1) . date('-12-31');
}
if (isset($from, $to) && !count($this->errors)) {
$this->context->employee->stats_date_from = $from;
$this->context->employee->stats_date_to = $to;
$this->context->employee->update();
if (!$this->isXmlHttpRequest()) {
Tools::redirectAdmin($_SERVER['REQUEST_URI']);
}
}
}
public function ajaxProcessSetDashboardDateRange()
{
$this->processDateRange();
if ($this->isXmlHttpRequest()) {
if (is_array($this->errors) && count($this->errors)) {
die(json_encode(
[
'has_errors' => true,
'errors' => [$this->errors],
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to, ]
));
} else {
die(json_encode(
[
'has_errors' => false,
'date_from' => $this->context->employee->stats_date_from,
'date_to' => $this->context->employee->stats_date_to, ]
));
}
}
}
protected function getDate()
{
$year = isset($this->context->cookie->stats_year) ? $this->context->cookie->stats_year : date('Y');
$month = isset($this->context->cookie->stats_month) ? sprintf('%02d', $this->context->cookie->stats_month) : '%';
$day = isset($this->context->cookie->stats_day) ? sprintf('%02d', $this->context->cookie->stats_day) : '%';
return $year . '-' . $month . '-' . $day;
}
}

View File

@@ -0,0 +1,598 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Core\Image\ImageFormatConfiguration;
/**
* @property Store $object
*/
class AdminStoresControllerCore extends AdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = 'store';
$this->className = 'Store';
$this->lang = false;
$this->toolbar_scroll = false;
parent::__construct();
if (!Tools::getValue('realedit')) {
$this->deleted = false;
}
$this->fieldImageSettings = [
'name' => 'image',
'dir' => 'st',
];
$this->fields_list = [
'id_store' => ['title' => $this->trans('ID', [], 'Admin.Global'), 'align' => 'center', 'class' => 'fixed-width-xs'],
'name' => ['title' => $this->trans('Name', [], 'Admin.Global'), 'filter_key' => 'sl!name'],
'address1' => ['title' => $this->trans('Address', [], 'Admin.Global'), 'filter_key' => 'sl!address1'],
'city' => ['title' => $this->trans('City', [], 'Admin.Global')],
'postcode' => ['title' => $this->trans('Zip/Postal code', [], 'Admin.Global')],
'state' => ['title' => $this->trans('State', [], 'Admin.Global'), 'filter_key' => 'st!name'],
'country' => ['title' => $this->trans('Country', [], 'Admin.Global'), 'filter_key' => 'cl!name'],
'phone' => ['title' => $this->trans('Phone', [], 'Admin.Global')],
'fax' => ['title' => $this->trans('Fax', [], 'Admin.Global')],
'active' => ['title' => $this->trans('Enabled', [], 'Admin.Global'), 'align' => 'center', 'active' => 'status', 'type' => 'bool', 'orderby' => false],
];
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
'icon' => 'icon-trash',
],
];
$this->_buildOrderedFieldsShop($this->_getDefaultFieldsContent());
}
public function renderOptions()
{
// Set toolbar options
$this->display = 'options';
$this->show_toolbar = true;
$this->toolbar_scroll = true;
$this->initToolbar();
return parent::renderOptions();
}
public function initToolbar()
{
parent::initToolbar();
if ($this->display == 'options') {
unset($this->toolbar_btn['new']);
} elseif ($this->display != 'add' && $this->display != 'edit') {
unset($this->toolbar_btn['save']);
}
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_store'] = [
'href' => self::$currentIndex . '&addstore&token=' . $this->token,
'desc' => $this->trans('Add new store', [], 'Admin.Shopparameters.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function renderList()
{
// Set toolbar options
$this->display = null;
$this->initToolbar();
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = 'cl.`name` country, st.`name` state, sl.*';
$this->_join = '
LEFT JOIN `' . _DB_PREFIX_ . 'country_lang` cl
ON (cl.`id_country` = a.`id_country`
AND cl.`id_lang` = ' . (int) $this->context->language->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'state` st
ON (st.`id_state` = a.`id_state`)
LEFT JOIN `' . _DB_PREFIX_ . 'store_lang` sl
ON (sl.`id_store` = a.`id_store`
AND sl.`id_lang` = ' . (int) $this->context->language->id . ') ';
return parent::renderList();
}
/**
* @return string|void
*
* @throws PrestaShopDatabaseException
* @throws SmartyException
*/
public function renderForm()
{
if (!($obj = $this->loadObject(true))) {
return;
}
$image = _PS_STORE_IMG_DIR_ . $obj->id . '.jpg';
$image_url = ImageManager::thumbnail(
$image,
$this->table . '_' . (int) $obj->id . '.' . $this->imageType,
350,
$this->imageType,
true,
true
);
$image_size = file_exists($image) ? filesize($image) / 1000 : false;
$tmp_addr = new Address();
$res = $tmp_addr->getFieldsRequiredDatabase();
$required_fields = [];
foreach ($res as $row) {
$required_fields[(int) $row['id_required_field']] = $row['field_name'];
}
$this->fields_form = [
'legend' => [
'title' => $this->trans('Stores', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-home',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'lang' => true,
'required' => false,
'hint' => [
$this->trans('Store name (e.g. City Center Mall Store).', [], 'Admin.Shopparameters.Feature'),
$this->trans('Allowed characters: letters, spaces and %s', [], 'Admin.Shopparameters.Feature'),
],
],
[
'type' => 'text',
'label' => $this->trans('Address', [], 'Admin.Global'),
'name' => 'address1',
'lang' => true,
'required' => true,
],
[
'type' => 'text',
'label' => $this->trans('Address (2)', [], 'Admin.Global'),
'name' => 'address2',
'lang' => true,
],
[
'type' => 'text',
'label' => $this->trans('Zip/Postal code', [], 'Admin.Global'),
'name' => 'postcode',
'required' => in_array('postcode', $required_fields),
],
[
'type' => 'text',
'label' => $this->trans('City', [], 'Admin.Global'),
'name' => 'city',
'required' => true,
],
[
'type' => 'select',
'label' => $this->trans('Country', [], 'Admin.Global'),
'name' => 'id_country',
'required' => true,
'default_value' => (int) $this->context->country->id,
'options' => [
'query' => Country::getCountries($this->context->language->id),
'id' => 'id_country',
'name' => 'name',
],
],
[
'type' => 'select',
'label' => $this->trans('State', [], 'Admin.Global'),
'name' => 'id_state',
'required' => true,
'options' => [
'id' => 'id_state',
'name' => 'name',
'query' => null,
],
],
[
'type' => 'latitude',
'label' => $this->trans('Latitude / Longitude', [], 'Admin.Shopparameters.Feature'),
'name' => 'latitude',
'required' => true,
'maxlength' => 12,
'hint' => $this->trans('Store coordinates (e.g. 45.265469/-47.226478).', [], 'Admin.Shopparameters.Feature'),
],
[
'type' => 'text',
'label' => $this->trans('Phone', [], 'Admin.Global'),
'name' => 'phone',
],
[
'type' => 'text',
'label' => $this->trans('Fax', [], 'Admin.Global'),
'name' => 'fax',
],
[
'type' => 'text',
'label' => $this->trans('Email address', [], 'Admin.Global'),
'name' => 'email',
],
[
'type' => 'textarea',
'label' => $this->trans('Note', [], 'Admin.Global'),
'name' => 'note',
'lang' => true,
'cols' => 42,
'rows' => 4,
],
[
'type' => 'switch',
'label' => $this->trans('Active', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Whether or not to display this store.', [], 'Admin.Shopparameters.Help'),
],
[
'type' => 'file',
'label' => $this->trans('Picture', [], 'Admin.Shopparameters.Feature'),
'name' => 'image',
'display_image' => true,
'image' => $image_url ? $image_url : false,
'size' => $image_size,
'hint' => $this->trans('Storefront picture.', [], 'Admin.Shopparameters.Help'),
],
],
'hours' => [
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
if (Shop::isFeatureActive()) {
$this->fields_form['input'][] = [
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
];
}
$days = [];
$days[1] = $this->trans('Monday', [], 'Admin.Shopparameters.Feature');
$days[2] = $this->trans('Tuesday', [], 'Admin.Shopparameters.Feature');
$days[3] = $this->trans('Wednesday', [], 'Admin.Shopparameters.Feature');
$days[4] = $this->trans('Thursday', [], 'Admin.Shopparameters.Feature');
$days[5] = $this->trans('Friday', [], 'Admin.Shopparameters.Feature');
$days[6] = $this->trans('Saturday', [], 'Admin.Shopparameters.Feature');
$days[7] = $this->trans('Sunday', [], 'Admin.Shopparameters.Feature');
$hours = [];
$hours_temp = $this->getFieldValue($obj, 'hours');
if (is_array($hours_temp) && !empty($hours_temp)) {
$langs = Language::getLanguages(false);
$hours_temp = array_map('json_decode', $hours_temp);
$hours = array_map(
[$this, 'adaptHoursFormat'],
$hours_temp
);
$hours = (count($langs) > 1) ? $hours : $hours[reset($langs)['id_lang']];
}
$this->fields_value = [
'latitude' => $this->getFieldValue($obj, 'latitude') ? $this->getFieldValue($obj, 'latitude') : '',
'longitude' => $this->getFieldValue($obj, 'longitude') ? $this->getFieldValue($obj, 'longitude') : '',
'days' => $days,
'hours' => $hours,
];
$this->tpl_form_vars['states_url'] = $this->getContainer()->get('router')->generate('admin_country_states');
return parent::renderForm();
}
public function postProcess()
{
if (isset($_POST['submitAdd' . $this->table])) {
$langs = Language::getLanguages(false);
/* Cleaning fields */
foreach ($_POST as $kp => $vp) {
if (is_string($vp)) {
$_POST[$kp] = trim($vp);
}
if ('hours' === $kp) {
foreach ($vp as $day => $value) {
$_POST['hours'][$day] = is_array($value) ? array_map('trim', $_POST['hours'][$day]) : trim($value);
}
}
}
/* Rewrite latitude and longitude to 8 digits */
$_POST['latitude'] = number_format((float) $_POST['latitude'], 8);
$_POST['longitude'] = number_format((float) $_POST['longitude'], 8);
/* If the selected country does not contain states */
$id_state = (int) Tools::getValue('id_state');
$id_country = (int) Tools::getValue('id_country');
$country = new Country($id_country);
if ($id_country && !(int) $country->contains_states && $id_state) {
$this->errors[] = $this->trans('You\'ve selected a state for a country that does not contain states.', [], 'Admin.Advparameters.Notification');
}
/* If the selected country contains states, then a state have to be selected */
if ((int) $country->contains_states && !$id_state) {
$this->errors[] = $this->trans('An address located in a country containing states must have a state selected.', [], 'Admin.Shopparameters.Notification');
}
$latitude = (float) Tools::getValue('latitude');
$longitude = (float) Tools::getValue('longitude');
if (empty($latitude) || empty($longitude)) {
$this->errors[] = $this->trans('Latitude and longitude are required.', [], 'Admin.Shopparameters.Notification');
}
$postcode = Tools::getValue('postcode');
/* Check zip code format */
if ($country->zip_code_format && !$country->checkZipCode($postcode)) {
$this->errors[] = $this->trans('Your Zip/Postal code is incorrect.', [], 'Admin.Notifications.Error') . '<br />' . $this->trans('It must be entered as follows:', [], 'Admin.Notifications.Error') . ' ' . str_replace('C', $country->iso_code, str_replace('N', '0', str_replace('L', 'A', $country->zip_code_format)));
} elseif (empty($postcode) && $country->need_zip_code) {
$this->errors[] = $this->trans('A Zip/Postal code is required.', [], 'Admin.Notifications.Error');
} elseif ($postcode && !Validate::isPostCode($postcode)) {
$this->errors[] = $this->trans('The Zip/Postal code is invalid.', [], 'Admin.Notifications.Error');
}
/* Store hours */
$encodedHours = [];
foreach ($langs as $lang) {
$hours = [];
for ($i = 1; $i < 8; ++$i) {
if (1 < count($langs)) {
$hours[] = explode(' | ', $_POST['hours'][$i][$lang['id_lang']]);
unset($_POST['hours'][$i][$lang['id_lang']]);
} else {
$hours[] = explode(' | ', $_POST['hours'][$i]);
unset($_POST['hours'][$i]);
}
}
$encodedHours[$lang['id_lang']] = json_encode($hours);
}
$_POST['hours'] = (1 < count($langs)) ? $encodedHours : json_encode($hours ?? []);
}
if (!count($this->errors)) {
parent::postProcess();
} else {
$this->display = 'add';
}
}
protected function postImage($id)
{
$ret = parent::postImage($id);
/*
* Let's resolve which formats we will use for image generation.
*
* In case of .jpg images, the actual format inside is decided by ImageManager.
*/
$configuredImageFormats = $this->get(ImageFormatConfiguration::class)->getGenerationFormats();
if (($id_store = (int) Tools::getValue('id_store')) && count($_FILES) && file_exists(_PS_STORE_IMG_DIR_ . $id_store . '.jpg')) {
$images_types = ImageType::getImagesTypes('stores');
foreach ($images_types as $image_type) {
foreach ($configuredImageFormats as $imageFormat) {
ImageManager::resize(
_PS_STORE_IMG_DIR_ . $id_store . '.jpg',
_PS_STORE_IMG_DIR_ . $id_store . '-' . stripslashes($image_type['name']) . '.' . $imageFormat,
(int) $image_type['width'],
(int) $image_type['height'],
$imageFormat
);
}
}
}
return $ret;
}
protected function _getDefaultFieldsContent()
{
$this->context = Context::getContext();
$countryList = [];
$countryList[] = ['id' => '0', 'name' => $this->trans('Choose your country', [], 'Admin.Shopparameters.Feature')];
foreach (Country::getCountries($this->context->language->id) as $country) {
$countryList[] = ['id' => $country['id_country'], 'name' => $country['name']];
}
$stateList = [];
$stateList[] = ['id' => '0', 'name' => $this->trans('Choose your state (if applicable)', [], 'Admin.Shopparameters.Feature')];
foreach (State::getStates($this->context->language->id) as $state) {
$stateList[] = ['id' => $state['id_state'], 'name' => $state['name']];
}
$formFields = [
'PS_SHOP_NAME' => [
'title' => $this->trans('Store name', [], 'Admin.Shopparameters.Feature'),
'hint' => $this->trans('Displayed in emails and page titles.', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isGenericName',
'required' => true,
'type' => 'text',
'no_escape' => true,
],
'PS_SHOP_EMAIL' => ['title' => $this->trans('Shop email', [], 'Admin.Shopparameters.Feature'),
'hint' => $this->trans('Displayed in emails sent to customers.', [], 'Admin.Shopparameters.Help'),
'validation' => 'isEmail',
'required' => true,
'type' => 'text',
],
'PS_SHOP_DETAILS' => [
'title' => $this->trans('Registration number', [], 'Admin.Shopparameters.Feature'),
'hint' => $this->trans('Shop registration information (e.g. SIRET or RCS).', [], 'Admin.Shopparameters.Help'),
'validation' => 'isGenericName',
'type' => 'textarea',
'cols' => 30,
'rows' => 5,
],
'PS_SHOP_ADDR1' => [
'title' => $this->trans('Shop address line 1', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isAddress',
'type' => 'text',
],
'PS_SHOP_ADDR2' => [
'title' => $this->trans('Shop address line 2', [], 'Admin.Shopparameters.Feature'),
'validation' => 'isAddress',
'type' => 'text',
],
'PS_SHOP_CODE' => [
'title' => $this->trans('Zip/Postal code', [], 'Admin.Global'),
'validation' => 'isGenericName',
'type' => 'text',
],
'PS_SHOP_CITY' => [
'title' => $this->trans('City', [], 'Admin.Global'),
'validation' => 'isGenericName',
'type' => 'text',
],
'PS_SHOP_COUNTRY_ID' => [
'title' => $this->trans('Country', [], 'Admin.Global'),
'validation' => 'isInt',
'type' => 'select',
'list' => $countryList,
'identifier' => 'id',
'cast' => 'intval',
'defaultValue' => (int) $this->context->country->id,
],
'PS_SHOP_STATE_ID' => [
'title' => $this->trans('State', [], 'Admin.Global'),
'validation' => 'isInt',
'type' => 'select',
'list' => $stateList,
'identifier' => 'id',
'cast' => 'intval',
],
'PS_SHOP_PHONE' => [
'title' => $this->trans('Phone', [], 'Admin.Global'),
'validation' => 'isGenericName',
'type' => 'text',
],
'PS_SHOP_FAX' => [
'title' => $this->trans('Fax', [], 'Admin.Global'),
'validation' => 'isGenericName',
'type' => 'text',
],
];
return $formFields;
}
protected function _buildOrderedFieldsShop(array $formFields)
{
// You cannot do that, because the fields must be sorted for the country you've selected.
// Simple example: the current country is France, where we don't display the state. You choose "US" as a country in the form. The state is not dsplayed at the right place...
// $associatedOrderKey = array(
// 'PS_SHOP_NAME' => 'company',
// 'PS_SHOP_ADDR1' => 'address1',
// 'PS_SHOP_ADDR2' => 'address2',
// 'PS_SHOP_CITY' => 'city',
// 'PS_SHOP_STATE_ID' => 'State:name',
// 'PS_SHOP_CODE' => 'postcode',
// 'PS_SHOP_COUNTRY_ID' => 'Country:name',
// 'PS_SHOP_PHONE' => 'phone');
// $fields = array();
// $orderedFields = AddressFormat::getOrderedAddressFields(Configuration::get('PS_SHOP_COUNTRY_ID'), false, true);
// foreach ($orderedFields as $lineFields)
// if (($patterns = explode(' ', $lineFields)))
// foreach ($patterns as $pattern)
// if (($key = array_search($pattern, $associatedOrderKey)))
// $fields[$key] = $formFields[$key];
// foreach ($formFields as $key => $value)
// if (!isset($fields[$key]))
// $fields[$key] = $formFields[$key];
$fields = $formFields;
$this->fields_options['contact'] = [
'title' => $this->trans('Contact details', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-user',
'fields' => $fields,
'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions')],
];
}
public function beforeUpdateOptions()
{
if (isset($_POST['PS_SHOP_STATE_ID']) && $_POST['PS_SHOP_STATE_ID'] != '0') {
$sql = 'SELECT `active` FROM `' . _DB_PREFIX_ . 'state`
WHERE `id_country` = ' . (int) Tools::getValue('PS_SHOP_COUNTRY_ID') . '
AND `id_state` = ' . (int) Tools::getValue('PS_SHOP_STATE_ID');
$isStateOk = Db::getInstance()->getValue($sql);
if ($isStateOk != 1) {
$this->errors[] = $this->trans('The specified state is not located in this country.', [], 'Admin.Shopparameters.Notification');
}
}
}
public function updateOptionPsShopCountryId($value)
{
if (!$this->errors && $value) {
$country = new Country($value, $this->context->language->id);
if ($country->id) {
Configuration::updateValue('PS_SHOP_COUNTRY_ID', $value);
Configuration::updateValue('PS_SHOP_COUNTRY', pSQL($country->name));
}
}
}
public function updateOptionPsShopStateId($value)
{
if (!$this->errors && $value) {
$state = new State($value);
if ($state->id) {
Configuration::updateValue('PS_SHOP_STATE_ID', $value);
Configuration::updateValue('PS_SHOP_STATE', pSQL($state->name));
}
}
}
/**
* Adapt the format of hours.
*
* @param array $value
*
* @return array
*/
protected function adaptHoursFormat(?array $value)
{
if (null === $value) {
return [];
}
$separator = array_fill(0, count($value), ' | ');
return array_map('implode', $separator, $value);
}
}

View File

@@ -0,0 +1,379 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Tab $object
*/
class AdminTabsControllerCore extends AdminController
{
/** @var string */
protected $position_identifier = 'id_tab';
public function __construct()
{
$this->bootstrap = true;
$this->multishop_context = Shop::CONTEXT_ALL;
$this->table = 'tab';
$this->list_id = 'tab';
$this->className = 'Tab';
$this->lang = true;
parent::__construct();
$this->fieldImageSettings = [
'name' => 'icon',
'dir' => 't',
];
$this->imageType = 'gif';
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Actions'),
'icon' => 'icon-trash',
],
];
$this->fields_list = [
'id_tab' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
],
'class_name' => [
'title' => $this->trans('Class', [], 'Admin.Global'),
],
'module' => [
'title' => $this->trans('Module', [], 'Admin.Global'),
],
'active' => [
'title' => $this->trans('Enabled', [], 'Admin.Global'),
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'orderby' => false,
],
'position' => [
'title' => $this->trans('Position', [], 'Admin.Global'),
'filter_key' => 'a!position',
'position' => 'position',
'align' => 'center',
'class' => 'fixed-width-md',
],
];
}
public function initPageHeaderToolbar()
{
$this->page_header_toolbar_title = $this->trans('Menus', [], 'Admin.Global');
if ($this->display == 'details') {
$this->page_header_toolbar_btn['back_to_list'] = [
'href' => Context::getContext()->link->getAdminLink('AdminTabs'),
'desc' => $this->trans('Back to list', [], 'Admin.Actions'),
'icon' => 'process-icon-back',
];
} elseif (empty($this->display)) {
$this->page_header_toolbar_btn['new_menu'] = [
'href' => self::$currentIndex . '&addtab&token=' . $this->token,
'desc' => $this->trans('Add new menu', [], 'Admin.Actions'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
/**
* AdminController::renderForm() override.
*
* @see AdminController::renderForm()
*
* @return string
*
* @throws SmartyException
*/
public function renderForm()
{
$tabs = Tab::getTabs($this->context->language->id, 0);
// If editing, we clean itself
if (Tools::isSubmit('id_tab')) {
foreach ($tabs as $key => $tab) {
if ($tab['id_tab'] == Tools::getValue('id_tab')) {
unset($tabs[$key]);
}
}
}
// added category "Home" in var $tabs
$tab_zero = [
'id_tab' => 0,
'name' => $this->trans('Home', [], 'Admin.Global'),
];
array_unshift($tabs, $tab_zero);
$this->fields_form = [
'legend' => [
'title' => $this->trans('Menus', [], 'Admin.Global'),
'icon' => 'icon-list-ul',
],
'input' => [
[
'type' => 'hidden',
'name' => 'position',
'required' => false,
],
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'lang' => true,
'required' => true,
'hint' => $this->trans('Invalid characters:', [], 'Admin.Notifications.Info') . ' &lt;&gt;{}',
],
[
'type' => 'text',
'label' => $this->trans('Class', [], 'Admin.Global'),
'name' => 'class_name',
'required' => true,
],
[
'type' => 'text',
'label' => $this->trans('Module', [], 'Admin.Global'),
'name' => 'module',
],
[
'type' => 'switch',
'label' => $this->trans('Status', [], 'Admin.Global'),
'name' => 'active',
'required' => false,
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
'hint' => $this->trans('Show or hide menu.', [], 'Admin.Actions'),
],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
$display_parent = true;
if (Validate::isLoadedObject($this->object) && !class_exists($this->object->class_name . 'Controller')) {
$display_parent = false;
}
if ($display_parent) {
$this->fields_form['input'][] = [
'type' => 'select',
'label' => $this->trans('Parent', [], 'Admin.Global'),
'name' => 'id_parent',
'options' => [
'query' => $tabs,
'id' => 'id_tab',
'name' => 'name',
],
];
}
return parent::renderForm();
}
/**
* AdminController::renderList() override.
*
* @see AdminController::renderList()
*/
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('details');
$this->addRowAction('delete');
$this->_where = 'AND a.`id_parent` = 0';
$this->_orderBy = 'position';
return parent::renderList();
}
public function initProcess()
{
if (Tools::getIsset('details' . $this->table)) {
$this->list_id = 'details';
if (isset($_POST['submitReset' . $this->list_id])) {
$this->processResetFilters();
}
} else {
$this->list_id = 'tab';
}
return parent::initProcess();
}
/**
* @return false|string|void
*
* @throws PrestaShopException
*/
public function renderDetails()
{
if ($id = Tools::getValue('id_tab')) {
$this->lang = false;
$this->list_id = 'details';
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->toolbar_btn = [];
/** @var Tab $tab */
$tab = $this->loadObject($id);
$this->toolbar_title = $tab->name[$this->context->employee->id_lang];
$this->_select = 'b.*';
$this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'tab_lang` b ON (b.`id_tab` = a.`id_tab` AND b.`id_lang` = ' .
(int) $this->context->language->id . ')';
$this->_where = 'AND a.`id_parent` = ' . (int) $id;
$this->_orderBy = 'position';
$this->_use_found_rows = false;
self::$currentIndex = self::$currentIndex . '&details' . $this->table;
$this->processFilter();
return parent::renderList();
}
}
public function postProcess()
{
/* PrestaShop demo mode */
if (_PS_MODE_DEMO_) {
$this->errors[] = $this->trans('This functionality has been disabled.', [], 'Admin.Notifications.Error');
return;
}
/* PrestaShop demo mode */
if (($id_tab = (int) Tools::getValue('id_tab')) && ($direction = Tools::getValue('move')) && Validate::isLoadedObject($tab = new Tab($id_tab))) {
if ($tab->move($direction)) {
Tools::redirectAdmin(self::$currentIndex . '&token=' . $this->token);
}
} elseif (Tools::getValue('position') && !Tools::isSubmit('submitAdd' . $this->table)) {
$object = new Tab((int) Tools::getValue($this->identifier));
if ($this->access('edit') !== '1') {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
} elseif (!Validate::isLoadedObject($object)) {
$this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') .
' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error');
}
if (!$object->updatePosition((bool) Tools::getValue('way'), (int) Tools::getValue('position'))) {
$this->errors[] = $this->trans('Failed to update the position.', [], 'Admin.Notifications.Error');
} else {
Tools::redirectAdmin(self::$currentIndex . '&conf=5&token=' . Tools::getAdminTokenLite('AdminTabs'));
}
} elseif (Tools::isSubmit('submitAdd' . $this->table) && Tools::getValue('id_tab') === Tools::getValue('id_parent')) {
$this->errors[] = $this->trans('You can\'t put this menu inside itself. ', [], 'Admin.Advparameters.Notification');
} elseif (Tools::isSubmit('submitAdd' . $this->table) && $id_parent = (int) Tools::getValue('id_parent')) {
$this->redirect_after = self::$currentIndex . '&id_' . $this->table . '=' . $id_parent . '&details' . $this->table . '&conf=4&token=' . $this->token;
} elseif (isset($_GET['details' . $this->table]) && is_array($this->bulk_actions)) {
$submit_bulk_actions = array_merge([
'enableSelection' => [
'text' => $this->trans('Enable selection', [], 'Admin.Actions'),
'icon' => 'icon-power-off text-success',
],
'disableSelection' => [
'text' => $this->trans('Disable selection', [], 'Admin.Actions'),
'icon' => 'icon-power-off text-danger',
],
], $this->bulk_actions);
foreach ($submit_bulk_actions as $bulk_action => $params) {
if (Tools::isSubmit('submitBulk' . $bulk_action . $this->table) || Tools::isSubmit('submitBulk' . $bulk_action)) {
if ($this->access('edit')) {
$this->action = 'bulk' . $bulk_action;
$this->boxes = Tools::getValue($this->list_id . 'Box');
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
break;
} elseif (Tools::isSubmit('submitBulk')) {
if ($this->access('edit')) {
$this->action = 'bulk' . Tools::getValue('select_submitBulk');
$this->boxes = Tools::getValue($this->list_id . 'Box');
} else {
$this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error');
}
break;
}
}
} else {
// Temporary add the position depend of the selection of the parent category
if (!Tools::isSubmit('id_tab')) { // @todo Review
$_POST['position'] = Tab::getNbTabs(Tools::getValue('id_parent'));
}
}
if (!count($this->errors)) {
parent::postProcess();
}
}
/**
* @return bool|void
*/
protected function afterImageUpload()
{
if (!($obj = $this->loadObject(true))) {
return;
}
/* @var Tab $obj */
@rename(_PS_IMG_DIR_ . 't/' . $obj->id . '.gif', _PS_IMG_DIR_ . 't/' . $obj->class_name . '.gif');
}
public function ajaxProcessUpdatePositions()
{
$way = (bool) Tools::getValue('way');
$id_tab = (int) Tools::getValue('id');
$positions = Tools::getValue('tab');
// when changing positions in a tab sub-list, the first array value is empty and needs to be removed
if (!$positions[0]) {
unset($positions[0]);
// reset indexation from 0
$positions = array_merge($positions);
}
foreach ($positions as $position => $value) {
$pos = explode('_', $value);
if (isset($pos[2]) && (int) $pos[2] === $id_tab) {
$tab = new Tab((int) $pos[2]);
if (Validate::isLoadedObject($tab)) {
if (isset($position) && $tab->updatePosition($way, $position)) {
echo 'ok position ' . (int) $position . ' for tab ' . (int) $pos[1] . '\r\n';
} else {
echo '{"hasError" : true, "errors" : "Can not update tab ' . (int) $id_tab . ' to position ' . (int) $position . ' "}';
}
} else {
echo '{"hasError" : true, "errors" : "This tab (' . (int) $id_tab . ') can t be loaded"}';
}
break;
}
}
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property Tag $object
*/
class AdminTagsControllerCore extends AdminController
{
/** @var bool */
public $bootstrap = true;
public function __construct()
{
$this->table = 'tag';
$this->className = 'Tag';
parent::__construct();
$this->fields_list = [
'id_tag' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'lang' => [
'title' => $this->trans('Language', [], 'Admin.Global'),
'filter_key' => 'l!name',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
'filter_key' => 'a!name',
],
'products' => [
'title' => $this->trans('Products', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
'havingFilter' => true,
],
];
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'icon' => 'icon-trash',
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
],
];
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_tag'] = [
'href' => self::$currentIndex . '&addtag&token=' . $this->token,
'desc' => $this->trans('Add new tag', [], 'Admin.Shopparameters.Feature'),
'icon' => 'process-icon-new',
];
}
parent::initPageHeaderToolbar();
}
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = 'l.name as lang, COUNT(pt.id_product) as products';
$this->_join = '
LEFT JOIN `' . _DB_PREFIX_ . 'product_tag` pt
ON (a.`id_tag` = pt.`id_tag`)
LEFT JOIN `' . _DB_PREFIX_ . 'lang` l
ON (l.`id_lang` = a.`id_lang`)';
$this->_group = 'GROUP BY a.name, a.id_lang';
return parent::renderList();
}
public function postProcess()
{
if ($this->access('edit') && Tools::getValue('submitAdd' . $this->table)) {
$id = (int) Tools::getValue($this->identifier);
$obj = new $this->className($id);
if (Validate::isLoadedObject($obj)) {
/** @var Tag $obj */
$previous_products = $obj->getProducts();
$removed_products = [];
foreach ($previous_products as $product) {
if (!in_array($product['id_product'], $_POST['products'])) {
$removed_products[] = $product['id_product'];
}
}
if (Configuration::get('PS_SEARCH_INDEXATION')) {
Search::removeProductsSearchIndex($removed_products);
}
$obj->setProducts($_POST['products']);
}
}
return parent::postProcess();
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderForm()
{
/** @var Tag|null $obj */
$obj = $this->loadObject(true);
if (!$obj) {
return;
}
$this->fields_form = [
'legend' => [
'title' => $this->trans('Tag', [], 'Admin.Shopparameters.Feature'),
'icon' => 'icon-tag',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'required' => true,
],
[
'type' => 'select',
'label' => $this->trans('Language', [], 'Admin.Global'),
'name' => 'id_lang',
'required' => true,
'options' => [
'query' => Language::getLanguages(false),
'id' => 'id_lang',
'name' => 'name',
],
],
],
'selects' => [
'products' => $obj->getProducts(true),
'products_unselected' => $obj->getProducts(false),
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
],
];
return parent::renderForm();
}
}

View File

@@ -0,0 +1,561 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @property TaxRulesGroup $object
*/
class AdminTaxRulesGroupControllerCore extends AdminController
{
public $tax_rule;
public $selected_countries = [];
public $selected_states = [];
public $errors_tax_rule;
public function __construct()
{
$this->bootstrap = true;
$this->table = 'tax_rules_group';
$this->className = 'TaxRulesGroup';
$this->lang = false;
parent::__construct();
$this->fields_list = [
'id_tax_rules_group' => [
'title' => $this->trans('ID', [], 'Admin.Global'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'name' => [
'title' => $this->trans('Name', [], 'Admin.Global'),
],
'active' => [
'title' => $this->trans('Enabled', [], 'Admin.Global'),
'active' => 'status',
'type' => 'bool',
'orderby' => false,
'align' => 'center',
'class' => 'fixed-width-sm',
],
];
$this->bulk_actions = [
'delete' => [
'text' => $this->trans('Delete selected', [], 'Admin.Actions'),
'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'),
'icon' => 'icon-trash',
],
];
$this->_where .= ' AND a.deleted = 0';
}
public function initPageHeaderToolbar()
{
if (empty($this->display)) {
$this->page_header_toolbar_btn['new_tax_rules_group'] = [
'href' => self::$currentIndex . '&addtax_rules_group&token=' . $this->token,
'desc' => $this->trans('Add new tax rules group', [], 'Admin.International.Feature'),
'icon' => 'process-icon-new',
];
}
if ($this->display === 'edit') {
$this->page_header_toolbar_btn['new'] = [
'href' => '#',
'desc' => $this->trans('Add a new tax rule', [], 'Admin.International.Feature'),
];
}
parent::initPageHeaderToolbar();
}
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('delete');
return parent::renderList();
}
public function initRulesList($id_group)
{
$this->table = 'tax_rule';
$this->list_id = 'tax_rule';
$this->identifier = 'id_tax_rule';
$this->className = 'TaxRule';
$this->lang = false;
$this->list_simple_header = false;
$this->toolbar_btn = null;
$this->list_no_link = true;
$this->bulk_actions = [
'delete' => ['text' => $this->trans('Delete selected', [], 'Admin.Actions'), 'confirm' => $this->trans('Delete selected items?', [], 'Admin.Notifications.Warning'), 'icon' => 'icon-trash'],
];
$this->fields_list = [
'country_name' => [
'title' => $this->trans('Country', [], 'Admin.Global'),
],
'state_name' => [
'title' => $this->trans('State', [], 'Admin.Global'),
],
'zipcode' => [
'title' => $this->trans('Zip/Postal code', [], 'Admin.Global'),
'class' => 'fixed-width-md',
],
'behavior' => [
'title' => $this->trans('Behavior', [], 'Admin.International.Feature'),
],
'rate' => [
'title' => $this->trans('Tax', [], 'Admin.Global'),
'class' => 'fixed-width-sm',
],
'description' => [
'title' => $this->trans('Description', [], 'Admin.Global'),
],
];
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->_select = '
c.`name` AS country_name,
s.`name` AS state_name,
CONCAT_WS(" - ", a.`zipcode_from`, a.`zipcode_to`) AS zipcode,
t.rate';
$this->_join = '
LEFT JOIN `' . _DB_PREFIX_ . 'country_lang` c
ON (a.`id_country` = c.`id_country` AND id_lang = ' . (int) $this->context->language->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'state` s
ON (a.`id_state` = s.`id_state`)
LEFT JOIN `' . _DB_PREFIX_ . 'tax` t
ON (a.`id_tax` = t.`id_tax` AND t.active = 1)';
$this->_where = 'AND `id_tax_rules_group` = ' . (int) $id_group;
$this->_use_found_rows = false;
$this->show_toolbar = false;
$this->tpl_list_vars = ['id_tax_rules_group' => (int) $id_group];
$this->_filter = false;
return parent::renderList();
}
/**
* @return string|void
*
* @throws SmartyException
*/
public function renderForm()
{
$this->fields_form = [
'legend' => [
'title' => $this->trans('Tax rules', [], 'Admin.International.Feature'),
'icon' => 'icon-money',
],
'input' => [
[
'type' => 'text',
'label' => $this->trans('Name', [], 'Admin.Global'),
'name' => 'name',
'required' => true,
'hint' => $this->trans('Invalid characters:', [], 'Admin.Notifications.Info') . ' <>{}',
],
[
'type' => 'switch',
'label' => $this->trans('Enable', [], 'Admin.Actions'),
'name' => 'active',
'required' => false,
'is_bool' => true,
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->trans('Yes', [], 'Admin.Global'),
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->trans('No', [], 'Admin.Global'),
],
],
],
],
'submit' => [
'title' => $this->trans('Save and stay', [], 'Admin.Actions'),
'stay' => true,
],
];
if (Shop::isFeatureActive()) {
$this->fields_form['input'][] = [
'type' => 'shop',
'label' => $this->trans('Store association', [], 'Admin.Global'),
'name' => 'checkBoxShopAsso',
];
}
if (!($obj = $this->loadObject(true))) {
return;
}
if (!isset($obj->id)) {
$content = parent::renderForm();
} else {
$this->page_header_toolbar_btn['new'] = [
'href' => '#',
'desc' => $this->trans('Add a new tax rule', [], 'Admin.International.Feature'),
];
$content = parent::renderForm();
$this->tpl_folder = 'tax_rules/';
$content .= $this->initRuleForm();
// We change the variable $ tpl_folder to avoid the overhead calling the file in list_action_edit.tpl in intList ();
$content .= $this->initRulesList((int) $obj->id);
}
return $content;
}
public function initRuleForm()
{
$this->fields_form[0]['form'] = [
'legend' => [
'title' => $this->trans('New tax rule', [], 'Admin.International.Feature'),
'icon' => 'icon-money',
],
'input' => [
[
'type' => 'select',
'label' => $this->trans('Country', [], 'Admin.Global'),
'name' => 'country',
'id' => 'country',
'options' => [
'query' => Country::getCountries($this->context->language->id),
'id' => 'id_country',
'name' => 'name',
'default' => [
'value' => 0,
'label' => $this->trans('All', [], 'Admin.Global'),
],
],
],
[
'type' => 'select',
'label' => $this->trans('State', [], 'Admin.Global'),
'name' => 'states[]',
'id' => 'states',
'multiple' => true,
'options' => [
'query' => [],
'id' => 'id_state',
'name' => 'name',
'default' => [
'value' => 0,
'label' => $this->trans('All', [], 'Admin.Global'),
],
],
],
[
'type' => 'hidden',
'name' => 'action',
],
[
'type' => 'text',
'label' => $this->trans('Zip/Postal code range', [], 'Admin.International.Feature'),
'name' => 'zipcode',
'required' => false,
'hint' => $this->trans('You can define a range of Zip/Postal codes (e.g., 75000-75015) or simply use one Zip/Postal code.', [], 'Admin.International.Help'),
],
[
'type' => 'select',
'label' => $this->trans('Behavior', [], 'Admin.International.Feature'),
'name' => 'behavior',
'required' => false,
'options' => [
'query' => [
[
'id' => 0,
'name' => $this->trans('This tax only', [], 'Admin.International.Feature'),
],
[
'id' => 1,
'name' => $this->trans('Combine', [], 'Admin.International.Feature'),
],
[
'id' => 2,
'name' => $this->trans('One after another', [], 'Admin.International.Feature'),
],
],
'id' => 'id',
'name' => 'name',
],
'hint' => [
$this->trans('You must define the behavior if an address matches multiple rules:', [], 'Admin.International.Help') . '<br>',
$this->trans('- This tax only: Will apply only this tax', [], 'Admin.International.Help') . '<br>',
$this->trans('- Combine: Combine taxes (e.g.: 10% + 5% = 15%)', [], 'Admin.International.Help') . '<br>',
$this->trans('- One after another: Apply taxes one after another (e.g.: 100 + 10% => 110 + 5% = 115.5)', [], 'Admin.International.Help'),
],
],
[
'type' => 'select',
'label' => $this->trans('Tax', [], 'Admin.Global'),
'name' => 'id_tax',
'required' => false,
'options' => [
'query' => Tax::getTaxes((int) $this->context->language->id),
'id' => 'id_tax',
'name' => 'name',
'default' => [
'value' => 0,
'label' => $this->trans('No Tax', [], 'Admin.International.Help'),
],
],
],
[
'type' => 'text',
'label' => $this->trans('Description', [], 'Admin.Global'),
'name' => 'description',
],
],
'submit' => [
'title' => $this->trans('Save and stay', [], 'Admin.Actions'),
'stay' => true,
],
];
if (!($obj = $this->loadObject(true))) {
return;
}
$this->fields_value = [
'action' => 'create_rule',
'id_tax_rules_group' => $obj->id,
'id_tax_rule' => '',
];
$this->getlanguages();
$helper = new HelperForm();
$helper->override_folder = $this->tpl_folder;
$helper->currentIndex = self::$currentIndex;
$helper->token = $this->token;
$helper->table = 'tax_rule';
$helper->identifier = 'id_tax_rule';
$helper->id = $obj->id;
$helper->toolbar_scroll = true;
$helper->show_toolbar = true;
$helper->languages = $this->_languages;
$helper->default_form_language = $this->default_form_language;
$helper->allow_employee_form_lang = $this->allow_employee_form_lang;
$helper->fields_value = $this->getFieldsValue($this->object);
$helper->toolbar_btn['save_new_rule'] = [
'href' => self::$currentIndex . '&amp;id_tax_rules_group=' . $obj->id . '&amp;action=create_rule&amp;token=' . $this->token,
'desc' => 'Save tax rule',
'class' => 'process-icon-save',
];
$helper->submit_action = 'create_rule';
return $helper->generateForm($this->fields_form);
}
public function initProcess()
{
if (Tools::isSubmit('deletetax_rule')) {
if ($this->access('delete')) {
$this->action = 'delete_tax_rule';
} else {
$this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::isSubmit('submitBulkdeletetax_rule')) {
if ($this->access('delete')) {
$this->action = 'bulk_delete_tax_rules';
} else {
$this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error');
}
} elseif (Tools::getValue('action') == 'create_rule') {
if ($this->access('add')) {
$this->action = 'create_rule';
} else {
$this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error');
}
} else {
parent::initProcess();
}
}
protected function processCreateRule()
{
$zip_code = Tools::getValue('zipcode');
$zip_code = ('' === $zip_code) ? 0 : $zip_code;
$id_rule = (int) Tools::getValue('id_tax_rule');
$id_tax = (int) Tools::getValue('id_tax');
$id_tax_rules_group = (int) Tools::getValue('id_tax_rules_group');
$behavior = (int) Tools::getValue('behavior');
$description = pSQL(Tools::getValue('description'));
if ((int) ($id_country = Tools::getValue('country')) == 0) {
$countries = Country::getCountries($this->context->language->id);
$this->selected_countries = [];
foreach ($countries as $country) {
$this->selected_countries[] = (int) $country['id_country'];
}
} else {
$this->selected_countries = [$id_country];
}
$this->selected_states = Tools::getValue('states');
if (empty($this->selected_states) || count($this->selected_states) == 0) {
$this->selected_states = [0];
}
$tax_rules_group = new TaxRulesGroup((int) $id_tax_rules_group);
foreach ($this->selected_countries as $id_country) {
$first = true;
foreach ($this->selected_states as $id_state) {
if ($tax_rules_group->hasUniqueTaxRuleForCountry($id_country, $id_state, $id_rule)) {
$this->errors[] = $this->trans('A tax rule already exists for this country/state with tax only behavior.', [], 'Admin.International.Notification');
continue;
}
$tr = new TaxRule();
// update or creation?
if ($first) {
$tr->id = $id_rule;
$first = false;
}
$tr->id_tax = $id_tax;
$tax_rules_group = new TaxRulesGroup((int) $id_tax_rules_group);
$tr->id_tax_rules_group = (int) $tax_rules_group->id;
$tr->id_country = (int) $id_country;
$tr->id_state = (int) $id_state;
list($tr->zipcode_from, $tr->zipcode_to) = $tr->breakDownZipCode($zip_code);
// Construct Object Country
$country = new Country((int) $id_country, (int) $this->context->language->id);
if ($zip_code && $country->need_zip_code) {
if ($country->zip_code_format) {
foreach ([$tr->zipcode_from, $tr->zipcode_to] as $zip_code) {
if ($zip_code) {
if (!$country->checkZipCode($zip_code)) {
$this->errors[] = $this->trans(
'The Zip/Postal code is invalid. It must be typed as follows: %format% for %country%.',
[
'%format%' => str_replace('C', $country->iso_code, str_replace('N', '0', str_replace('L', 'A', $country->zip_code_format))),
'%country%' => $country->name,
],
'Admin.International.Notification'
);
}
}
}
}
}
$tr->behavior = (int) $behavior;
$tr->description = $description;
$this->tax_rule = $tr;
$_POST['id_state'] = $tr->id_state;
$this->errors = array_merge($this->errors, $this->validateTaxRule($tr));
if (count($this->errors) == 0) {
$tax_rules_group = $this->updateTaxRulesGroup($tax_rules_group);
$tr->id = (int) $tax_rules_group->getIdTaxRuleGroupFromHistorizedId((int) $tr->id);
$tr->id_tax_rules_group = (int) $tax_rules_group->id;
if (!$tr->save()) {
$this->errors[] = $this->trans('An error has occurred: Cannot save the current tax rule.', [], 'Admin.International.Notification');
}
}
}
}
if (count($this->errors) == 0) {
Tools::redirectAdmin(
self::$currentIndex . '&' . $this->identifier . '=' . (int) $tax_rules_group->id . '&conf=4&update' . $this->table . '&token=' . $this->token
);
} else {
$this->display = 'edit';
}
}
protected function processBulkDeleteTaxRules()
{
$this->deleteTaxRule(Tools::getValue('tax_ruleBox'));
}
protected function processDeleteTaxRule()
{
$this->deleteTaxRule([Tools::getValue('id_tax_rule')]);
}
public function displayAjaxUpdateTaxRule()
{
if ($this->access('view')) {
$id_tax_rule = Tools::getValue('id_tax_rule');
$tax_rules = new TaxRule((int) $id_tax_rule);
$output = [];
foreach (get_object_vars($tax_rules) as $key => $result) {
$output[$key] = $result;
}
die(json_encode($output));
}
}
protected function deleteTaxRule(array $id_tax_rule_list)
{
$result = true;
foreach ($id_tax_rule_list as $id_tax_rule) {
$tax_rule = new TaxRule((int) $id_tax_rule);
if (Validate::isLoadedObject($tax_rule)) {
$tax_rules_group = new TaxRulesGroup((int) $tax_rule->id_tax_rules_group);
$tax_rules_group = $this->updateTaxRulesGroup($tax_rules_group);
$tax_rule = new TaxRule($tax_rules_group->getIdTaxRuleGroupFromHistorizedId((int) $id_tax_rule));
if (Validate::isLoadedObject($tax_rule)) {
$result &= $tax_rule->delete();
}
}
}
$idTaxRulesGroup = isset($tax_rules_group) ? (int) $tax_rules_group->id : 0;
Tools::redirectAdmin(
self::$currentIndex . '&' . $this->identifier . '=' . $idTaxRulesGroup . '&conf=4&update' . $this->table . '&token=' . $this->token
);
}
/**
* Check if the tax rule could be added in the database.
*
* @param TaxRule $tr
*
* @return array
*/
protected function validateTaxRule(TaxRule $tr)
{
// @TODO: check if the rule already exists
return $tr->validateController();
}
/**
* @param TaxRulesGroup $object
*
* @return TaxRulesGroup
*/
protected function updateTaxRulesGroup(TaxRulesGroup $object)
{
static $tax_rules_group = null;
if ($tax_rules_group === null) {
$object->update();
$tax_rules_group = $object;
}
return $tax_rules_group;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class BoOrderCore extends PaymentModule
{
/** @var bool */
public $active = true;
/** @var string */
public $name = 'bo_order';
public function __construct()
{
$this->displayName = $this->trans('Back office order', [], 'Admin.Orderscustomers.Feature');
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
declare(strict_types=1);
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This class is used only because some parts of the Back Office require a Controller
* to function (like the NullDispatcher). It is also used in integration tests for override.
*/
class DummyAdminControllerCore extends AdminController
{
public function __construct()
{
parent::__construct();
$this->id = 0;
$this->controller_type = 'dummy';
}
public function checkAccess()
{
return true;
}
public function viewAccess($disable = false)
{
return true;
}
public function postProcess()
{
return true;
}
public function display()
{
return '';
}
public function setMedia($isNewTheme = false)
{
return null;
}
public function initHeader()
{
return '';
}
public function initContent()
{
return '';
}
public function initCursedPage()
{
return '';
}
public function initFooter()
{
return '';
}
protected function redirect()
{
return '';
}
protected function buildContainer(): ContainerInterface
{
return SymfonyContainer::getInstance();
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
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;

View File

@@ -0,0 +1,172 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AddressControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var bool */
public $guestAllowed = true;
/** @var string */
public $php_self = 'address';
/** @var string */
public $authRedirection = 'addresses';
/** @var bool */
public $ssl = true;
protected $address_form;
protected $should_redirect = false;
/**
* Initialize address controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
$this->address_form = $this->makeAddressForm();
$this->context->smarty->assign('address_form', $this->address_form->getProxy());
}
/**
* Start forms process.
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
$this->context->smarty->assign('editing', false);
$id_address = (int) Tools::getValue('id_address');
// Initialize address if an id exists
if ($id_address) {
$this->address_form->loadAddressById($id_address);
}
// Fill the form with data
$this->address_form->fillWith(Tools::getAllValues());
// Submit the address, don't care if it's an edit or add
if (Tools::isSubmit('submitAddress')) {
if (!$this->address_form->submit()) {
$this->errors[] = $this->trans('Please fix the error below.', [], 'Shop.Notifications.Error');
} else {
if ($id_address) {
$this->success[] = $this->trans('Address successfully updated.', [], 'Shop.Notifications.Success');
} else {
$this->success[] = $this->trans('Address successfully added.', [], 'Shop.Notifications.Success');
}
$this->should_redirect = true;
}
}
// There is no id_adress, no need to continue
if (!$id_address) {
return;
}
if (Tools::getValue('delete')) {
if (
Validate::isLoadedObject($this->context->cart)
&& ($this->context->cart->id_address_invoice == $id_address
|| $this->context->cart->id_address_delivery == $id_address)
) {
$this->errors[] = $this->trans(
'Could not delete the address since it is used in the shopping cart.',
[],
'Shop.Notifications.Error'
);
return;
}
$ok = $this->makeAddressPersister()->delete(
new Address($id_address, $this->context->language->id),
Tools::getValue('token')
);
if ($ok) {
$this->success[] = $this->trans('Address successfully deleted.', [], 'Shop.Notifications.Success');
$this->should_redirect = true;
} else {
$this->errors[] = $this->trans('Could not delete address.', [], 'Shop.Notifications.Error');
}
} else {
$this->context->smarty->assign('editing', true);
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (!$this->ajax && $this->should_redirect) {
if (($back = Tools::getValue('back')) && Tools::urlBelongsToShop($back)) {
$mod = Tools::getValue('mod');
$this->redirectWithNotifications('index.php?controller=' . $back . ($mod ? '&back=' . $mod : ''));
} else {
$this->redirectWithNotifications($this->context->link->getPageLink('addresses'));
}
}
parent::initContent();
$this->setTemplate(
'customer/address',
[
'entity' => 'address',
'id' => (int) Tools::getValue('id_address'),
]
);
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Addresses', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('addresses'),
];
$id_address = Tools::getValue('id_address');
$title = $id_address
? $this->trans('Update your address', [], 'Shop.Theme.Customeraccount')
: $this->trans('New address', [], 'Shop.Theme.Customeraccount');
$breadcrumb['links'][] = [
'title' => $title,
'url' => '#',
];
return $breadcrumb;
}
public function displayAjaxAddressForm(): void
{
$addressForm = $this->makeAddressForm();
if (Tools::getIsset('id_address') && ($id_address = (int) Tools::getValue('id_address'))) {
$addressForm->loadAddressById($id_address);
}
if (Tools::getIsset('id_country')) {
$addressForm->fillWith(['id_country' => Tools::getValue('id_country')]);
}
ob_end_clean();
header('Content-Type: application/json');
$this->ajaxRender(json_encode([
'address_form' => $this->render(
'customer/_partials/address-form',
$addressForm->getTemplateVariables()
),
]));
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AddressesControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'addresses';
/** @var string */
public $authRedirection = 'addresses';
/** @var bool */
public $ssl = true;
/**
* Initialize addresses controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
if (!Validate::isLoadedObject($this->context->customer)) {
throw new PrestaShopException($this->trans('The customer could not be found.', [], 'Shop.Notifications.Error'));
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->setTemplate('customer/addresses');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Addresses', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('addresses'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AttachmentControllerCore extends FrontController
{
public function postProcess(): void
{
$attachment = new Attachment(Tools::getValue('id_attachment'), $this->context->language->id);
if (!$attachment->id) {
Tools::redirect('index.php');
}
Hook::exec('actionDownloadAttachment', ['attachment' => &$attachment]);
if (ob_get_level() && ob_get_length() > 0) {
ob_end_clean();
}
header('Content-Transfer-Encoding: binary');
header('Content-Type: ' . $attachment->mime);
header('Content-Length: ' . filesize(_PS_DOWNLOAD_DIR_ . $attachment->file));
header('Content-Disposition: attachment; filename="' . utf8_decode($attachment->file_name) . '"');
@set_time_limit(0);
$this->readfileChunked(_PS_DOWNLOAD_DIR_ . $attachment->file);
exit;
}
/**
* @see http://ca2.php.net/manual/en/function.readfile.php#54295
*/
public function readfileChunked(string $filename, bool $retbytes = true)
{
// how many bytes per chunk
$chunksize = 1 * (1024 * 1024);
$buffer = '';
$totalBytes = 0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$totalBytes += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
// return num. bytes delivered like readfile() does.
return $totalBytes;
}
return $status;
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class AuthControllerCore extends FrontController
{
/** @var bool */
public $ssl = true;
/** @var string */
public $php_self = 'authentication';
/** @var bool */
public $auth = false;
/**
* Check if the controller is available for the current user/visitor.
*
* @see Controller::checkAccess()
*
* @return bool
*/
public function checkAccess(): bool
{
if ($this->context->customer->isLogged() && !$this->ajax) {
$this->redirect_after = $this->authRedirection ? urlencode($this->authRedirection) : 'my-account';
$this->redirect();
}
return parent::checkAccess();
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Tools::isSubmit('create_account')) {
$this->redirectWithNotifications('registration');
}
$should_redirect = false;
$login_form = $this->makeLoginForm()->fillWith(
Tools::getAllValues()
);
if (Tools::isSubmit('submitLogin')) {
if ($login_form->submit()) {
$should_redirect = true;
}
}
$this->context->smarty->assign([
'login_form' => $login_form->getProxy(),
]);
$this->setTemplate('customer/authentication');
parent::initContent();
if ($should_redirect && !$this->ajax) {
$back = rawurldecode(Tools::getValue('back'));
if (Tools::urlBelongsToShop($back)) {
// Checks to see if "back" is a fully qualified
// URL that is on OUR domain, with the right protocol
$this->redirectWithNotifications($back);
}
// Well we're not redirecting to a URL,
// so...
if ($this->authRedirection) {
// We may need to go there if defined
$this->redirectWithNotifications($this->authRedirection);
}
// go home
$this->redirectWithNotifications(__PS_BASE_URI__);
}
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Log in to your account', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('authentication'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('authentication');
}
}

View File

@@ -0,0 +1,713 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Core\Domain\Product\Stock\ValueObject\OutOfStockType;
class CartControllerCore extends FrontController
{
/** @var string */
public $php_self = 'cart';
protected $id_product;
protected $id_product_attribute;
protected $id_address_delivery;
protected $customization_id;
protected $qty;
/**
* To specify if you are in the preview mode or not.
*
* @var bool
*/
protected $preview;
/** @var bool */
public $ssl = true;
/**
* An array of errors, in case the update action of product is wrong.
*
* @var string[]
*/
protected $updateOperationError = [];
/**
* This is not a public page, so the canonical redirection is disabled.
*
* @param string $canonicalURL
*/
public function canonicalRedirection(string $canonicalURL = ''): void
{
}
/**
* Initialize cart controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
// Send noindex to avoid ghost carts by bots
header('X-Robots-Tag: noindex, nofollow', true);
// Get page main parameters
$this->id_product = (int) Tools::getValue('id_product', null);
$this->id_product_attribute = (int) Tools::getValue('id_product_attribute', Tools::getValue('ipa'));
$this->customization_id = (int) Tools::getValue('id_customization');
$this->qty = abs((int) Tools::getValue('qty', 1));
$this->id_address_delivery = (int) Tools::getValue('id_address_delivery');
$this->preview = ('1' === Tools::getValue('preview'));
if ('show' === Tools::getValue('action')) {
/* Check if the products in the cart are available */
$isAvailable = $this->areProductsAvailable();
if (Tools::getIsset('checkout')) {
Tools::redirect($this->context->link->getPageLink('order'));
}
if (true !== $isAvailable) {
$this->errors[] = $isAvailable;
}
/* Check if countries used in the cart are enabled */
if (true !== $this->context->cart->checkCountriesAreEnabled()) {
$this->errors[] = $this->trans(
'Some of the countries used in your cart are not available and cannot be used.',
[],
'Shop.Notifications.Error'
);
}
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode() && Tools::getValue('action') === 'show') {
Tools::redirect('index.php');
}
/*
* Check that minimal quantity conditions are respected for each product in the cart
* (this is to be applied only on page load, not for ajax calls)
*/
if (!Tools::getValue('ajax')) {
$this->checkCartProductsMinimalQuantities();
}
if ($this->context->cart->hasProducts()) {
$this->setTemplate('checkout/cart');
} else {
$this->context->smarty->assign([
'allProductsLink' => $this->context->link->getCategoryLink(
(int) Configuration::get('PS_HOME_CATEGORY')
),
]);
$this->setTemplate('checkout/cart-empty');
}
parent::initContent();
}
public function displayAjaxUpdate(): void
{
if (Configuration::isCatalogMode()) {
return;
}
$productsInCart = $this->context->cart->getProducts();
$updatedProducts = array_filter($productsInCart, [$this, 'productInCartMatchesCriteria']);
$updatedProduct = reset($updatedProducts);
$productQuantity = $updatedProduct['quantity'] ?? 0;
if (!$this->errors) {
$presentedCart = $this->cart_presenter->present($this->context->cart, true);
// filter product output
$presentedCart['products'] = $this->get('prestashop.core.filter.front_end_object.product_collection')
->filter($presentedCart['products']);
$this->ajaxRender(json_encode([
'success' => true,
'id_product' => $this->id_product,
'id_product_attribute' => $this->id_product_attribute,
'id_customization' => $this->customization_id,
'quantity' => $productQuantity,
'cart' => $presentedCart,
'errors' => empty($this->updateOperationError) ? '' : reset($this->updateOperationError),
]));
return;
} else {
$this->ajaxRender(json_encode([
'hasError' => true,
'errors' => $this->errors,
'quantity' => $productQuantity,
]));
return;
}
}
public function displayAjaxRefresh(): void
{
if (Configuration::isCatalogMode()) {
return;
}
ob_end_clean();
header('Content-Type: application/json');
$this->ajaxRender(json_encode([
'cart_detailed' => $this->render('checkout/_partials/cart-detailed'),
'cart_detailed_totals' => $this->render('checkout/_partials/cart-detailed-totals'),
'cart_summary_items_subtotal' => $this->render('checkout/_partials/cart-summary-items-subtotal'),
'cart_summary_products' => $this->render('checkout/_partials/cart-summary-products'),
'cart_summary_subtotals_container' => $this->render('checkout/_partials/cart-summary-subtotals'),
'cart_summary_totals' => $this->render('checkout/_partials/cart-summary-totals'),
'cart_detailed_actions' => $this->render('checkout/_partials/cart-detailed-actions'),
'cart_voucher' => $this->render('checkout/_partials/cart-voucher'),
'cart_summary_top' => $this->render('checkout/_partials/cart-summary-top'),
]));
}
/**
* @deprecated 1.7.3.1 the product link is now accessible
* in #quantity_wanted[data-url-update]
*/
public function displayAjaxProductRefresh(): void
{
if ($this->id_product) {
$idProductAttribute = 0;
$groups = Tools::getValue('group');
if (!empty($groups)) {
$idProductAttribute = (int) Product::getIdProductAttributeByIdAttributes(
$this->id_product,
$groups,
true
);
}
$url = $this->context->link->getProductLink(
$this->id_product,
null,
null,
null,
$this->context->language->id,
null,
$idProductAttribute,
false,
false,
true,
[
'quantity_wanted' => (int) $this->qty,
'preview' => $this->preview,
]
);
} else {
$url = false;
}
ob_end_clean();
header('Content-Type: application/json');
$this->ajaxRender(json_encode([
'success' => true,
'productUrl' => $url,
]));
}
public function postProcess(): void
{
$this->updateCart();
}
protected function updateCart(): void
{
// Update the cart ONLY if it's not a bot, in order to avoid ghost carts
if (!Connection::isBot()
&& !$this->errors
&& !($this->context->customer->isLogged() && !$this->isTokenValid())
) {
if (Tools::getIsset('add') || Tools::getIsset('update')) {
$this->processChangeProductInCart();
} elseif (Tools::getIsset('delete')) {
$this->processDeleteProductInCart();
} elseif (CartRule::isFeatureActive()) {
if (Tools::getIsset('addDiscount')) {
if (!($code = trim(Tools::getValue('discount_name')))) {
$this->errors[] = $this->trans(
'You must enter a voucher code.',
[],
'Shop.Notifications.Error'
);
} elseif (!Validate::isCleanHtml($code)) {
$this->errors[] = $this->trans(
'The voucher code is invalid.',
[],
'Shop.Notifications.Error'
);
} else {
$cartRule = new CartRule(CartRule::getIdByCode($code));
if (Validate::isLoadedObject($cartRule)) {
if ($error = $cartRule->checkValidity($this->context)) {
$this->errors[] = $error;
} else {
$result = $this->context->cart->addCartRule($cartRule->id);
if ($result !== true) {
// we have an incompatibility with another cart rule
$this->errors[] = $result;
}
}
} else {
$this->errors[] = $this->trans(
'This voucher does not exist.',
[],
'Shop.Notifications.Error'
);
}
}
} elseif (($id_cart_rule = (int) Tools::getValue('deleteDiscount'))
&& Validate::isUnsignedId($id_cart_rule)
) {
$this->context->cart->removeCartRule($id_cart_rule);
CartRule::autoAddToCart($this->context);
}
}
} elseif (!$this->isTokenValid() && Tools::getValue('action') !== 'show' && !Tools::getValue('ajax')) {
Tools::redirect('index.php');
}
}
/**
* This process delete a product from the cart.
*/
protected function processDeleteProductInCart(): void
{
$customization_product = Db::getInstance()->executeS(
'SELECT * FROM `' . _DB_PREFIX_ . 'customization`'
. ' WHERE `id_cart` = ' . (int) $this->context->cart->id
. ' AND `id_product` = ' . (int) $this->id_product
. ' AND `id_customization` != ' . (int) $this->customization_id
. ' AND `in_cart` = 1'
. ' AND `quantity` > 0'
);
if (count($customization_product)) {
$product = new Product((int) $this->id_product);
if ($this->id_product_attribute > 0) {
$minimal_quantity = (int) ProductAttribute::getAttributeMinimalQty($this->id_product_attribute);
} else {
$minimal_quantity = (int) $product->minimal_quantity;
}
$total_quantity = 0;
foreach ($customization_product as $custom) {
$total_quantity += $custom['quantity'];
}
if ($total_quantity < $minimal_quantity) {
$this->errors[] = $this->trans(
'You must add %quantity% minimum quantity',
['%quantity%' => $minimal_quantity],
'Shop.Notifications.Error'
);
return;
}
}
$data = [
'id_cart' => (int) $this->context->cart->id,
'id_product' => (int) $this->id_product,
'id_product_attribute' => (int) $this->id_product_attribute,
'customization_id' => (int) $this->customization_id,
'id_address_delivery' => (int) $this->id_address_delivery,
];
// An array [module_name => module_output] will be returned (no effect)
Hook::exec('actionObjectProductInCartDeleteBefore', $data, null, true);
if ($this->context->cart->deleteProduct(
$this->id_product,
$this->id_product_attribute,
$this->customization_id
)) {
Hook::exec('actionObjectProductInCartDeleteAfter', $data);
if (!Cart::getNbProducts((int) $this->context->cart->id)) {
$this->context->cart->setDeliveryOption(null);
$this->context->cart->gift = 0;
$this->context->cart->gift_message = '';
$this->context->cart->update();
}
$isAvailable = $this->areProductsAvailable();
if (true !== $isAvailable) {
$this->updateOperationError[] = $isAvailable;
}
}
CartRule::autoRemoveFromCart();
CartRule::autoAddToCart();
}
/**
* This process add or update a product in the cart.
*/
protected function processChangeProductInCart(): void
{
$mode = (Tools::getIsset('update') && $this->id_product) ? 'update' : 'add';
$ErrorKey = ('update' === $mode) ? 'updateOperationError' : 'errors';
if (Tools::getIsset('group')) {
$this->id_product_attribute = (int) Product::getIdProductAttributeByIdAttributes(
$this->id_product,
Tools::getValue('group')
);
}
if ($this->qty == 0) {
$this->{$ErrorKey}[] = $this->trans(
'Null quantity.',
[],
'Shop.Notifications.Error'
);
} elseif (!$this->id_product) {
$this->{$ErrorKey}[] = $this->trans(
'Product not found',
[],
'Shop.Notifications.Error'
);
}
$product = new Product($this->id_product, true, $this->context->language->id);
if (!$product->id || !$product->active || !$product->checkAccess($this->context->cart->id_customer)) {
$this->{$ErrorKey}[] = $this->trans(
'This product (%product%) is no longer available.',
['%product%' => $product->name],
'Shop.Notifications.Error'
);
return;
}
if (!$this->id_product_attribute && $product->hasAttributes()) {
$minimum_quantity = ($product->out_of_stock == OutOfStockType::OUT_OF_STOCK_DEFAULT)
? !Configuration::get('PS_ORDER_OUT_OF_STOCK')
: !$product->out_of_stock;
$this->id_product_attribute = Product::getDefaultAttribute($product->id, (int) $minimum_quantity);
// @todo do something better than a redirect admin !!
if (!$this->id_product_attribute) {
Tools::redirectAdmin($this->context->link->getProductLink($product));
}
}
$qty_to_check = $this->qty;
$cart_products = $this->context->cart->getProducts();
if (is_array($cart_products)) {
foreach ($cart_products as $cart_product) {
if ($this->productInCartMatchesCriteria($cart_product)) {
$qty_to_check = $cart_product['cart_quantity'];
if (Tools::getValue('op', 'up') == 'down') {
$qty_to_check -= $this->qty;
} else {
$qty_to_check += $this->qty;
}
break;
}
}
}
// Check product quantity availability
if ('update' !== $mode && $this->shouldAvailabilityErrorBeRaised($product, $qty_to_check)) {
/*
* If the product can't be in the cart in this quantity, we raise an error.
* For the purpose of this error message, we must get the real quantity in stock.
* No subtracting of quantity in the cart here.
*/
$availableProductQuantity = Product::getQuantity($this->id_product, $this->id_product_attribute);
$productName = Product::getProductName($this->id_product, $this->id_product_attribute);
if ($availableProductQuantity > 0) {
$this->errors[] = $this->trans(
'You can only buy %quantity% "%product%". Please adjust the quantity in your cart to continue.',
[
'%product%' => $productName,
'%quantity%' => $availableProductQuantity,
],
'Shop.Notifications.Error'
);
} else {
$this->errors[] = $this->trans(
'This product (%product%) is no longer available.',
['%product%' => $productName],
'Shop.Notifications.Error'
);
}
return;
}
// Check minimal_quantity
if (!$this->id_product_attribute) {
if ($qty_to_check < $product->minimal_quantity) {
$this->errors[] = $this->trans(
'The minimum purchase order quantity for the product %product% is %quantity%.',
['%product%' => $product->name, '%quantity%' => $product->minimal_quantity],
'Shop.Notifications.Error'
);
return;
}
} else {
$combination = new Combination($this->id_product_attribute);
if ($qty_to_check < $combination->minimal_quantity) {
$this->errors[] = $this->trans(
'The minimum purchase order quantity for the product %product% is %quantity%.',
['%product%' => $product->name, '%quantity%' => $combination->minimal_quantity],
'Shop.Notifications.Error'
);
return;
}
}
// If no errors, process product addition
if (!$this->errors) {
// Add cart if no cart found
if (!$this->context->cart->id) {
$this->context->cart->add();
if (Validate::isLoadedObject($this->context->cart)) {
$this->context->cookie->id_cart = (int) $this->context->cart->id;
}
}
// Check customizable fields
if (!$product->hasAllRequiredCustomizableFields() && !$this->customization_id) {
$this->{$ErrorKey}[] = $this->trans(
'Please fill in all of the required fields, and then save your customizations.',
[],
'Shop.Notifications.Error'
);
}
$update_quantity = $this->context->cart->updateQty(
$this->qty,
$this->id_product,
$this->id_product_attribute,
$this->customization_id,
Tools::getValue('op', 'up'),
0,
null,
true,
true
);
if ($update_quantity < 0) {
// If product has attribute, minimal quantity is set with minimal quantity of attribute
$minimal_quantity = ($this->id_product_attribute)
? ProductAttribute::getAttributeMinimalQty($this->id_product_attribute)
: $product->minimal_quantity;
$this->{$ErrorKey}[] = $this->trans(
'You must add %quantity% minimum quantity',
['%quantity%' => $minimal_quantity],
'Shop.Notifications.Error'
);
} elseif (!$update_quantity) {
$this->errors[] = $this->trans(
'You already have the maximum quantity available for this product.',
[],
'Shop.Notifications.Error'
);
} elseif ($this->shouldAvailabilityErrorBeRaised($product, $qty_to_check)) {
/*
* If the product can't be in the cart in this quantity, we raise an error.
* For the purpose of this error message, we must get the real quantity in stock.
* No subtracting of quantity in the cart here.
*/
$availableProductQuantity = Product::getQuantity($this->id_product, $this->id_product_attribute);
$productName = Product::getProductName($this->id_product, $this->id_product_attribute);
if ($availableProductQuantity > 0) {
$this->{$ErrorKey}[] = $this->trans(
'You can only buy %quantity% "%product%". Please adjust the quantity in your cart to continue.',
[
'%product%' => $productName,
'%quantity%' => $availableProductQuantity,
],
'Shop.Notifications.Error'
);
} else {
$this->{$ErrorKey}[] = $this->trans(
'This product (%product%) is no longer available.',
['%product%' => $productName],
'Shop.Notifications.Error'
);
}
}
}
// Check validity of all cart rules in cart and check if there are some automatic ones that should be applied
CartRule::autoRemoveFromCart();
CartRule::autoAddToCart();
// Finally check that all other products are also available, but only if there was no previous error
if ('add' !== $mode && empty($this->{$ErrorKey})) {
$areProductsAvailable = $this->areProductsAvailable();
if (true !== $areProductsAvailable) {
$this->{$ErrorKey}[] = $areProductsAvailable;
}
}
}
/**
* @param array $productInCart
*
* @return bool
*/
public function productInCartMatchesCriteria(array $productInCart)
{
return (
!isset($this->id_product_attribute)
|| (
$productInCart['id_product_attribute'] == $this->id_product_attribute
&& $productInCart['id_customization'] == $this->customization_id
)
) && isset($this->id_product) && $productInCart['id_product'] == $this->id_product;
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
if (!$this->context->cart->hasProducts()) {
$page['body_classes']['cart-empty'] = true;
}
return $page;
}
/**
* Check product quantity availability to acknowledge whether
* an availability error should be raised.
*
* If shop has been configured to oversell, answer is no.
* If there is no items available (no stock), answer is yes.
* If there is items available, but the Cart already contains more than the quantity,
* answer is yes.
*
* @param Product $product
* @param int $qtyToCheck
*
* @return bool
*/
protected function shouldAvailabilityErrorBeRaised(Product $product, int $qtyToCheck)
{
if ($this->id_product_attribute) {
return !Product::isAvailableWhenOutOfStock($product->out_of_stock)
&& !ProductAttribute::checkAttributeQty($this->id_product_attribute, $qtyToCheck);
} elseif (Product::isAvailableWhenOutOfStock($product->out_of_stock)) {
return false;
}
/*
* We check if this product is out-of-stock.
*/
$availableProductQuantity = Product::getQuantity($this->id_product, $this->id_product_attribute);
if ($availableProductQuantity < $qtyToCheck) {
return true;
}
// Check if this product is out-of-stock after cart quantities have been removed from stock
// Be aware that Product::getQuantity() returns the available quantity after decreasing products in cart
$productQuantityAvailableAfterCartItemsHaveBeenRemovedFromStock = Product::getQuantity(
$this->id_product,
$this->id_product_attribute,
null,
$this->context->cart,
false
);
return $productQuantityAvailableAfterCartItemsHaveBeenRemovedFromStock < 0;
}
/**
* Check if the products in the cart are available.
* This is a general check that is handy when you want to check whole cart,
* for example when loading the cart or order page.
*
* @return bool|string
*/
protected function areProductsAvailable(): bool|string
{
$products = $this->context->cart->getProducts();
foreach ($products as $product) {
$currentProduct = new Product();
$currentProduct->hydrate($product);
if ($currentProduct->hasAttributes() && $product['id_product_attribute'] === '0') {
return $this->trans(
'The item %product% in your cart is now a product with attributes. Please delete it and choose one of its combinations to proceed with your order.',
['%product%' => $product['name']],
'Shop.Notifications.Error'
);
}
}
$product = $this->context->cart->checkQuantities(true);
if (true === $product || !is_array($product)) {
return true;
}
$productName = !empty($product['attributes']) ? $product['name'] . ' ' . $product['attributes'] : $product['name'];
if ($product['active'] && $product['quantity_available'] > 0) {
return $this->trans(
'You can only buy %quantity% "%product%". Please adjust the quantity in your cart to continue.',
[
'%product%' => $productName,
'%quantity%' => $product['quantity_available'],
],
'Shop.Notifications.Error'
);
}
return $this->trans(
'This product (%product%) is no longer available.',
['%product%' => $productName],
'Shop.Notifications.Error'
);
}
/**
* Check that minimal quantity conditions are respected for each product in the cart
*/
private function checkCartProductsMinimalQuantities(): void
{
$productList = $this->context->cart->getProducts();
foreach ($productList as $product) {
if ($product['minimal_quantity'] > $product['cart_quantity']) {
// display minimal quantity warning error message
$this->errors[] = $this->trans(
'The minimum purchase order quantity for the product %product% is %quantity%.',
[
'%product%' => $product['name'],
'%quantity%' => $product['minimal_quantity'],
],
'Shop.Notifications.Error'
);
}
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class ChangeCurrencyControllerCore extends FrontController
{
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
$currency = new Currency((int) Tools::getValue('id_currency'));
if (Validate::isLoadedObject($currency) && !$currency->deleted) {
$this->context->cookie->id_currency = (int) $currency->id;
$this->ajaxRender('1');
return;
}
$this->ajaxRender('0');
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShopBundle\Security\Admin\LegacyAdminTokenValidator;
class CmsControllerCore extends FrontController
{
public const CMS_CASE_PAGE = 1;
public const CMS_CASE_CATEGORY = 2;
/** @var string */
public $php_self = 'cms';
public $assignCase;
/**
* @var CMS|null
*/
protected $cms;
/**
* @var CMSCategory|null
*/
protected $cms_category;
/** @var bool */
public $ssl = false;
public function canonicalRedirection(string $canonicalURL = ''): void
{
if (Validate::isLoadedObject($this->cms) && ($canonicalURL = $this->context->link->getCMSLink($this->cms, $this->cms->link_rewrite))) {
parent::canonicalRedirection($canonicalURL);
} elseif (Validate::isLoadedObject($this->cms_category) && ($canonicalURL = $this->context->link->getCMSCategoryLink($this->cms_category))) {
parent::canonicalRedirection($canonicalURL);
}
}
/**
* Initialize cms controller.
*
* @see FrontController::init()
*/
public function init(): void
{
if ($id_cms = (int) Tools::getValue('id_cms')) {
$this->cms = new CMS($id_cms, $this->context->language->id, $this->context->shop->id);
} elseif ($id_cms_category = (int) Tools::getValue('id_cms_category')) {
$this->cms_category = new CMSCategory($id_cms_category, $this->context->language->id, $this->context->shop->id);
}
if (Configuration::get('PS_SSL_ENABLED') && Tools::getValue('content_only') && $id_cms && Validate::isLoadedObject($this->cms)
&& in_array($id_cms, $this->getSSLCMSPageIds())) {
$this->ssl = true;
}
parent::init();
$this->canonicalRedirection();
if (Validate::isLoadedObject($this->cms)) {
if (!$this->cms->isAssociatedToShop()) {
$this->redirect_after = '404';
$this->redirect();
} elseif (!$this->cms->active) {
$adminTokenValidator = $this->getContainer()->get(LegacyAdminTokenValidator::class);
$isAdminTokenValid = $adminTokenValidator->isTokenValid((int) Tools::getValue('id_employee'), Tools::getValue('adtoken'));
if (!$isAdminTokenValid) {
$this->redirect_after = '404';
$this->redirect();
}
} else {
$this->assignCase = self::CMS_CASE_PAGE;
}
} elseif (Validate::isLoadedObject($this->cms_category) && $this->cms_category->active) {
$this->assignCase = self::CMS_CASE_CATEGORY;
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if ($this->assignCase == self::CMS_CASE_PAGE) {
$cmsVar = $this->objectPresenter->present($this->cms);
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredCmsContent = Hook::exec(
'filterCmsContent',
['object' => $cmsVar],
null,
false,
true,
false,
null,
true
);
if (!empty($filteredCmsContent['object'])) {
$cmsVar = $filteredCmsContent['object'];
}
$this->context->smarty->assign([
'cms' => $cmsVar,
]);
if ($this->cms->indexation == 0) {
$this->context->smarty->assign('nobots', true);
}
$this->setTemplate(
'cms/page',
['entity' => 'cms', 'id' => $this->cms->id]
);
} elseif ($this->assignCase == self::CMS_CASE_CATEGORY) {
$cmsCategoryVar = $this->getTemplateVarCategoryCms();
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredCmsCategoryContent = Hook::exec(
'filterCmsCategoryContent',
['object' => $cmsCategoryVar],
null,
false,
true,
false,
null,
true
);
if (!empty($filteredCmsCategoryContent['object'])) {
$cmsCategoryVar = $filteredCmsCategoryContent['object'];
}
$this->context->smarty->assign($cmsCategoryVar);
$this->setTemplate(
'cms/category',
['entity' => 'cms_category', 'id' => $this->cms_category->id]
);
}
parent::initContent();
}
/**
* Return an array of IDs of CMS pages, which shouldn't be forwared to their canonical URLs in SSL environment.
* Required for pages which are shown in iframes.
*/
protected function getSSLCMSPageIds(): array
{
return [(int) Configuration::get('PS_CONDITIONS_CMS_ID'), (int) Configuration::get('LEGAL_CMS_ID_REVOCATION')];
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
if ($this->assignCase == self::CMS_CASE_CATEGORY) {
$cmsCategory = new CMSCategory($this->cms_category->id_cms_category);
} else {
$cmsCategory = new CMSCategory($this->cms->id_cms_category);
}
if ($cmsCategory->id_parent != 0) {
foreach (array_reverse($cmsCategory->getParentsCategories()) as $category) {
if ($category['active']) {
$cmsSubCategory = new CMSCategory($category['id_cms_category']);
$breadcrumb['links'][] = [
'title' => $cmsSubCategory->getName(),
'url' => $this->context->link->getCMSCategoryLink($cmsSubCategory),
];
}
}
}
if ($this->assignCase == self::CMS_CASE_PAGE && $this->context->controller instanceof CmsControllerCore) {
$breadcrumb['links'][] = [
'title' => $this->context->controller->cms->meta_title,
'url' => $this->context->link->getCMSLink($this->context->controller->cms),
];
}
return $breadcrumb;
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
if ($this->assignCase == self::CMS_CASE_CATEGORY) {
$page['body_classes']['cms-id-' . $this->cms_category->id] = true;
} else {
$page['body_classes']['cms-id-' . $this->cms->id] = true;
if (!$this->cms->indexation) {
$page['meta']['robots'] = 'noindex';
}
}
return $page;
}
public function getTemplateVarCategoryCms(): array
{
$categoryCms = [];
$categoryCms['cms_category'] = $this->objectPresenter->present($this->cms_category);
$categoryCms['sub_categories'] = [];
$categoryCms['cms_pages'] = [];
foreach ($this->cms_category->getSubCategories($this->context->language->id) as $subCategory) {
$categoryCms['sub_categories'][$subCategory['id_cms_category']] = $subCategory;
$categoryCms['sub_categories'][$subCategory['id_cms_category']]['link'] = $this->context->link->getCMSCategoryLink($subCategory['id_cms_category'], $subCategory['link_rewrite']);
}
foreach (CMS::getCMSPages($this->context->language->id, (int) $this->cms_category->id, true, (int) $this->context->shop->id) as $cmsPages) {
$categoryCms['cms_pages'][$cmsPages['id_cms']] = $cmsPages;
$categoryCms['cms_pages'][$cmsPages['id_cms']]['link'] = $this->context->link->getCMSLink($cmsPages['id_cms'], $cmsPages['link_rewrite']);
}
return $categoryCms;
}
/**
* @return CMS|null
*/
public function getCms(): ?CMS
{
return $this->cms;
}
/**
* @return CMSCategory|null
*/
public function getCmsCategory(): ?CMSCategory
{
return $this->cms_category;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
if (Validate::isLoadedObject($this->cms)) {
return $this->context->link->getCMSLink($this->cms, $this->cms->link_rewrite);
} elseif (Validate::isLoadedObject($this->cms_category)) {
return $this->context->link->getCMSCategoryLink($this->cms_category);
}
return '';
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class ContactControllerCore extends FrontController
{
/** @var string */
public $php_self = 'contact';
/** @var bool */
public $ssl = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->setTemplate('contact');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->getTranslator()->trans('Contact us', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('contact'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('contact');
}
}

View File

@@ -0,0 +1,205 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class DiscountControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'discount';
/** @var string */
public $authRedirection = 'discount';
/** @var bool */
public $ssl = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
$this->context->smarty->assign([
'cart_rules' => $this->getTemplateVarCartRules(),
]);
parent::initContent();
$this->setTemplate('customer/discount');
}
public function getTemplateVarCartRules(): array
{
$cart_rules = [];
$customerId = $this->context->customer->id;
$languageId = $this->context->language->id;
$vouchers = CartRule::getCustomerCartRules(
$languageId,
$customerId,
true,
false
);
foreach ($vouchers as $key => $voucher) {
$voucherCustomerId = (int) $voucher['id_customer'];
$voucherIsRestrictedToASingleCustomer = ($voucherCustomerId !== 0);
if ($voucherIsRestrictedToASingleCustomer && $customerId !== $voucherCustomerId) {
continue;
}
if ($voucher['quantity'] === 0 || $voucher['quantity_for_user'] === 0) {
continue;
}
$cart_rule = $this->buildCartRuleFromVoucher($voucher);
$cart_rules[$key] = $cart_rule;
}
return $cart_rules;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Your vouchers', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('discount'),
];
return $breadcrumb;
}
/**
* @param array $voucher
*
* @return mixed
*/
protected function getCombinableVoucherTranslation(array $voucher)
{
if ($voucher['cart_rule_restriction']) {
$combinableVoucherTranslation = $this->trans('No', [], 'Shop.Theme.Global');
} else {
$combinableVoucherTranslation = $this->trans('Yes', [], 'Shop.Theme.Global');
}
return $combinableVoucherTranslation;
}
/**
* Formats a value of a voucher with fixed reduction
*
* @param bool $hasTaxIncluded
* @param float $amount
* @param int $currencyId
*
* @return string
*/
protected function formatReductionAmount(bool $hasTaxIncluded, float $amount, int $currencyId)
{
if ($hasTaxIncluded) {
$taxTranslation = $this->trans('Tax included', [], 'Shop.Theme.Checkout');
} else {
$taxTranslation = $this->trans('Tax excluded', [], 'Shop.Theme.Checkout');
}
return sprintf(
'%s ' . $taxTranslation,
$this->context->getCurrentLocale()->formatPrice($amount, Currency::getIsoCodeById((int) $currencyId))
);
}
/**
* Formats a value of a voucher with percentage reduction
*
* @param float $percentage
*
* @return string
*/
protected function formatReductionInPercentage(float $percentage)
{
return sprintf('%s%%', $this->context->getCurrentLocale()->formatNumber($percentage));
}
/**
* Formats all reductions and benefits of a voucher. (One voucher can provide a discount and gift at the same time.)
*
* @param array $voucher
*
* @return array
*/
protected function accumulateCartRuleValue(array $voucher)
{
$cartRuleValue = [];
if ($voucher['reduction_percent'] > 0) {
$cartRuleValue[] = $this->formatReductionInPercentage((float) $voucher['reduction_percent']);
}
if ($voucher['reduction_amount'] > 0) {
$cartRuleValue[] = $this->formatReductionAmount(
(bool) $voucher['reduction_tax'],
(float) $voucher['reduction_amount'],
$voucher['reduction_currency']
);
}
if ($voucher['free_shipping']) {
$cartRuleValue[] = $this->trans('Free shipping', [], 'Shop.Theme.Checkout');
}
if ($voucher['gift_product'] > 0) {
$cartRuleValue[] = Product::getProductName(
$voucher['gift_product'],
$voucher['gift_product_attribute']
);
}
return $cartRuleValue;
}
/**
* Prepares a single row of voucher table to show it to customer.
*
* @param array $voucher
*
* @return array
*/
protected function buildCartRuleFromVoucher(array $voucher): array
{
$voucher['voucher_date'] = Tools::displayDate($voucher['date_to'], false);
if ((int) $voucher['minimum_amount'] === 0) {
$voucher['voucher_minimal'] = $this->trans('None', [], 'Shop.Theme.Global');
} else {
$voucher['voucher_minimal'] = $this->context->getCurrentLocale()->formatPrice(
$voucher['minimum_amount'],
Currency::getIsoCodeById((int) $voucher['minimum_amount_currency'])
);
}
$voucher['voucher_cumulable'] = $this->getCombinableVoucherTranslation($voucher);
// Get all benefits of this voucher into array (discount, gift, free shipping etc.)
$cartRuleValues = $this->accumulateCartRuleValue($voucher);
// And combine them into a string
// If for some magical reason the voucher has no benefit (should not be achievable in BO), we display a dash
if (0 === count($cartRuleValues)) {
$voucher['value'] = '-';
} else {
$voucher['value'] = implode(' + ', $cartRuleValues);
}
return $voucher;
}
}

View File

@@ -0,0 +1,343 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class GetFileControllerCore extends FrontController
{
protected const MIME_TYPES = [
'ez' => 'application/andrew-inset',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'doc' => 'application/msword',
'oda' => 'application/oda',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'bcpio' => 'application/x-bcpio',
'vcd' => 'application/x-cdlink',
'pgn' => 'application/x-chess-pgn',
'cpio' => 'application/x-cpio',
'csh' => 'application/x-csh',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'spl' => 'application/x-futuresplash',
'gtar' => 'application/x-gtar',
'hdf' => 'application/x-hdf',
'js' => 'application/x-javascript',
'skp' => 'application/x-koan',
'skd' => 'application/x-koan',
'skt' => 'application/x-koan',
'skm' => 'application/x-koan',
'latex' => 'application/x-latex',
'nc' => 'application/x-netcdf',
'cdf' => 'application/x-netcdf',
'sh' => 'application/x-sh',
'shar' => 'application/x-shar',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'sv4cpio' => 'application/x-sv4cpio',
'sv4crc' => 'application/x-sv4crc',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'tex' => 'application/x-tex',
'texinfo' => 'application/x-texinfo',
'texi' => 'application/x-texinfo',
't' => 'application/x-troff',
'tr' => 'application/x-troff',
'roff' => 'application/x-troff',
'man' => 'application/x-troff-man',
'me' => 'application/x-troff-me',
'ms' => 'application/x-troff-ms',
'ustar' => 'application/x-ustar',
'src' => 'application/x-wais-source',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'zip' => 'application/zip',
'au' => 'audio/basic',
'snd' => 'audio/basic',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'kar' => 'audio/midi',
'mpga' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'm3u' => 'audio/x-mpegurl',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'wav' => 'audio/x-wav',
'pdb' => 'chemical/x-pdb',
'xyz' => 'chemical/x-xyz',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'ief' => 'image/ief',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'tiff' => 'image/tiff',
'tif' => 'image/tif',
'djvu' => 'image/vnd.djvu',
'djv' => 'image/vnd.djvu',
'wbmp' => 'image/vnd.wap.wbmp',
'ras' => 'image/x-cmu-raster',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'rgb' => 'image/x-rgb',
'xbm' => 'image/x-xbitmap',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-windowdump',
'igs' => 'model/iges',
'iges' => 'model/iges',
'msh' => 'model/mesh',
'mesh' => 'model/mesh',
'silo' => 'model/mesh',
'wrl' => 'model/vrml',
'vrml' => 'model/vrml',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'asc' => 'text/plain',
'txt' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'sgml' => 'text/sgml',
'sgm' => 'text/sgml',
'tsv' => 'text/tab-seperated-values',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
'etx' => 'text/x-setext',
'xml' => 'text/xml',
'xsl' => 'text/xml',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'mxu' => 'video/vnd.mpegurl',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'ice' => 'x-conference-xcooltalk',
];
/** @var bool */
protected $display_header = false;
/** @var bool */
protected $display_footer = false;
/**
* Initialize the controller.
*
* @see FrontController::init()
*/
public function init(): void
{
if (isset($this->context->employee) && $this->context->employee->isLoggedBack() && Tools::getValue('file')) {
// Admin can directly access to file
$filename = Tools::getValue('file');
if (!Validate::isSha1($filename)) {
throw new PrestaShopException('Filename is not a valid SHA1 checksum.');
}
$file = _PS_DOWNLOAD_DIR_ . (string) preg_replace('/\.{2,}/', '.', $filename);
$filename = ProductDownload::getFilenameFromFilename(Tools::getValue('file'));
if (empty($filename)) {
$newFileName = Tools::getValue('filename');
if (!empty($newFileName)) {
$filename = Tools::getValue('filename');
} else {
$filename = 'file';
}
}
if (!file_exists($file)) {
Tools::redirect('index.php');
}
} else {
if (!($key = Tools::getValue('key'))) {
$this->displayCustomError('Invalid key.');
}
Tools::setCookieLanguage();
if (!$this->context->customer->isLogged()) {
if (!Tools::getValue('secure_key') && !Tools::getValue('id_order')) {
Tools::redirect('index.php?controller=authentication&back=get-file.php%26key=' . $key);
} elseif (Tools::getValue('secure_key') && Tools::getValue('id_order')) {
$order = new Order((int) Tools::getValue('id_order'));
if (!Validate::isLoadedObject($order)) {
$this->displayCustomError('Invalid key.');
}
if ($order->secure_key != Tools::getValue('secure_key')) {
$this->displayCustomError('Invalid key.');
}
} else {
$this->displayCustomError('Invalid key.');
}
}
/* Key format: <sha1-filename>-<hashOrder> */
$tmp = explode('-', $key);
if (count($tmp) != 2) {
$this->displayCustomError('Invalid key.');
}
$hash = $tmp[1];
if (!($info = OrderDetail::getDownloadFromHash($hash))) {
$this->displayCustomError('This product does not exist in our store.');
}
/* check whether order has been paid, which is required to download the product */
$order = new Order((int) $info['id_order']);
$state = $order->getCurrentOrderState();
if (!$state || !$state->paid) {
$this->displayCustomError('This order has not been paid.');
}
// Check whether the order was made by the current user
// If the order was made by a guest, skip this step
$customer = new Customer((int) $order->id_customer);
if (!$customer->is_guest && $order->secure_key !== $this->context->customer->secure_key) {
Tools::redirect('index.php?controller=authentication&back=get-file.php%26key=' . $key);
}
/* Product no more present in catalog */
if (!isset($info['id_product_download']) || empty($info['id_product_download'])) {
$this->displayCustomError('This product has been deleted.');
}
if (!Validate::isFileName($info['filename']) || !file_exists(_PS_DOWNLOAD_DIR_ . $info['filename'])) {
$this->displayCustomError('This file no longer exists.');
}
if (isset($info['product_quantity_refunded'], $info['product_quantity_return'])
&& ($info['product_quantity_refunded'] > 0 || $info['product_quantity_return'] > 0)) {
$this->displayCustomError('This product has been refunded.');
}
$now = time();
$product_deadline = (int) strtotime($info['download_deadline']);
if ($now > $product_deadline && $info['download_deadline'] != '0000-00-00 00:00:00') {
$this->displayCustomError('The product deadline is in the past.');
}
if ($info['date_expiration'] !== '0000-00-00 00:00:00') {
$customer_deadline = (int) strtotime($info['date_expiration']);
if ($now > $customer_deadline) {
$this->displayCustomError('Expiration date has passed, you cannot download this product');
}
}
if ($info['download_nb'] >= $info['nb_downloadable'] && $info['nb_downloadable']) {
$this->displayCustomError('You have reached the maximum number of allowed downloads.');
}
/* Access is authorized -> increment download value for the customer */
OrderDetail::incrementDownload($info['id_order_detail']);
$file = _PS_DOWNLOAD_DIR_ . $info['filename'];
$filename = $info['display_filename'];
}
$this->sendFile($file, $filename);
}
protected function sendFile(string $file, string $filename, bool $forceDownload = true): void
{
if (ob_get_level() && ob_get_length() > 0) {
ob_end_clean();
}
/* Set headers for download */
header('Content-Transfer-Encoding: binary');
header('Content-Type: ' . $this->getMimeType($file, $filename));
header('Content-Length: ' . filesize($file));
if ($forceDownload) {
header('Content-Disposition: attachment; filename="' . $filename . '"');
}
// prevents max execution timeout, when reading large files
@set_time_limit(0);
$fp = fopen($file, 'rb');
if ($fp && is_resource($fp)) {
while (!feof($fp)) {
echo fgets($fp, 16384);
}
}
exit;
}
private function getMimeType(string $file, string $filename): string
{
if (function_exists('finfo_open')) {
$finfo = @finfo_open(FILEINFO_MIME);
$mimeType = @finfo_file($finfo, $file);
@finfo_close($finfo);
} elseif (function_exists('mime_content_type')) {
$mimeType = @mime_content_type($file);
} elseif (function_exists('exec')) {
$mimeType = trim(@exec('file -b --mime-type ' . escapeshellarg($file)));
if (!$mimeType) {
$mimeType = trim(@exec('file --mime ' . escapeshellarg($file)));
}
if (!$mimeType) {
$mimeType = trim(@exec('file -bi ' . escapeshellarg($file)));
}
}
if (empty($mimeType)) {
$bName = basename($filename);
$bName = explode('.', $bName);
$bName = strtolower($bName[count($bName) - 1]);
$mimeType = static::MIME_TYPES[$bName] ?? 'application/octet-stream';
}
return $mimeType;
}
/**
* Display an error message with js
* and redirect using js function.
*
* @param string $msg
*/
protected function displayCustomError(string $msg)
{
$translations = [
'Invalid key.' => $this->trans('Invalid key.', [], 'Shop.Notifications.Error'),
'This product does not exist in our store.' => $this->trans('This product does not exist in our store.', [], 'Shop.Notifications.Error'),
'This product has been deleted.' => $this->trans('This product has been deleted.', [], 'Shop.Notifications.Error'),
'This file no longer exists.' => $this->trans('This file no longer exists.', [], 'Shop.Notifications.Error'),
'This product has been refunded.' => $this->trans('This product has been refunded.', [], 'Shop.Notifications.Error'),
'The product deadline is in the past.' => $this->trans('The product deadline is in the past.', [], 'Shop.Notifications.Error'),
'Expiration date exceeded' => $this->trans('The product expiration date has passed, preventing you from download this product.', [], 'Shop.Notifications.Error'),
'Expiration date has passed, you cannot download this product' => $this->trans('Expiration date has passed, you cannot download this product.', [], 'Shop.Notifications.Error'),
'You have reached the maximum number of allowed downloads.' => $this->trans('You have reached the maximum number of downloads allowed.', [], 'Shop.Notifications.Error'),
]; ?>
<script type="text/javascript">
//<![CDATA[
alert("<?php echo isset($translations[$msg]) ? html_entity_decode($translations[$msg], ENT_QUOTES, 'utf-8') : html_entity_decode($msg, ENT_QUOTES, 'utf-8'); ?>");
window.location.href = '<?php echo __PS_BASE_URI__; ?>';
//]]>
</script>
<?php
exit;
}
}

View File

@@ -0,0 +1,183 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderPresenter;
use PrestaShop\PrestaShop\Core\Security\PasswordPolicyConfiguration;
class GuestTrackingControllerCore extends FrontController
{
/** @var bool */
public $ssl = true;
/** @var bool */
public $auth = false;
/** @var string */
public $php_self = 'guest-tracking';
protected $order;
/**
* Initialize guest tracking controller.
*
* @see FrontController::init()
*/
public function init(): void
{
if ($this->context->customer->isLogged()) {
Tools::redirect('history.php');
}
parent::init();
}
/**
* Start forms process.
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
$order_reference = current(explode('#', Tools::getValue('order_reference')));
$email = Tools::getValue('email');
if (!$email && !$order_reference) {
return;
} elseif (!$email || !$order_reference) {
$this->errors[] = $this->getTranslator()->trans(
'Please provide the required information',
[],
'Shop.Notifications.Error'
);
return;
}
$this->order = Order::getByReferenceAndEmail($order_reference, $email);
if (!Validate::isLoadedObject($this->order)) {
$this->errors[] = $this->getTranslator()->trans(
'We couldn\'t find your order with the information provided, please try again',
[],
'Shop.Notifications.Error'
);
}
if (Tools::isSubmit('submitTransformGuestToCustomer') && Tools::getValue('password')) {
$customer = new Customer((int) $this->order->id_customer);
/** @var string $password */
$password = Tools::getValue('password');
if (empty($password)) {
$this->errors[] = $this->trans(
'Enter a password to transform your guest account into a customer account.',
[],
'Shop.Forms.Help'
);
} elseif (!Validate::isAcceptablePasswordLength($password)) {
$this->errors[] = $this->trans(
'Your password length must be between %d and %d',
[Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_LENGTH), Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MAXIMUM_LENGTH)],
'Shop.Forms.Help'
);
} elseif (!Validate::isAcceptablePasswordScore($password)) {
$this->errors[] = $this->trans(
'Customer password is too weak',
[],
'Shop.Forms.Help'
);
// Prevent error
// A) either on page refresh
// B) if we already transformed him in other window or through backoffice
} elseif ($customer->is_guest == 0) {
$this->errors[] = $this->trans(
'A customer account has already been created from this guest account. Please sign in.',
[],
'Shop.Notifications.Error'
);
// Check if a different customer with the same email was not already created in a different window or through backoffice
} elseif (Customer::customerExists($customer->email)) {
$this->errors[] = $this->trans(
'You can\'t transform your account into a customer account, because a registered customer with the same email already exists.',
[],
'Shop.Notifications.Error'
);
// Attempt to convert the customer
} elseif ($customer->transformToCustomer($this->context->language->id, $password)) {
$this->success[] = $this->trans(
'Your guest account has been successfully transformed into a customer account. You can now log in as a registered shopper.',
[],
'Shop.Notifications.Success'
);
} else {
$this->errors[] = $this->trans(
'An unexpected error occurred while creating your account.',
[],
'Shop.Notifications.Error'
);
}
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
if (!Validate::isLoadedObject($this->order)) {
$this->setTemplate('customer/guest-login');
return;
}
if ((int) $this->order->isReturnable()) {
$this->info[] = $this->trans(
'You cannot return merchandise with a guest account.',
[],
'Shop.Notifications.Warning'
);
}
// Kept for backwards compatibility (is_customer), inline it in later versions
$registered_customer_exists = Customer::customerExists(Tools::getValue('email'));
$this->context->smarty->assign([
'order' => (new OrderPresenter())->present($this->order),
'guest_email' => Tools::getValue('email'),
'registered_customer_exists' => $registered_customer_exists,
'is_customer' => $registered_customer_exists, // Kept for backwards compatibility
'HOOK_DISPLAYORDERDETAIL' => Hook::exec('displayOrderDetail', ['order' => $this->order]),
]);
$this->setTemplate('customer/guest-tracking');
}
public function getBreadcrumbLinks(): array
{
$breadcrumbLinks = parent::getBreadcrumbLinks();
$breadcrumbLinks['links'][] = [
'title' => $this->getTranslator()->trans('Guest order tracking', [], 'Shop.Theme.Checkout'),
'url' => $this->context->link->getPageLink('guest-tracking'),
];
if (Validate::isLoadedObject($this->order)) {
$breadcrumbLinks['links'][] = [
'title' => $this->order->reference,
'url' => '#',
];
}
return $breadcrumbLinks;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('guest-tracking');
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderPresenter;
class HistoryControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'history';
/** @var string */
public $authRedirection = 'history';
/** @var bool */
public $ssl = true;
/** @var OrderPresenter|null */
public $order_presenter;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
if ($this->order_presenter === null) {
$this->order_presenter = new OrderPresenter();
}
if (Tools::isSubmit('slowvalidation')) {
$this->warning[] = $this->trans('If you have just placed an order, it may take a few minutes for it to be validated. Please refresh this page if your order is missing.', [], 'Shop.Notifications.Warning');
}
$this->context->smarty->assign([
'orders' => $this->getTemplateVarOrders(),
]);
parent::initContent();
$this->setTemplate('customer/history');
}
public function getTemplateVarOrders(): array
{
$orders = [];
$customer_orders = Order::getCustomerOrders($this->context->customer->id);
foreach ($customer_orders as $customer_order) {
$order = new Order((int) $customer_order['id_order']);
$orders[$customer_order['id_order']] = $this->order_presenter->present($order);
}
return $orders;
}
/**
* Generates a URL to download the PDF invoice of a given order
*
* @param Order $order
* @param Context $context
*
* @return string
*/
public static function getUrlToInvoice(Order $order, Context $context)
{
$url_to_invoice = '';
if ((bool) Configuration::get('PS_INVOICE') && OrderState::invoiceAvailable($order->current_state) && count($order->getInvoicesCollection())) {
$params = [
'id_order' => (int) $order->id,
'secure_key' => (!$context->customer->isLogged()) ? $order->secure_key : null,
];
$url_to_invoice = $context->link->getPageLink('pdf-invoice', null, null, $params);
}
return $url_to_invoice;
}
/**
* Generates a URL to reorder a given order
*
* @param int $id_order
* @param Context $context
*
* @return string
*/
public static function getUrlToReorder(int $id_order, Context $context)
{
$url_to_reorder = '';
if (!(bool) Configuration::get('PS_DISALLOW_HISTORY_REORDERING')) {
$params = [
'submitReorder' => 1,
'id_order' => (int) $id_order,
];
$url_to_reorder = $context->link->getPageLink('order', null, null, $params);
}
return $url_to_reorder;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Order history', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('history'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class IdentityControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'identity';
/** @var string */
public $authRedirection = 'identity';
/** @var bool */
public $ssl = true;
public $passwordRequired = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
$should_redirect = false;
$customer_form = $this->makeCustomerForm()->setPasswordRequired($this->passwordRequired);
$customer = new Customer();
$customer_form->getFormatter()
->setAskForNewPassword(true)
->setAskForPassword($this->passwordRequired)
->setPasswordRequired($this->passwordRequired)
->setPartnerOptinRequired($customer->isFieldRequired('optin'));
if (Tools::isSubmit('submitCreate')) {
$customer_form->fillWith(Tools::getAllValues());
if ($customer_form->submit()) {
$this->success[] = $this->trans('Information successfully updated.', [], 'Shop.Notifications.Success');
$should_redirect = true;
} else {
$this->errors[] = $this->trans('Could not update your information, please check your data.', [], 'Shop.Notifications.Error');
}
} else {
$customer_form->fillFromCustomer(
$this->context->customer
);
}
$this->context->smarty->assign([
'customer_form' => $customer_form->getProxy(),
]);
if ($should_redirect) {
$this->redirectWithNotifications($this->getCurrentURL());
}
parent::initContent();
$this->setTemplate('customer/identity');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Your personal information', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('identity'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class IndexControllerCore extends FrontController
{
/** @var string */
public $php_self = 'index';
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->context->smarty->assign([
'HOOK_HOME' => Hook::exec('displayHome'),
]);
$this->setTemplate('index');
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('index');
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class MyAccountControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'my-account';
/** @var string */
public $authRedirection = 'my-account';
/** @var bool */
public $ssl = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->setTemplate('customer/my-account');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
return $breadcrumb;
}
}

View File

@@ -0,0 +1,340 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderPresenter;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
use PrestaShop\PrestaShop\Core\Security\PasswordPolicyConfiguration;
use ZxcvbnPhp\Zxcvbn;
class OrderConfirmationControllerCore extends FrontController
{
/** @var bool */
public $ssl = true;
/** @var string */
public $php_self = 'order-confirmation';
/** @var int Cart ID */
public $id_cart;
public $id_module;
public $id_order;
public $secure_key;
/** @var Order Order object we found by cart ID */
protected $order;
/** @var Customer Customer object related to the cart */
protected $customer;
public $reference; // Deprecated
public $order_presenter; // Deprecated
/**
* Initialize order confirmation controller.
*
* @see FrontController::init()
*/
public function init(): void
{
// Test below to prevent unnecessary logs from "parent::init()"
$this->id_cart = (int) Tools::getValue('id_cart', 0);
if (!empty($this->context->cookie->id_cart) && $this->context->cookie->id_cart == $this->id_cart) {
$cart = new Cart($this->id_cart);
if ($cart->orderExists()) {
unset($this->context->cookie->id_cart);
}
}
parent::init();
/*
* There is a special case for free orders, when this page does more than just display
* the confirmation. It also creates the order if it is free. This is done if free_order
* parameter is passed to this page.
*
* After the order is created, we redirect to the same page without free_order parameter
* and display the confirmation as usual.
*/
if ((bool) Tools::getValue('free_order') === true) {
$this->checkFreeOrder();
}
/*
* Because of order splitting scenarios, we must get the data by id_cart parameter (not id_order),
* so we can display all orders made from this cart.
*
* It's not implemented yet, however, and probably won't be, because we are switching to a new
* logic of multiple shipments per order, which doesn't require splitting orders anymore.
*/
$this->id_order = Order::getIdByCartId((int) $this->id_cart);
if (empty($this->id_order)) {
Tools::redirect('pagenotfound');
}
// Now, load the order object and check validity
$this->order = new Order((int) $this->id_order);
if (!Validate::isLoadedObject($this->order)) {
Tools::redirect('pagenotfound');
}
/*
* Now, to prevent users from seeing other customers' order confirmations, we are using
* a secure key mechanism. The confirmation link contains a unique key which is also saved
* in database when the order is created. If the key from the URL doesn't match the one
* in database, we redirect to "page not found".
*/
$this->secure_key = Tools::getValue('key', false);
if (empty($this->secure_key) || $this->secure_key != $this->order->secure_key) {
Tools::redirect('pagenotfound');
}
// Last step, initialize some other data
$this->id_module = $this->order->module == 'free_order' ? -1 : Module::getModuleIdByName($this->order->module);
// This data is kept only for backward compatibility purposes
$this->reference = (string) $this->order->reference;
// If checks passed, initialize customer, we will need him anyway
$this->customer = new Customer((int) $this->order->id_customer);
}
/**
* Logic after submitting forms
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
if (Tools::isSubmit('submitTransformGuestToCustomer')) {
// Only variable we need is the password
// There is no need to check other variables, because hacker would be kicked out in init(), if he tried to convert another customer
$password = Tools::getValue('password');
if (empty($password)) {
$this->errors[] = $this->trans(
'Enter a password to transform your guest account into a customer account.',
[],
'Shop.Forms.Help'
);
} else {
if (Validate::isAcceptablePasswordLength($password) === false) {
$this->errors[] = $this->translator->trans(
'Password must be between %d and %d characters long',
[
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_LENGTH),
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MAXIMUM_LENGTH),
],
'Shop.Notifications.Error'
);
}
if (Validate::isAcceptablePasswordScore($password) === false) {
$wordingsForScore = [
$this->translator->trans('Very weak', [], 'Shop.Theme.Global'),
$this->translator->trans('Weak', [], 'Shop.Theme.Global'),
$this->translator->trans('Average', [], 'Shop.Theme.Global'),
$this->translator->trans('Strong', [], 'Shop.Theme.Global'),
$this->translator->trans('Very strong', [], 'Shop.Theme.Global'),
];
$globalErrorMessage = $this->translator->trans(
'The minimum score must be: %s',
[
$wordingsForScore[(int) Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_SCORE)],
],
'Shop.Notifications.Error'
);
if ($this->context->shop->theme->get('global_settings.new_password_policy_feature') !== true) {
$zxcvbn = new Zxcvbn();
$result = $zxcvbn->passwordStrength($password);
if (!empty($result['feedback']['warning'])) {
$this->errors[] = $this->translator->trans(
$result['feedback']['warning'], [], 'Shop.Theme.Global'
);
} else {
$this->errors[] = $globalErrorMessage;
}
foreach ($result['feedback']['suggestions'] as $suggestion) {
$this->errors[] = $this->translator->trans($suggestion, [], 'Shop.Theme.Global');
}
} else {
$this->errors[] = $globalErrorMessage;
}
}
}
if (!empty($this->errors)) {
return;
}
// Prevent error
// A) either on page refresh
// B) if we already transformed him in other window or through backoffice
if ($this->customer->is_guest == 0) {
$this->errors[] = $this->trans(
'A customer account has already been created from this guest account. Please sign in.',
[],
'Shop.Notifications.Error'
);
// Check if a different customer with the same email was not already created in a different window or through backoffice
} elseif (Customer::customerExists($this->customer->email)) {
$this->errors[] = $this->trans(
'You can\'t transform your account into a customer account, because a registered customer with the same email already exists.',
[],
'Shop.Notifications.Error'
);
// Attempt to convert the customer
} elseif ($this->customer->transformToCustomer($this->context->language->id, $password)) {
$this->success[] = $this->trans(
'Your guest account has been successfully transformed into a customer account. You can now log in as a registered shopper.',
[],
'Shop.Notifications.Success'
);
} else {
$this->errors[] = $this->trans(
'An unexpected error occurred while creating your account.',
[],
'Shop.Notifications.Error'
);
}
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
/** @var FeatureFlagStateCheckerInterface $featureFlagManager */
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class);
$this->context->smarty->assign([
'HOOK_ORDER_CONFIRMATION' => $this->displayOrderConfirmation($this->order),
'HOOK_PAYMENT_RETURN' => $this->displayPaymentReturn($this->order),
'order' => (new OrderPresenter())->present($this->order),
'order_customer' => $this->objectPresenter->present($this->customer),
'is_multishipment_enabled' => $featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_IMPROVED_SHIPMENT),
'registered_customer_exists' => Customer::customerExists($this->customer->email),
]);
$this->setTemplate('checkout/order-confirmation');
// If logged in guest we clear the cookie for security reasons
if ($this->context->customer->is_guest) {
$this->context->customer->mylogout();
}
}
/**
* Execute the hook displayPaymentReturn. This hook should be used to display payment
* information on the order confirmation page. Payment status, instructions, QR code etc.
*/
public function displayPaymentReturn(Order $order)
{
// Check if we have a sensible module ID. Free orders have -1 as module ID
if (!Validate::isUnsignedId($this->id_module)) {
return false;
}
// Hook called only for the module concerned
return Hook::exec('displayPaymentReturn', ['order' => $order], $this->id_module);
}
/**
* Execute the hook displayOrderConfirmation.
*/
public function displayOrderConfirmation(Order $order)
{
return Hook::exec('displayOrderConfirmation', ['order' => $order]);
}
/**
* Check if an order is free and create it. After creation, we redirect to the same page
* which will display the order confirmation as usual.
*/
protected function checkFreeOrder(): void
{
/*
* Verify if this is not a faulty or duplicate call. If an order
* already exists for this cart, we do not create another one.
*/
if (!empty(Order::getIdByCartId((int) $this->id_cart))) {
return;
}
$cart = $this->context->cart;
if ($cart->id_customer == 0 || $cart->id_address_delivery == 0 || $cart->id_address_invoice == 0) {
Tools::redirect($this->context->link->getPageLink('order'));
}
$customer = new Customer($cart->id_customer);
if (!Validate::isLoadedObject($customer)) {
Tools::redirect($this->context->link->getPageLink('order'));
}
$total = (float) $cart->getOrderTotal(true, Cart::BOTH);
if ($total > 0) {
Tools::redirect($this->context->link->getPageLink('order'));
}
$order = new PaymentFree();
$order->validateOrder(
$cart->id,
(int) Configuration::get('PS_OS_PAYMENT'),
0,
$this->trans('Free order', [], 'Admin.Orderscustomers.Feature'),
null,
[],
null,
false,
$cart->secure_key
);
/*
* Redirect back to this page to display the order confirmation.
* Note the id_module parameter with value -1, it's only kept for
* backward compatibility, but not used anymore.
*/
Tools::redirect($this->context->link->getPageLink(
'order-confirmation',
null,
null,
[
'id_cart' => (int) $cart->id,
'id_module' => '-1',
'id_order' => (int) $order->currentOrder,
'key' => $cart->secure_key,
]
));
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Order confirmation', [], 'Shop.Theme.Checkout'),
'url' => $this->context->link->getPageLink('order-confirmation'),
];
return $breadcrumb;
}
/**
* @return Order
*/
public function getOrder(): Order
{
return $this->order;
}
/**
* @return Customer
*/
public function getCustomer(): Customer
{
return $this->customer;
}
}

View File

@@ -0,0 +1,434 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Product\PriceFormatter;
use PrestaShop\PrestaShop\Adapter\Shipment\DeliveryOptionsProvider;
use PrestaShop\PrestaShop\Core\Checkout\TermsAndConditions;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
use PrestaShop\PrestaShop\Core\Foundation\Templating\RenderableProxy;
use PrestaShopBundle\Translation\TranslatorComponent;
class OrderControllerCore extends FrontController
{
/** @var bool */
public $ssl = true;
/** @var string */
public $php_self = 'order';
/** @var string */
public $page_name = 'checkout';
public $checkoutWarning = [];
/**
* @var CheckoutProcess
*/
protected $checkoutProcess;
/**
* @var CartChecksum
*/
protected $cartChecksum;
/**
* Overrides the same parameter in FrontController
*
* @var bool automaticallyAllocateInvoiceAddress
*/
protected $automaticallyAllocateInvoiceAddress = false;
/**
* Overrides the same parameter in FrontController
*
* @var bool
*/
protected $automaticallyAllocateDeliveryAddress = false;
/**
* Initialize order controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
$this->cartChecksum = new CartChecksum(new AddressChecksum());
}
public function postProcess(): void
{
parent::postProcess();
if (Tools::isSubmit('submitReorder')
&& $this->context->customer->isLogged()
&& $id_order = (int) Tools::getValue('id_order')
) {
$oldCart = new Cart(Order::getCartIdStatic($id_order, $this->context->customer->id));
$duplication = $oldCart->duplicate();
if (!$duplication || !Validate::isLoadedObject($duplication['cart'])) {
$this->errors[] = $this->trans('Sorry. We cannot renew your order.', [], 'Shop.Notifications.Error');
} elseif (!$duplication['success']) {
$this->errors[] = $this->trans(
'Some items are no longer available, and we are unable to renew your order.',
[],
'Shop.Notifications.Error'
);
} else {
$this->context->cookie->id_cart = $duplication['cart']->id;
$context = $this->context;
$context->cart = $duplication['cart'];
CartRule::autoAddToCart($context);
$this->context->cookie->write();
Tools::redirect($this->context->link->getPageLink('order'));
}
}
$this->bootstrap();
}
/**
* @return CheckoutProcess
*/
public function getCheckoutProcess(): CheckoutProcess
{
return $this->checkoutProcess;
}
/**
* @return CheckoutSession
*/
public function getCheckoutSession(): CheckoutSession
{
/** @var FeatureFlagStateCheckerInterface $featureFlagManager */
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class);
if ($featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_IMPROVED_SHIPMENT)) {
$deliveryOptionsFinder = new DeliveryOptionsProvider(
$this->context,
$this->getTranslator(),
$this->objectPresenter,
new PriceFormatter(),
$this->cart_presenter,
);
} else {
$deliveryOptionsFinder = new DeliveryOptionsFinder(
$this->context,
$this->getTranslator(),
$this->objectPresenter,
new PriceFormatter()
);
}
$session = new CheckoutSession(
$this->context,
$deliveryOptionsFinder
);
return $session;
}
protected function bootstrap(): void
{
$translator = $this->getTranslator();
$session = $this->getCheckoutSession();
$this->checkoutProcess = $this->buildCheckoutProcess($session, $translator);
Hook::exec('actionCheckoutRender', ['checkoutProcess' => &$this->checkoutProcess]);
}
/**
* Persists cart-related data in checkout session.
*
* @param CheckoutProcess $process
*/
protected function saveDataToPersist(CheckoutProcess $process)
{
$data = $process->getDataToPersist();
$cart = $this->context->cart;
$data['checksum'] = $this->cartChecksum->generateChecksum($cart);
Db::getInstance()->execute(
'UPDATE ' . _DB_PREFIX_ . 'cart SET checkout_session_data = "' . pSQL(json_encode($data)) . '"
WHERE id_cart = ' . (int) $cart->id
);
}
/**
* Restores from checkout session some previously persisted cart-related data.
*
* @param CheckoutProcess $process
*/
protected function restorePersistedData(CheckoutProcess $process)
{
$cart = $this->context->cart;
$customer = $this->context->customer;
$rawData = Db::getInstance()->getValue(
'SELECT checkout_session_data FROM ' . _DB_PREFIX_ . 'cart WHERE id_cart = ' . (int) $cart->id
);
$data = json_decode($rawData ?? '', true);
if (!is_array($data)) {
$data = [];
}
$addressValidator = new AddressValidator();
$invalidAddressIds = $addressValidator->validateCartAddresses($cart);
// Build the currently selected address' warning message (if relevant)
if (!$customer->isGuest() && !empty($invalidAddressIds)) {
$this->checkoutWarning['address'] = [
'id_address' => (int) reset($invalidAddressIds),
'exception' => $this->trans(
'Your address is incomplete, please update it.',
[],
'Shop.Notifications.Error'
),
];
}
// Prevent check for guests
if ($customer->id) {
// Prepare all other addresses' warning messages (if relevant).
// These messages are displayed when changing the selected address.
$allInvalidAddressIds = $addressValidator->validateCustomerAddresses($customer, $this->context->language);
$this->checkoutWarning['invalid_addresses'] = $allInvalidAddressIds;
}
if (isset($data['checksum']) && $data['checksum'] === $this->cartChecksum->generateChecksum($cart)) {
$process->restorePersistedData($data);
}
}
public function displayAjaxselectDeliveryOption(): void
{
$cart = $this->cart_presenter->present(
$this->context->cart,
true
);
ob_end_clean();
header('Content-Type: application/json');
$this->ajaxRender(json_encode([
'preview' => $this->render('checkout/_partials/cart-summary', [
'cart' => $cart,
'static_token' => Tools::getToken(false),
]),
]));
}
public function displayAjaxCheckCartStillOrderable(): void
{
$responseData = [
'errors' => false,
'cartUrl' => '',
];
if ($this->context->cart->isAllProductsInStock() !== true
|| $this->context->cart->checkAllProductsAreStillAvailableInThisState() !== true
|| $this->context->cart->checkAllProductsHaveMinimalQuantities() !== true
|| $this->context->cart->checkCountriesAreEnabled() !== true) {
$responseData['errors'] = true;
$responseData['cartUrl'] = $this->context->link->getPageLink('cart', null, null, ['action' => 'show']);
}
header('Content-Type: application/json');
$this->ajaxRender(json_encode($responseData));
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
$this->restorePersistedData($this->checkoutProcess);
$this->checkoutProcess->handleRequest(
Tools::getAllValues()
);
$presentedCart = $this->cart_presenter->present($this->context->cart, true);
$shouldRedirectToCart = false;
// Check the cart meets minimal order amount treshold
// Check that the cart is not empty
if (count($presentedCart['products']) <= 0 || $presentedCart['minimalPurchaseRequired']) {
$shouldRedirectToCart = true;
}
// Check that products are still orderable, at any point in checkout
if ($this->context->cart->isAllProductsInStock() !== true
|| $this->context->cart->checkAllProductsAreStillAvailableInThisState() !== true
|| $this->context->cart->checkAllProductsHaveMinimalQuantities() !== true) {
$shouldRedirectToCart = true;
}
// Additionally, check that the addresses are valid
if ($this->context->cart->checkCountriesAreEnabled() !== true) {
$shouldRedirectToCart = true;
}
// If there was a problem, we redirect the user to cart, CartController deals with display of detailed errors
// We don't redirect in case of ajax requests, so we can get our response
if ($shouldRedirectToCart === true && !$this->ajax) {
$cartLink = $this->context->link->getPageLink('cart', null, null, ['action' => 'show']);
$this->redirectWithNotifications($cartLink);
}
$this->checkoutProcess
->setNextStepReachable()
->markCurrentStep()
->invalidateAllStepsAfterCurrent();
$this->saveDataToPersist($this->checkoutProcess);
if (!$this->checkoutProcess->hasErrors()) {
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && !$this->ajax) {
$this->redirectWithNotifications(
$this->checkoutProcess->getCheckoutSession()->getCheckoutURL()
);
}
}
$this->context->smarty->assign([
'checkout_process' => new RenderableProxy($this->checkoutProcess),
'display_transaction_updated_info' => Tools::getIsset('updatedTransaction'),
'tos_cms' => $this->getDefaultTermsAndConditions(),
]);
parent::initContent();
$this->setTemplate('checkout/checkout');
}
public function displayAjaxAddressForm(): void
{
$addressForm = $this->makeAddressForm();
if (Tools::getIsset('id_address') && ($id_address = (int) Tools::getValue('id_address'))) {
$addressForm->loadAddressById($id_address);
}
if (Tools::getIsset('id_country')) {
$addressForm->fillWith(['id_country' => Tools::getValue('id_country')]);
}
$stepTemplateParameters = [];
foreach ($this->checkoutProcess->getSteps() as $step) {
if ($step instanceof CheckoutAddressesStep) {
$stepTemplateParameters = $step->getTemplateParameters();
}
}
$templateParams = array_merge(
$addressForm->getTemplateVariables(),
$stepTemplateParameters,
['type' => 'delivery']
);
ob_end_clean();
header('Content-Type: application/json');
$this->ajaxRender(json_encode([
'address_form' => $this->render(
'checkout/_partials/address-form',
$templateParams
),
]));
}
/**
* Return default TOS link for checkout footer
*
* @return string|bool
*/
protected function getDefaultTermsAndConditions(): string|bool
{
$cms = new CMS((int) Configuration::get('PS_CONDITIONS_CMS_ID'), $this->context->language->id);
if (!Validate::isLoadedObject($cms)) {
return false;
}
$link = $this->context->link->getCMSLink($cms, $cms->link_rewrite);
$termsAndConditions = new TermsAndConditions();
$termsAndConditions
->setText(
'[' . $cms->meta_title . ']',
$link
)
->setIdentifier('terms-and-conditions-footer');
return $termsAndConditions->format();
}
/**
* @param CheckoutSession $session
* @param TranslatorComponent $translator
*
* @return CheckoutProcess
*/
protected function buildCheckoutProcess(CheckoutSession $session, $translator)
{
$checkoutProcess = new CheckoutProcess(
$this->context,
$session
);
$checkoutProcess
->addStep(new CheckoutPersonalInformationStep(
$this->context,
$translator,
$this->makeLoginForm(),
$this->makeCustomerForm()
))
->addStep(new CheckoutAddressesStep(
$this->context,
$translator,
$this->makeAddressForm()
));
if (!$this->context->cart->isVirtualCart()) {
$checkoutDeliveryStep = new CheckoutDeliveryStep(
$this->context,
$translator
);
$checkoutDeliveryStep
->setRecyclablePackAllowed((bool) Configuration::get('PS_RECYCLABLE_PACK'))
->setGiftAllowed((bool) Configuration::get('PS_GIFT_WRAPPING'))
->setIncludeTaxes(
!Product::getTaxCalculationMethod((int) $this->context->cart->id_customer)
&& (int) Configuration::get('PS_TAX')
)
->setDisplayTaxesLabel(Configuration::get('PS_TAX'))
->setGiftCost(
$this->context->cart->getGiftWrappingPrice(
$checkoutDeliveryStep->getIncludeTaxes()
)
);
$checkoutProcess->addStep($checkoutDeliveryStep);
}
$checkoutProcess
->addStep(new CheckoutPaymentStep(
$this->context,
$translator,
new PaymentOptionsFinder(),
new ConditionsToApproveFinder(
$this->context,
$translator
),
));
return $checkoutProcess;
}
}

View File

@@ -0,0 +1,233 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderPresenter;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
class OrderDetailControllerCore extends FrontController
{
/** @var string */
public $php_self = 'order-detail';
/** @var bool */
public $auth = true;
/** @var string */
public $authRedirection = 'history';
/** @var bool */
public $ssl = true;
protected $order_to_display;
protected $reference;
/**
* Start forms process.
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
if (Tools::isSubmit('submitMessage')) {
$idOrder = (int) Tools::getValue('id_order');
$msgText = Tools::getValue('msgText');
if (!$idOrder || !Validate::isUnsignedId($idOrder)) {
$this->errors[] = $this->trans('The order is no longer valid.', [], 'Shop.Notifications.Error');
} elseif (empty(trim($msgText))) {
$this->errors[] = $this->trans('The message cannot be blank.', [], 'Shop.Notifications.Error');
}
if (!count($this->errors)) {
$order = new Order($idOrder);
if (Validate::isLoadedObject($order) && $order->id_customer == $this->context->customer->id) {
// check if a thread already exist
$id_customer_thread = CustomerThread::getIdCustomerThreadByEmailAndIdOrder($this->context->customer->email, $order->id);
$id_product = (int) Tools::getValue('id_product');
$cm = new CustomerMessage();
if (!$id_customer_thread) {
$ct = new CustomerThread();
$ct->id_contact = 0;
$ct->id_customer = (int) $order->id_customer;
$ct->id_shop = (int) $this->context->shop->id;
if ($id_product && $order->orderContainProduct($id_product)) {
$ct->id_product = $id_product;
}
$ct->id_order = (int) $order->id;
$ct->id_lang = (int) $this->context->language->id;
$ct->email = $this->context->customer->email;
$ct->status = 'open';
$ct->token = Tools::passwdGen(12);
$ct->add();
} else {
$ct = new CustomerThread((int) $id_customer_thread);
$ct->status = 'open';
$ct->update();
}
$cm->id_customer_thread = $ct->id;
$cm->message = $msgText;
$cm->id_product = $id_product;
$client_ip_address = Tools::getRemoteAddr();
$cm->ip_address = (string) ip2long($client_ip_address);
$cm->add();
if (!Configuration::get('PS_MAIL_EMAIL_MESSAGE')) {
$to = (string) Configuration::get('PS_SHOP_EMAIL');
} else {
$to = new Contact((int) Configuration::get('PS_MAIL_EMAIL_MESSAGE'));
$to = (string) $to->email;
}
$toName = (string) Configuration::get('PS_SHOP_NAME');
$customer = $this->context->customer;
$product = new Product($id_product);
$product_name = '';
if (Validate::isLoadedObject($product) && isset($product->name[(int) $this->context->language->id])) {
$product_name = $product->name[(int) $this->context->language->id];
}
if (Validate::isLoadedObject($customer)) {
Mail::Send(
$this->context->language->id,
'order_customer_comment',
$this->trans(
'Message from a customer',
[],
'Emails.Subject'
),
[
'{lastname}' => $customer->lastname,
'{firstname}' => $customer->firstname,
'{email}' => $customer->email,
'{id_order}' => (int) $order->id,
'{order_name}' => $order->getUniqReference(),
'{message}' => Tools::nl2br(Tools::htmlentitiesUTF8($msgText)),
'{product_name}' => $product_name,
],
$to,
$toName,
(string) Configuration::get('PS_SHOP_EMAIL'),
$customer->firstname . ' ' . $customer->lastname,
null,
null,
_PS_MAIL_DIR_,
false,
null,
null,
$customer->email
);
}
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $idOrder,
'messagesent' => 1,
]
));
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
$id_order = (int) Tools::getValue('id_order');
$id_order = $id_order && Validate::isUnsignedId($id_order) ? $id_order : false;
if (!$id_order) {
$reference = Tools::getValue('reference');
$reference = $reference && Validate::isReference($reference) ? $reference : false;
$order = $reference ? Order::getByReference($reference)->getFirst() : false;
$id_order = $order ? $order->id : false;
}
if (!$id_order) {
$this->redirect_after = '404';
$this->redirect();
} else {
if (Tools::getIsset('errorQuantity')) {
$this->errors[] = $this->trans('You do not have enough products to request an additional merchandise return.', [], 'Shop.Notifications.Error');
} elseif (Tools::getIsset('errorMsg')) {
$this->errors[] = $this->trans('Please provide an explanation for your RMA.', [], 'Shop.Notifications.Error');
} elseif (Tools::getIsset('errorDetail1')) {
$this->errors[] = $this->trans('Please check at least one product you would like to return.', [], 'Shop.Notifications.Error');
} elseif (Tools::getIsset('errorDetail2')) {
$this->errors[] = $this->trans('For each product you wish to add, please specify the desired quantity.', [], 'Shop.Notifications.Error');
} elseif (Tools::getIsset('errorNotReturnable')) {
$this->errors[] = $this->trans('This order cannot be returned', [], 'Shop.Notifications.Error');
} elseif (Tools::getIsset('messagesent')) {
$this->success[] = $this->trans('Message successfully sent', [], 'Shop.Notifications.Success');
}
$order = new Order($id_order);
if (Validate::isLoadedObject($order) && $order->id_customer == $this->context->customer->id) {
if ($order->id_shop != $this->context->shop->id && $this->context->customer->id_shop_group == $this->context->shop->id_shop_group) {
$shopGroup = new ShopGroup($this->context->customer->id_shop_group);
if (!$shopGroup->share_order) {
$this->redirect_after = '404';
$this->redirect();
}
}
$this->order_to_display = (new OrderPresenter())->present($order);
$this->reference = $order->reference;
/** @var FeatureFlagStateCheckerInterface $featureFlagManager */
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class);
$this->context->smarty->assign([
'order' => $this->order_to_display,
'orderIsVirtual' => $order->isVirtual(),
'HOOK_DISPLAYORDERDETAIL' => Hook::exec('displayOrderDetail', ['order' => $order]),
'is_multishipment_enabled' => $featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_IMPROVED_SHIPMENT),
]);
} else {
$this->redirect_after = '404';
$this->redirect();
}
unset($order);
}
$this->setTemplate('customer/order-detail');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Order history', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('history'),
];
if (!empty($this->reference)) {
$breadcrumb['links'][] = [
'title' => $this->reference,
'url' => '#',
];
}
return $breadcrumb;
}
}

View File

@@ -0,0 +1,167 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderReturnPresenter;
class OrderFollowControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'order-follow';
/** @var string */
public $authRedirection = 'order-follow';
/** @var bool */
public $ssl = true;
/**
* Start forms process.
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
if (Tools::isSubmit('submitReturnMerchandise')) {
$order_qte_input = Tools::getValue('order_qte_input');
if (!$id_order = (int) Tools::getValue('id_order')) {
Tools::redirect($this->context->link->getPageLink('history'));
}
if (!($ids_order_detail = Tools::getValue('ids_order_detail'))) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorDetail1' => 1,
]
));
}
if (!$order_qte_input) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorDetail2' => 1,
]
));
}
$order = new Order((int) $id_order);
if (!$order->isReturnable()) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorNotReturnable' => 1,
]
));
}
if ($order->id_customer != $this->context->customer->id) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorNotReturnable' => 1,
]
));
}
$orderReturn = new OrderReturn();
$orderReturn->id_customer = (int) $this->context->customer->id;
$orderReturn->id_order = $id_order;
$orderReturn->question = htmlspecialchars(Tools::getValue('returnText'));
if (empty($orderReturn->question)) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorMsg' => 1,
'ids_order_detail' => $ids_order_detail,
'order_qte_input' => $order_qte_input,
]
));
}
if (!$orderReturn->checkEnoughProduct($ids_order_detail, $order_qte_input)) {
Tools::redirect($this->context->link->getPageLink(
'order-detail',
null,
null,
[
'id_order' => $id_order,
'errorQuantity' => 1,
]
));
}
$orderReturn->state = 1;
$orderReturn->add();
$orderReturn->addReturnDetail($ids_order_detail, $order_qte_input);
Hook::exec('actionOrderReturn', ['orderReturn' => $orderReturn]);
Tools::redirect($this->context->link->getPageLink('order-follow'));
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if ((bool) Configuration::get('PS_ORDER_RETURN') === false) {
$this->redirect_after = '404';
$this->redirect();
}
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
$this->context->smarty->assign('ordersReturn', $this->getTemplateVarOrdersReturns());
parent::initContent();
$this->setTemplate('customer/order-follow');
}
public function getTemplateVarOrdersReturns(): array
{
$orders_returns = [];
$orders_return = OrderReturn::getOrdersReturn($this->context->customer->id);
$orderReturnPresenter = new OrderReturnPresenter(
Configuration::get('PS_RETURN_PREFIX', $this->context->language->id),
$this->context->link
);
foreach ($orders_return as $id_order_return => $order_return) {
$orders_returns[$id_order_return] = $orderReturnPresenter->present($order_return);
}
return $orders_returns;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Merchandise returns', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('order-follow'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever;
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderReturnLazyArray;
use PrestaShop\PrestaShop\Adapter\Presenter\Order\OrderReturnPresenter;
class OrderReturnControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'order-return';
/** @var string */
public $authRedirection = 'order-follow';
/** @var bool */
public $ssl = true;
/**
* Initialize order return controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
$id_order_return = (int) Tools::getValue('id_order_return');
if (!Validate::isUnsignedId($id_order_return)) {
$this->redirect_after = '404';
$this->redirect();
} else {
$order_return = new OrderReturn((int) $id_order_return);
if (Validate::isLoadedObject($order_return) && $order_return->id_customer == $this->context->cookie->id_customer) {
$order = new Order((int) $order_return->id_order);
if (Validate::isLoadedObject($order)) {
if ($order_return->state == 1) {
$this->warning[] = $this->trans('You must wait for confirmation before returning any merchandise.', [], 'Shop.Notifications.Warning');
}
// StarterTheme: Use presenters!
$this->context->smarty->assign([
'return' => $this->getTemplateVarOrderReturn($order_return),
'products' => $this->getTemplateVarProducts((int) $order_return->id, $order),
]);
} else {
$this->redirect_after = '404';
$this->redirect();
}
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
parent::initContent();
$this->setTemplate('customer/order-return');
}
public function getTemplateVarOrderReturn(OrderReturn $orderReturn)
{
$orderReturns = OrderReturn::getOrdersReturn($orderReturn->id_customer, $orderReturn->id_order, false, null, $orderReturn->id);
if (empty($orderReturns)) {
return [];
}
$orderReturnPresenter = new OrderReturnPresenter(
Configuration::get('PS_RETURN_PREFIX', $this->context->language->id),
$this->context->link
);
return $orderReturnPresenter->present(array_shift($orderReturns));
}
public function getTemplateVarProducts(int $order_return_id, Order $order)
{
$products = [];
$return_products = OrderReturn::getOrdersReturnProducts((int) $order_return_id, $order);
foreach ($return_products as $id_return_product => $return_product) {
if (!isset($return_product['deleted'])) {
$products[$id_return_product] = $return_product;
$products[$id_return_product]['customizations'] = ($return_product['customizedDatas']) ? $this->getTemplateVarCustomization($return_product) : [];
}
}
return $products;
}
public function getTemplateVarCustomization(array $product)
{
$product_customizations = [];
$imageRetriever = new ImageRetriever($this->context->link);
foreach ($product['customizedDatas'] as $byAddress) {
foreach ($byAddress as $customization) {
$presentedCustomization = [
'quantity' => $customization['quantity'],
'fields' => [],
'id_customization' => null,
];
foreach ($customization['datas'] as $byType) {
$field = [];
foreach ($byType as $data) {
switch ($data['type']) {
case Product::CUSTOMIZE_FILE:
$field['type'] = 'image';
$field['image'] = $imageRetriever->getCustomizationImage(
$data['value']
);
break;
case Product::CUSTOMIZE_TEXTFIELD:
$field['type'] = 'text';
$field['text'] = $data['value'];
break;
default:
$field['type'] = null;
}
$field['label'] = $data['name'];
$field['id_module'] = $data['id_module'];
$presentedCustomization['id_customization'] = $data['id_customization'];
}
$presentedCustomization['fields'][] = $field;
}
$product_customizations[] = $presentedCustomization;
}
}
return $product_customizations;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
if (($id_order_return = (int) Tools::getValue('id_order_return')) && Validate::isUnsignedId($id_order_return)) {
$breadcrumb['links'][] = [
'title' => $this->trans('Merchandise returns', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('order-follow'),
];
$prefix = Configuration::get('PS_RETURN_PREFIX', $this->context->language->id);
$orderReturn = new OrderReturn($id_order_return);
$orderReturn->id_order_return = $id_order_return;
$orderReturnLazyArray = new OrderReturnLazyArray($prefix, $this->context->link, (array) $orderReturn);
$orderReturnNumber = $orderReturnLazyArray->getReturnNumber();
$breadcrumb['links'][] = [
'title' => $orderReturnNumber,
'url' => '#',
];
}
return $breadcrumb;
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class OrderSlipControllerCore extends FrontController
{
/** @var bool */
public $auth = true;
/** @var string */
public $php_self = 'order-slip';
/** @var string */
public $authRedirection = 'order-slip';
/** @var bool */
public $ssl = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::isCatalogMode()) {
Tools::redirect('index.php');
}
$this->context->smarty->assign([
'credit_slips' => $this->getTemplateVarCreditSlips(),
]);
parent::initContent();
$this->setTemplate('customer/order-slip');
}
public function getTemplateVarCreditSlips(): array
{
$credit_slips = [];
$orders_slip = OrderSlip::getOrdersSlip((int) $this->context->cookie->id_customer);
foreach ($orders_slip as $order_slip) {
$order = new Order($order_slip['id_order']);
$credit_slips[$order_slip['id_order_slip']] = $order_slip;
$credit_slips[$order_slip['id_order_slip']]['credit_slip_number'] = $this->trans('#%id%', ['%id%' => $order_slip['id_order_slip']], 'Shop.Theme.Customeraccount');
$credit_slips[$order_slip['id_order_slip']]['order_number'] = $this->trans('#%id%', ['%id%' => $order_slip['id_order']], 'Shop.Theme.Customeraccount');
$credit_slips[$order_slip['id_order_slip']]['order_reference'] = $order->reference;
$credit_slips[$order_slip['id_order_slip']]['credit_slip_date'] = Tools::displayDate($order_slip['date_add'], false);
$credit_slips[$order_slip['id_order_slip']]['url'] = $this->context->link->getPageLink('pdf-order-slip', null, null, 'id_order_slip=' . (int) $order_slip['id_order_slip']);
$credit_slips[$order_slip['id_order_slip']]['order_url_details'] = $this->context->link->getPageLink('order-detail', null, null, 'id_order=' . (int) $order_slip['id_order']);
}
return $credit_slips;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = $this->addMyAccountToBreadcrumb();
$breadcrumb['links'][] = [
'title' => $this->trans('Credit slips', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('order-slip'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class PageNotFoundControllerCore extends FrontController
{
/** @var string */
public $php_self = 'pagenotfound';
/** @var string */
public $page_name = 'pagenotfound';
/** @var bool */
public $ssl = true;
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found');
$this->context->cookie->disallowWriting();
parent::initContent();
$this->setTemplate('errors/404');
}
protected function canonicalRedirection(string $canonical_url = ''): void
{
// 404 - no need to redirect to the canonical url
}
protected function sslRedirection(): void
{
// 404 - no need to redirect
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
$page['title'] = $this->trans('The page you are looking for was not found.', [], 'Shop.Theme.Global');
return $page;
}
public function displayAjax(): void
{
header('Content-Type: application/json');
echo json_encode($this->trans('The page you are looking for was not found.', [], 'Shop.Theme.Global'));
}
}

View File

@@ -0,0 +1,314 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Core\Security\PasswordPolicyConfiguration;
use PrestaShop\PrestaShop\Core\Util\InternationalizedDomainNameConverter;
class PasswordControllerCore extends FrontController
{
/** @var string */
public $php_self = 'password';
/** @var bool */
public $auth = false;
/** @var bool */
public $ssl = true;
/**
* @var InternationalizedDomainNameConverter
*/
private $IDNConverter;
public function __construct()
{
parent::__construct();
$this->IDNConverter = new InternationalizedDomainNameConverter();
}
/**
* Start forms process.
*
* @see FrontController::postProcess()
*/
public function postProcess(): void
{
$this->setTemplate('customer/password-email');
if (Tools::isSubmit('email')) {
$this->sendRenewPasswordLink();
} elseif (Tools::getValue('token') && ($id_customer = (int) Tools::getValue('id_customer'))) {
$this->changePassword();
} elseif (Tools::getValue('token') || Tools::getValue('id_customer')) {
$this->errors[] = $this->trans('We cannot regenerate your password with the data you\'ve submitted', [], 'Shop.Notifications.Error');
}
}
protected function sendRenewPasswordLink(): void
{
if (!($email = $this->IDNConverter->emailToUtf8(trim(Tools::getValue('email')))) || !Validate::isEmail($email)) {
$this->errors[] = $this->trans('Invalid email address.', [], 'Shop.Notifications.Error');
} else {
$customer = new Customer();
$customer->getByEmail($email);
if (null === $customer->email) {
$customer->email = Tools::getValue('email');
}
if (!Validate::isLoadedObject($customer)) {
$this->success[] = $this->trans(
'If this email address has been registered in our store, you will receive a link to reset your password at %email%.',
['%email%' => $customer->email],
'Shop.Notifications.Success'
);
$this->setTemplate('customer/password-infos');
} elseif (!$customer->active) {
$this->errors[] = $this->trans('You cannot regenerate the password for this account.', [], 'Shop.Notifications.Error');
} elseif ((strtotime($customer->last_passwd_gen . '+' . ($minTime = (int) Configuration::get('PS_PASSWD_TIME_FRONT')) . ' minutes') - time()) > 0) {
$this->errors[] = $this->trans('You can regenerate your password only every %d minute(s)', [(int) $minTime], 'Shop.Notifications.Error');
} else {
if (!$customer->hasRecentResetPasswordToken()) {
$customer->stampResetPasswordToken();
$customer->update();
}
$mailParams = [
'{email}' => $customer->email,
'{lastname}' => $customer->lastname,
'{firstname}' => $customer->firstname,
'{url}' => $this->context->link->getPageLink('password', null, null, 'token=' . $customer->secure_key . '&id_customer=' . (int) $customer->id . '&reset_token=' . $customer->reset_password_token),
];
if (
Mail::Send(
$this->context->language->id,
'password_query',
$this->trans(
'Password query confirmation',
[],
'Emails.Subject'
),
$mailParams,
$customer->email,
$customer->firstname . ' ' . $customer->lastname
)
) {
$this->success[] = $this->trans('If this email address has been registered in our store, you will receive a link to reset your password at %email%.', ['%email%' => $customer->email], 'Shop.Notifications.Success');
$this->setTemplate('customer/password-infos');
} else {
$this->errors[] = $this->trans('An error occurred while sending the email.', [], 'Shop.Notifications.Error');
}
}
}
}
protected function changePassword(): void
{
$token = Tools::getValue('token');
$id_customer = (int) Tools::getValue('id_customer');
$reset_token = Tools::getValue('reset_token');
$email = Db::getInstance()->getValue(
'SELECT `email` FROM ' . _DB_PREFIX_ . 'customer c WHERE c.`secure_key` = \'' . pSQL($token) . '\' AND c.id_customer = ' . $id_customer
);
if ($email) {
$customer = new Customer();
$customer->getByEmail($email);
if (!Validate::isLoadedObject($customer)) {
$this->errors[] = $this->trans('Customer account not found', [], 'Shop.Notifications.Error');
} elseif (!$customer->active) {
$this->errors[] = $this->trans('You cannot regenerate the password for this account.', [], 'Shop.Notifications.Error');
} elseif ($customer->getValidResetPasswordToken() !== $reset_token) {
$this->errors[] = $this->trans('The password change request expired. You should ask for a new one.', [], 'Shop.Notifications.Error');
}
if ($this->errors) {
return;
}
if ($isSubmit = Tools::isSubmit('passwd')) {
// If password is submitted validate pass and confirmation
if (!$passwd = Tools::getValue('passwd')) {
$this->errors[] = $this->trans('The password is missing: please enter your new password.', [], 'Shop.Notifications.Error');
}
if (!$confirmation = Tools::getValue('confirmation')) {
$this->errors[] = $this->trans('The confirmation is empty: please fill in the password confirmation as well', [], 'Shop.Notifications.Error');
}
if ($passwd && $confirmation) {
if ($passwd !== $confirmation) {
$this->errors[] = $this->trans('The confirmation password doesn\'t match.', [], 'Shop.Notifications.Error');
}
if (!Validate::isAcceptablePasswordLength($passwd)) {
$this->errors[] = $this->trans('The password is not in a valid format.', [], 'Shop.Notifications.Error');
}
}
if (Validate::isAcceptablePasswordLength($passwd) === false) {
$this->errors[] = $this->translator->trans(
'Password must be between %d and %d characters long',
[
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_LENGTH),
Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MAXIMUM_LENGTH),
],
'Shop.Notifications.Error'
);
}
if (Validate::isAcceptablePasswordScore($passwd) === false) {
$wordingsForScore = [
$this->translator->trans('Very weak', [], 'Shop.Theme.Global'),
$this->translator->trans('Weak', [], 'Shop.Theme.Global'),
$this->translator->trans('Average', [], 'Shop.Theme.Global'),
$this->translator->trans('Strong', [], 'Shop.Theme.Global'),
$this->translator->trans('Very strong', [], 'Shop.Theme.Global'),
];
$this->errors[] = $this->translator->trans(
'The minimum score must be: %s',
[
$wordingsForScore[(int) Configuration::get(PasswordPolicyConfiguration::CONFIGURATION_MINIMUM_SCORE)],
],
'Shop.Notifications.Error'
);
}
}
if (!$isSubmit || $this->errors) {
// If password is NOT submitted OR there are errors, shows the form (and errors)
$this->context->smarty->assign([
'customer_email' => $customer->email,
'customer_token' => $token,
'id_customer' => $id_customer,
'reset_token' => $reset_token,
]);
$this->setTemplate('customer/password-new');
} else {
// Both password fields posted. Check if all is right and store new password properly.
if (!$reset_token || (strtotime($customer->last_passwd_gen . '+' . (int) Configuration::get('PS_PASSWD_TIME_FRONT') . ' minutes') - time()) > 0) {
Tools::redirect($this->context->link->getPageLink(
'authentication',
null,
null,
['error_regen_pwd' => 1]
));
} else {
$customer->passwd = $this->get('hashing')->hash($password = Tools::getValue('passwd'), _COOKIE_KEY_);
$customer->last_passwd_gen = date('Y-m-d H:i:s', time());
if ($customer->update()) {
Hook::exec('actionPasswordRenew', ['customer' => $customer, 'password' => $password]);
$customer->removeResetPasswordToken();
$customer->update();
$mail_params = [
'{email}' => $customer->email,
'{lastname}' => $customer->lastname,
'{firstname}' => $customer->firstname,
];
if (
Mail::Send(
$this->context->language->id,
'password',
$this->trans(
'Your new password',
[],
'Emails.Subject'
),
$mail_params,
$customer->email,
$customer->firstname . ' ' . $customer->lastname
)
) {
$this->context->smarty->assign([
'customer_email' => $customer->email,
]);
$this->success[] = $this->trans('Your password has been successfully reset and a confirmation has been sent to your email address: %s', [$customer->email], 'Shop.Notifications.Success');
$this->context->updateCustomer($customer);
$this->redirectWithNotifications($this->context->link->getPageLink('my-account'));
} else {
$this->errors[] = $this->trans('An error occurred while sending the email.', [], 'Shop.Notifications.Error');
}
} else {
$this->errors[] = $this->trans('An error occurred with your account, which prevents us from updating the new password. Please report this issue using the contact form.', [], 'Shop.Notifications.Error');
}
}
}
} else {
$this->errors[] = $this->trans('We cannot regenerate your password with the data you\'ve submitted', [], 'Shop.Notifications.Error');
}
}
/**
* @return void
*/
public function display(): void
{
$this->context->smarty->assign(
[
'layout' => $this->getLayout(),
'stylesheets' => $this->getStylesheets(),
'javascript' => $this->getJavascript(),
'js_custom_vars' => Media::getJsDef(),
'errors' => $this->getErrors(),
'successes' => $this->getSuccesses(),
]
);
$this->smartyOutputContent($this->template);
}
/**
* @return array
*/
protected function getErrors(): array
{
$notifications = $this->prepareNotifications();
$errors = [];
if (array_key_exists('error', $notifications)) {
$errors = $notifications['error'];
}
return $errors;
}
/**
* @return array
*/
protected function getSuccesses(): array
{
$notifications = $this->prepareNotifications();
$successes = [];
if (array_key_exists('success', $notifications)) {
$successes = $notifications['success'];
}
return $successes;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Reset your password', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('password'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('password');
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class PdfInvoiceControllerCore extends FrontController
{
/** @var string */
public $php_self = 'pdf-invoice';
/** @var bool */
protected $display_header = false;
/** @var bool */
protected $display_footer = false;
/** @var bool */
public $content_only = true;
/** @var string */
protected $template = '';
public $filename;
/** @var Order */
public $order;
public function postProcess(): void
{
// If the customer is not logged in AND no secure key was passed
if (!$this->context->customer->isLogged() && !Tools::getValue('secure_key')) {
Tools::redirect($this->context->link->getPageLink(
'authentication',
null,
null,
['back' => 'pdf-invoice']
));
}
// If built-in invoicing is disabled
if (!(int) Configuration::get('PS_INVOICE')) {
die($this->trans('Invoices are disabled in this shop.', [], 'Shop.Notifications.Error'));
}
$id_order = (int) Tools::getValue('id_order');
if (Validate::isUnsignedId($id_order)) {
$order = new Order((int) $id_order);
}
// If the order doesn't exist
if (!isset($order) || !Validate::isLoadedObject($order)) {
die($this->trans('The invoice was not found.', [], 'Shop.Notifications.Error'));
}
// Check if the user is not trying to download an invoice of an order of different customer
// Either the ID of the customer in context must match the customer in order OR a secure_key matching the one on the order must be provided
if (Tools::isSubmit('secure_key') && $order->secure_key != Tools::getValue('secure_key')) {
die($this->trans('The invoice was not found.', [], 'Shop.Notifications.Error'));
}
if (!Tools::isSubmit('secure_key') && (!isset($this->context->customer->id) || $order->id_customer != $this->context->customer->id)) {
die($this->trans('The invoice was not found.', [], 'Shop.Notifications.Error'));
}
if (!OrderState::invoiceAvailable($order->getCurrentState()) && !$order->invoice_number) {
die($this->trans('No invoice is available.', [], 'Shop.Notifications.Error'));
}
$this->order = $order;
}
/**
* @return void
*
* @throws PrestaShopException
*/
public function display(): void
{
$order_invoice_list = $this->order->getInvoicesCollection();
Hook::exec('actionPDFInvoiceRender', ['order_invoice_list' => $order_invoice_list]);
$pdf = new PDF($order_invoice_list, PDF::TEMPLATE_INVOICE, $this->context->smarty);
$pdf->render();
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShopBundle\Security\Admin\LegacyAdminTokenValidator;
class PdfOrderReturnControllerCore extends FrontController
{
/** @var string */
public $php_self = 'pdf-order-return';
/** @var bool */
protected $display_header = false;
/** @var bool */
protected $display_footer = false;
/**
* @var OrderReturn|null
*/
public $orderReturn;
public function postProcess(): void
{
$adminToken = Tools::getValue('adtoken');
if (!empty($adminToken)) {
$adminTokenValidator = $this->getContainer()->get(LegacyAdminTokenValidator::class);
$from_admin = $adminTokenValidator->isTokenValid((int) Tools::getValue('id_employee'), $adminToken);
} else {
$from_admin = false;
}
if (!$from_admin && !$this->context->customer->isLogged()) {
Tools::redirect($this->context->link->getPageLink(
'authentication',
null,
null,
['back' => 'order-follow']
));
}
if (Tools::getValue('id_order_return') && Validate::isUnsignedId(Tools::getValue('id_order_return'))) {
$this->orderReturn = new OrderReturn(Tools::getValue('id_order_return'));
}
if (!isset($this->orderReturn) || !Validate::isLoadedObject($this->orderReturn)) {
die($this->trans('Order return not found.', [], 'Shop.Notifications.Error'));
} elseif (!$from_admin && $this->orderReturn->id_customer != $this->context->customer->id) {
die($this->trans('Order return not found.', [], 'Shop.Notifications.Error'));
} elseif ($this->orderReturn->state < 2) {
die($this->trans('Order return not confirmed.', [], 'Shop.Notifications.Error'));
}
}
/**
* @return void
*
* @throws PrestaShopException
*/
public function display(): void
{
$pdf = new PDF($this->orderReturn, PDF::TEMPLATE_ORDER_RETURN, $this->context->smarty);
$pdf->render();
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class PdfOrderSlipControllerCore extends FrontController
{
/** @var string */
public $php_self = 'pdf-order-slip';
/** @var bool */
protected $display_header = false;
/** @var bool */
protected $display_footer = false;
protected $order_slip;
public function postProcess(): void
{
if (!$this->context->customer->isLogged()) {
Tools::redirect($this->context->link->getPageLink(
'authentication',
null,
null,
['back' => 'order-follow']
));
}
if (isset($_GET['id_order_slip']) && Validate::isUnsignedId($_GET['id_order_slip'])) {
$this->order_slip = new OrderSlip($_GET['id_order_slip']);
}
if (!isset($this->order_slip) || !Validate::isLoadedObject($this->order_slip)) {
die($this->trans('Order return not found.', [], 'Shop.Notifications.Error'));
} elseif ($this->order_slip->id_customer != $this->context->customer->id) {
die($this->trans('Order return not found.', [], 'Shop.Notifications.Error'));
}
}
/**
* @return void
*
* @throws PrestaShopException
*/
public function display(): void
{
$pdf = new PDF($this->order_slip, PDF::TEMPLATE_ORDER_SLIP, $this->context->smarty);
$pdf->render();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class RegistrationControllerCore extends FrontController
{
/** @var bool */
public $ssl = true;
/** @var string */
public $php_self = 'registration';
/** @var bool */
public $auth = false;
/**
* Check if the controller is available for the current user/visitor.
*
* @see Controller::checkAccess()
*
* @return bool
*/
public function checkAccess(): bool
{
// If the customer is already logged and he got here by 'accident', we will redirect him away
if ($this->context->customer->isLogged() && !$this->ajax) {
$this->redirect_after = $this->authRedirection ? urlencode($this->authRedirection) : 'my-account';
$this->redirect();
}
return parent::checkAccess();
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
$register_form = $this
->makeCustomerForm()
->setGuestAllowed(false)
->fillWith(Tools::getAllValues());
// If registration form was submitted
if (Tools::isSubmit('submitCreate')) {
$hookResult = array_reduce(
Hook::exec('actionSubmitAccountBefore', [], null, true),
function ($carry, $item) {
return $carry && $item;
},
true
);
// If no problem occured in the hook, let's get the user redirected
if ($hookResult && $register_form->submit() && !$this->ajax) {
// First option - redirect the customer to desired URL specified in 'back' parameter
// Before that, we need to check if 'back' is legit URL that is on OUR domain, with the right protocol
$back = rawurldecode(Tools::getValue('back'));
if (Tools::urlBelongsToShop($back)) {
$this->redirectWithNotifications($back);
}
// Second option - we will redirect him to authRedirection if set
if ($this->authRedirection) {
$this->redirectWithNotifications($this->authRedirection);
}
// Third option - we will redirect him to home URL
$this->redirectWithNotifications(__PS_BASE_URI__);
}
}
$this->context->smarty->assign([
'register_form' => $register_form->getProxy(),
'hook_create_account_top' => Hook::exec('displayCustomerAccountFormTop'),
]);
$this->setTemplate('customer/registration');
parent::initContent();
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Create an account', [], 'Shop.Theme.Customeraccount'),
'url' => $this->context->link->getPageLink('registration'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('registration');
}
}

View File

@@ -0,0 +1,230 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class SitemapControllerCore extends FrontController
{
/** @var string */
public $php_self = 'sitemap';
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
$sitemapUrls = [
'our_offers' => [
'name' => $this->trans('Our Offers', [], 'Shop.Theme.Global'),
'links' => $this->getOffersLinks(),
],
'categories' => [
'name' => $this->trans('Categories', [], 'Shop.Theme.Catalog'),
'links' => $this->getCategoriesLinks(),
],
'your_account' => [
'name' => $this->trans('Your account', [], 'Shop.Theme.Customeraccount'),
'links' => $this->getUserAccountLinks(),
],
'pages' => [
'name' => $this->trans('Pages', [], 'Shop.Theme.Catalog'),
'links' => $this->getPagesLinks(),
],
];
/*
* Allows modules to add own urls (even whole new groups) to frontend sitemap.
* For example landing pages, blog posts and others.
*/
Hook::exec(
'actionModifyFrontendSitemap',
['urls' => &$sitemapUrls]
);
/*
* Backward compatibility with older themes.
* This should be removed as soon as possible, because $pages variable is overwriting
* our global template variable assigned in FrontController.
*/
$this->context->smarty->assign(
[
'our_offers' => !empty($sitemapUrls['our_offers']['name']) ? $sitemapUrls['our_offers']['name'] : '',
'categories' => !empty($sitemapUrls['categories']['name']) ? $sitemapUrls['categories']['name'] : '',
'your_account' => !empty($sitemapUrls['your_account']['name']) ? $sitemapUrls['your_account']['name'] : '',
'pages' => !empty($sitemapUrls['pages']['name']) ? $sitemapUrls['pages']['name'] : '',
'links' => [
'offers' => !empty($sitemapUrls['our_offers']['links']) ? $sitemapUrls['our_offers']['links'] : [],
'pages' => !empty($sitemapUrls['pages']['links']) ? $sitemapUrls['pages']['links'] : [],
'user_account' => !empty($sitemapUrls['your_account']['links']) ? $sitemapUrls['your_account']['links'] : [],
'categories' => !empty($sitemapUrls['categories']['links']) ? $sitemapUrls['categories']['links'] : [],
],
]
);
$this->context->smarty->assign('sitemapUrls', $sitemapUrls);
parent::initContent();
$this->setTemplate('cms/sitemap');
}
public function getCategoriesLinks(): array
{
return [Category::getRootCategory()->recurseLiteCategTree(0, 0, null, null, 'sitemap')];
}
/**
* @return array
*/
protected function getPagesLinks(): array
{
$cms = CMSCategory::getRecurseCategory($this->context->language->id, 1, 1, 1);
$links = $this->getCmsTree($cms);
// We hide stores page, if there is no page configured
if (Store::atLeastOneStoreExists()) {
$links[] = [
'id' => 'stores-page',
'label' => $this->trans('Our stores', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('stores'),
];
}
$links[] = [
'id' => 'contact-page',
'label' => $this->trans('Contact us', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('contact'),
];
$links[] = [
'id' => 'sitemap-page',
'label' => $this->trans('Sitemap', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('sitemap'),
];
return $links;
}
/**
* @return array
*/
protected function getCmsTree($cms): array
{
$links = [];
foreach ($cms['cms'] as $p) {
$links[] = [
'id' => 'cms-page-' . $p['id_cms'],
'label' => $p['meta_title'],
'url' => $p['link'],
];
}
if (isset($cms['children'])) {
foreach ($cms['children'] as $c) {
$links[] = [
'id' => 'cms-category-' . $c['id_cms_category'],
'label' => $c['name'],
'url' => $c['link'],
'children' => $this->getCmsTree($c),
];
}
}
return $links;
}
/**
* @return array
*/
protected function getUserAccountLinks(): array
{
$links = [];
$links[] = [
'id' => 'login-page',
'label' => $this->trans('Log in', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('authentication'),
];
$links[] = [
'id' => 'register-page',
'label' => $this->trans('Create new account', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('registration'),
];
return $links;
}
/**
* @return array
*/
protected function getOffersLinks(): array
{
$links = [
[
'id' => 'new-product-page',
'label' => $this->trans('New products', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('new-products'),
],
];
if (!Configuration::isCatalogMode()) {
if (Configuration::get('PS_DISPLAY_BEST_SELLERS')) {
$links[] = [
'id' => 'best-sales-page',
'label' => $this->trans('Best sellers', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('best-sales'),
];
}
$links[] = [
'id' => 'prices-drop-page',
'label' => $this->trans('Price drop', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('prices-drop'),
];
}
if (Configuration::get('PS_DISPLAY_MANUFACTURERS')) {
$manufacturers = Manufacturer::getLiteManufacturersList($this->context->language->id, 'sitemap');
$links[] = [
'id' => 'manufacturer-page',
'label' => $this->trans('Brands', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('manufacturer'),
'children' => $manufacturers,
];
}
if (Configuration::get('PS_DISPLAY_SUPPLIERS')) {
$suppliers = Supplier::getLiteSuppliersList($this->context->language->id, 'sitemap');
$links[] = [
'id' => 'supplier-page',
'label' => $this->trans('Suppliers', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('supplier'),
'children' => $suppliers,
];
}
return $links;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Sitemap', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('sitemap'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('sitemap');
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
class StatisticsControllerCore extends FrontController
{
/** @var bool */
public $display_header = false;
/** @var bool */
public $display_footer = false;
protected $param_token;
public function postProcess(): void
{
$this->param_token = Tools::getValue('token');
if (!$this->param_token) {
die;
}
if ($_POST['type'] == 'navinfo') {
$this->processNavigationStats();
} elseif ($_POST['type'] == 'pagetime') {
$this->processPageTime();
} else {
exit;
}
}
/**
* Log statistics on navigation (resolution, plugins, etc.).
*/
protected function processNavigationStats(): void
{
$id_guest = (int) Tools::getValue('id_guest');
if (sha1($id_guest . _COOKIE_KEY_) != $this->param_token) {
die;
}
$guest = new Guest((int) substr($_POST['id_guest'], 0, 10));
$guest->javascript = true;
$guest->screen_resolution_x = (int) substr($_POST['screen_resolution_x'], 0, 5);
$guest->screen_resolution_y = (int) substr($_POST['screen_resolution_y'], 0, 5);
$guest->screen_color = (int) substr($_POST['screen_color'], 0, 3);
$guest->sun_java = (int) substr($_POST['sun_java'], 0, 1);
$guest->adobe_flash = (int) substr($_POST['adobe_flash'], 0, 1);
$guest->adobe_director = (int) substr($_POST['adobe_director'], 0, 1);
$guest->apple_quicktime = (int) substr($_POST['apple_quicktime'], 0, 1);
$guest->real_player = (int) substr($_POST['real_player'], 0, 1);
$guest->windows_media = (int) substr($_POST['windows_media'], 0, 1);
$guest->update();
}
/**
* Log statistics on time spend on pages.
*/
protected function processPageTime(): void
{
$id_connection = (int) Tools::getValue('id_connections');
$time = (int) Tools::getValue('time');
$time_start = Tools::getValue('time_start');
$id_page = (int) Tools::getValue('id_page');
if (sha1($id_connection . $id_page . $time_start . _COOKIE_KEY_) != $this->param_token) {
die;
}
if ($time <= 0) {
die;
}
Connection::setPageTime($id_connection, $id_page, substr($time_start, 0, 19), $time);
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Store\StorePresenter;
class StoresControllerCore extends FrontController
{
/** @var string */
public $php_self = 'stores';
/** @var StorePresenter */
protected $storePresenter;
/**
* Initialize stores controller.
*
* @see FrontController::init()
*/
public function init(): void
{
// Initialize presenter, we will use it for all cases
$this->storePresenter = new StorePresenter(
$this->context->link,
$this->context->getTranslator()
);
parent::init();
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
$distance_unit = Configuration::get('PS_DISTANCE_UNIT');
if (!in_array($distance_unit, ['km', 'mi'])) {
$distance_unit = 'km';
}
// Load stores and present them
$stores = $this->getTemplateVarStores();
// If no stores are configured, we hide this page
if (!empty($stores)) {
$this->context->smarty->assign([
'mediumSize' => Image::getSize(ImageType::getFormattedName('medium')),
'searchUrl' => $this->context->link->getPageLink('stores'),
'distance_unit' => $distance_unit,
'stores' => $stores,
]);
parent::initContent();
$this->setTemplate('cms/stores');
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
public function getTemplateVarStores(): array
{
$stores = Store::getStores($this->context->language->id);
foreach ($stores as &$store) {
$store = $this->storePresenter->present(
$store,
$this->context->language
);
}
return $stores;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Our stores', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('stores'),
];
return $breadcrumb;
}
/**
* {@inheritdoc}
*/
public function getCanonicalURL(): string
{
return $this->context->link->getPageLink('stores');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
declare(strict_types=1);
class UploadControllerCore extends GetFileController
{
private $filename;
/**
* Initialize the controller.
*
* @see FrontController::init()
*/
public function init(): void
{
FrontController::init();
if (Tools::getValue('file') !== null) {
$this->filename = pSQL(Tools::getValue('file'));
}
if (!file_exists($this->getPath()) || (!$this->isCustomization() && !$this->isEmployee())) {
$this->redirect_after = '404';
$this->redirect();
}
}
private function isEmployee(): bool
{
return !empty((new Cookie('psAdmin'))->id_employee);
}
private function isCustomization(): bool
{
if ($this->filename === null || !($this->context->cart instanceof Cart)) {
return false;
}
$isCustomization = Db::getInstance()->getValue('SELECT 1
FROM ' . _DB_PREFIX_ . 'cart c
INNER JOIN ' . _DB_PREFIX_ . 'customization cu ON c.id_cart = cu.id_cart
INNER JOIN ' . _DB_PREFIX_ . 'customized_data cd ON cd.id_customization = cu.id_customization
LEFT JOIN ' . _DB_PREFIX_ . 'orders o ON c.id_cart = o.id_cart
WHERE (c.id_customer = ' . (int) $this->context->cart->id_customer . '
AND c.id_guest = ' . (int) $this->context->cart->id_guest . '
OR o.reference = "' . pSQL(Tools::getValue('reference')) . '")
AND cd.type = ' . Product::CUSTOMIZE_FILE . '
AND (cd.value = "' . $this->filename . '" OR CONCAT(cd.value, "_small") = "' . $this->filename . '")');
return (bool) $isCustomization;
}
public function postProcess(): void
{
$this->sendFile($this->getPath(), $this->filename, false);
}
private function getPath(): string
{
return _PS_UPLOAD_DIR_ . basename($this->filename);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
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;

View File

@@ -0,0 +1,97 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\BestSales\BestSalesProductSearchProvider;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class BestSalesControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'best-sales';
/**
* Returns canonical URL for best-sales page
*
* @return string
*/
public function getCanonicalURL(): string
{
return $this->buildPaginatedUrl($this->context->link->getPageLink('best-sales'));
}
/**
* Initializes controller.
*
* @see FrontController::init()
*
* @throws PrestaShopException
*/
public function init(): void
{
if (Configuration::get('PS_DISPLAY_BEST_SELLERS')) {
parent::init();
} else {
Tools::redirect('pagenotfound');
}
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->doProductSearch('catalog/listing/best-sales', ['entity' => 'best-sales']);
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('best-sales')
->setSortOrder(new SortOrder('product', 'sales', 'desc'));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return BestSalesProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): BestSalesProductSearchProvider
{
return new BestSalesProductSearchProvider(
$this->getTranslator()
);
}
public function getListingLabel(): string
{
return $this->getTranslator()->trans('Best sellers', [], 'Shop.Theme.Catalog');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Best sellers', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('best-sales'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,382 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Category\CategoryProductSearchProvider;
use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever;
use PrestaShop\PrestaShop\Adapter\Presenter\Category\CategoryLazyArray;
use PrestaShop\PrestaShop\Adapter\Presenter\Category\CategoryPresenter;
use PrestaShop\PrestaShop\Core\Domain\Category\ValueObject\RedirectType;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class CategoryControllerCore extends ProductListingFrontController
{
/** @var string Internal controller name */
public $php_self = 'category';
/** @var bool If set to false, customer cannot view the current category. */
public $customer_access = true;
/** @var bool */
protected $notFound = false;
/**
* @var Category
*/
protected $category;
/** @var CategoryPresenter */
protected $categoryPresenter;
public function canonicalRedirection(string $canonicalURL = ''): void
{
if (Validate::isLoadedObject($this->category)) {
parent::canonicalRedirection($this->context->link->getCategoryLink($this->category));
}
}
/**
* Returns canonical URL for current category
*
* @return string
*/
public function getCanonicalURL(): string
{
if (!Validate::isLoadedObject($this->category)) {
return '';
}
return $this->buildPaginatedUrl($this->context->link->getCategoryLink($this->category));
}
/**
* Initializes category controller.
*
* @see FrontController::init()
*
* @throws PrestaShopException
*/
public function init(): void
{
// Get proper IDs
$id_category = (int) Tools::getValue('id_category');
// Try to load category object
$this->category = new Category($id_category, $this->context->language->id);
/*
* Call of parent::init() must be here, after initializing the category. Inside FrontController class
* there is a call to canonical redirection logic, that needs proper category data for it to work.
* If you move this to the top of init() method, the redirection will stop working.
*/
parent::init();
// Otherwise immediately show 404
if (!Validate::isLoadedObject($this->category)) {
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found');
$this->errors[] = $this->trans('This category is no longer available.', [], 'Shop.Notifications.Error');
$this->setTemplate('errors/404');
$this->notFound = true;
return;
}
// If this category is not active or not related to current shop in multistore context,
// we treat it as not available. We will either redirect away or show error, depending
// on settings of the category.
if (!$this->category->active || !$this->category->existsInShop($this->context->shop->id)) {
// If category should redirect and we don't know where, we take the closest parent
if (!$this->category->id_type_redirected && in_array($this->category->redirect_type, [RedirectType::TYPE_PERMANENT, RedirectType::TYPE_TEMPORARY])) {
$this->category->id_type_redirected = $this->getCategoryToRedirectTo();
}
// Now, we do as configured in "Redirection when not displayed" field on the category
switch ($this->category->redirect_type) {
case RedirectType::TYPE_PERMANENT:
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $this->context->link->getCategoryLink($this->category->id_type_redirected));
exit;
case RedirectType::TYPE_TEMPORARY:
header('HTTP/1.1 302 Moved Temporarily');
header('Cache-Control: no-cache');
header('Location: ' . $this->context->link->getCategoryLink($this->category->id_type_redirected));
exit;
case RedirectType::TYPE_GONE:
header('HTTP/1.1 410 Gone');
header('Status: 410 Gone');
$this->errors[] = $this->trans('This category is no longer available.', [], 'Shop.Notifications.Error');
$this->setTemplate('errors/410');
$this->notFound = true;
break;
case RedirectType::TYPE_NOT_FOUND:
default:
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found');
$this->errors[] = $this->trans('This category is no longer available.', [], 'Shop.Notifications.Error');
$this->setTemplate('errors/404');
$this->notFound = true;
break;
}
return;
}
// And one last check, we need to validate if current customer is a member
// of at least one group allowed to view this category.
if (!$this->category->checkAccess($this->context->customer->id)) {
header('HTTP/1.1 403 Forbidden');
header('Status: 403 Forbidden');
$this->errors[] = $this->trans('You do not have access to this category.', [], 'Shop.Notifications.Error');
$this->setTemplate('errors/forbidden');
return;
}
// Initialize presenter, we will use it for all cases
$this->categoryPresenter = new CategoryPresenter($this->context->link);
$this->context->smarty->assign([
'category' => $this->getTemplateVarCategory(),
'subcategories' => $this->getTemplateVarSubCategories(),
]);
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
if (
Validate::isLoadedObject($this->category)
&& $this->category->active
&& $this->category->checkAccess($this->context->customer->id)
&& $this->category->existsInShop($this->context->shop->id)
) {
$this->doProductSearch(
'catalog/listing/category',
[
'entity' => 'category',
'id' => $this->category->id,
]
);
}
}
/**
* overrides layout if category is not visible.
*
* @return bool|string
*/
public function getLayout(): bool|string
{
if (!$this->category->checkAccess($this->context->customer->id) || $this->notFound) {
return $this->context->shop->theme->getLayoutRelativePathForPage('error');
}
return parent::getLayout();
}
protected function getAjaxProductSearchVariables(): array
{
// Basic data with rendered products, facets, active filters etc.
$data = parent::getAjaxProductSearchVariables();
// Extra data for category pages, so we can dynamically update also these parts
$rendered_category_header = $this->render('catalog/_partials/category-header', ['listing' => $data]);
$data['rendered_products_header'] = $rendered_category_header;
$rendered_category_footer = $this->render('catalog/_partials/category-footer', ['listing' => $data]);
$data['rendered_products_footer'] = $rendered_category_footer;
return $data;
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*
* @throws PrestaShop\PrestaShop\Core\Product\Search\Exception\InvalidSortOrderDirectionException
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('category')
->setIdCategory($this->category->id)
->setSortOrder(new SortOrder('product', Tools::getProductsOrder('by'), Tools::getProductsOrder('way')));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return CategoryProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): CategoryProductSearchProvider
{
return new CategoryProductSearchProvider(
$this->getTranslator(),
$this->category
);
}
protected function getTemplateVarCategory(): CategoryLazyArray
{
$categoryVar = $this->categoryPresenter->present(
$this->category,
$this->context->language
);
$filteredCategory = Hook::exec(
'filterCategoryContent',
['object' => $categoryVar],
$id_module = null,
$array_return = false,
$check_exceptions = true,
$use_push = false,
$id_shop = null,
$chain = true
);
if (!empty($filteredCategory['object'])) {
$categoryVar = $filteredCategory['object'];
}
return $categoryVar;
}
protected function getTemplateVarSubCategories(): array
{
$subcategories = $this->category->getSubCategories($this->context->language->id);
foreach ($subcategories as &$subcategory) {
$subcategory = $this->categoryPresenter->present(
$subcategory,
$this->context->language
);
}
return $subcategories;
}
/**
* @deprecated since 9.0.0 and will be removed in 10.0.0
*/
protected function getImage(Category $object, int $id_image)
{
$retriever = new ImageRetriever(
$this->context->link
);
return $retriever->getImage($object, $id_image);
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
foreach ($this->category->getAllParents() as $category) {
/** @var Category $category */
if ($category->id_parent != 0 && !$category->is_root_category && $category->active) {
$breadcrumb['links'][] = [
'title' => $category->name,
'url' => $this->context->link->getCategoryLink($category),
];
}
}
if ($this->category->id_parent != 0 && !$this->category->is_root_category && $this->category->active) {
$breadcrumb['links'][] = [
'title' => $this->category->name,
'url' => $this->context->link->getCategoryLink($this->category),
];
}
return $breadcrumb;
}
/**
* @return Category
*/
public function getCategory(): Category
{
return $this->category;
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
if ($this->notFound) {
$page['page_name'] = 'pagenotfound';
$page['body_classes']['pagenotfound'] = true;
$page['title'] = $this->trans('The page you are looking for was not found.', [], 'Shop.Theme.Global');
} else {
$page['body_classes']['category-id-' . $this->category->id] = true;
$page['body_classes']['category-' . $this->category->name] = true;
$page['body_classes']['category-id-parent-' . $this->category->id_parent] = true;
$page['body_classes']['category-depth-level-' . $this->category->level_depth] = true;
}
return $page;
}
public function getListingLabel(): string
{
if (!Validate::isLoadedObject($this->category)) {
$this->category = new Category(
(int) Tools::getValue('id_category'),
$this->context->language->id
);
}
return $this->trans(
'Category: %category_name%',
['%category_name%' => $this->category->name],
'Shop.Theme.Catalog'
);
}
/**
* Returns a category that we will redirect into, in case we need 301/302 redirect.
* We will try to get the closest active parent of the current category.
*
* @return int category ID
*/
private function getCategoryToRedirectTo(): int
{
$categoryToRedirectTo = null;
foreach ($this->category->getParentsCategories() as $category) {
/*
* Or new favourite category is a one:
* that is not the current one
* that is active
* and that is deeper than the last one
*/
if ($category['id_category'] != $this->category->id
&& $category['active'] == 1
&& $category['level_depth'] > $categoryToRedirectTo['level_depth']
) {
$categoryToRedirectTo = $category;
}
}
return $categoryToRedirectTo['id_category'];
}
}

View File

@@ -0,0 +1,263 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Manufacturer\ManufacturerProductSearchProvider;
use PrestaShop\PrestaShop\Adapter\Presenter\Manufacturer\ManufacturerPresenter;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class ManufacturerControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'manufacturer';
/** @var Manufacturer|null */
protected $manufacturer;
protected $label;
/** @var ManufacturerPresenter */
protected $manufacturerPresenter;
public function canonicalRedirection(string $canonicalURL = ''): void
{
if (Validate::isLoadedObject($this->manufacturer)) {
parent::canonicalRedirection($this->context->link->getManufacturerLink($this->manufacturer));
} elseif ($canonicalURL) {
parent::canonicalRedirection($canonicalURL);
}
}
/**
* Returns canonical URL for current manufacturer or a manufacturer list
*
* @return string
*/
public function getCanonicalURL(): string
{
if (Validate::isLoadedObject($this->manufacturer)) {
return $this->buildPaginatedUrl($this->context->link->getManufacturerLink($this->manufacturer));
}
return $this->context->link->getPageLink('manufacturer');
}
/**
* Initialize manufaturer controller.
*
* @see FrontController::init()
*/
public function init(): void
{
if ($id_manufacturer = Tools::getValue('id_manufacturer')) {
$this->manufacturer = new Manufacturer((int) $id_manufacturer, $this->context->language->id);
if (!Validate::isLoadedObject($this->manufacturer) || !$this->manufacturer->active || !$this->manufacturer->isAssociatedToShop()) {
$this->redirect_after = '404';
$this->redirect();
} else {
$this->canonicalRedirection();
}
}
// Initialize presenter, we will use it for all cases
$this->manufacturerPresenter = new ManufacturerPresenter($this->context->link);
parent::init();
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::get('PS_DISPLAY_MANUFACTURERS')) {
parent::initContent();
if (Validate::isLoadedObject($this->manufacturer) && $this->manufacturer->active && $this->manufacturer->isAssociatedToShop()) {
$this->assignManufacturer();
$this->label = $this->trans(
'List of products by brand %brand_name%',
[
'%brand_name%' => $this->manufacturer->name,
],
'Shop.Theme.Catalog'
);
$this->doProductSearch(
'catalog/listing/manufacturer',
['entity' => 'manufacturer', 'id' => $this->manufacturer->id]
);
} else {
$this->assignAll();
$this->label = $this->trans(
'List of all brands',
[],
'Shop.Theme.Catalog'
);
$this->setTemplate('catalog/manufacturers', ['entity' => 'manufacturers']);
}
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*
* @throws PrestaShop\PrestaShop\Core\Product\Search\Exception\InvalidSortOrderDirectionException
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('manufacturer')
->setIdManufacturer($this->manufacturer->id)
->setSortOrder(new SortOrder('product', Tools::getProductsOrder('by'), Tools::getProductsOrder('way')));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return ManufacturerProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): ManufacturerProductSearchProvider
{
return new ManufacturerProductSearchProvider(
$this->getTranslator(),
$this->manufacturer
);
}
/**
* Assign template vars if displaying one manufacturer.
*/
protected function assignManufacturer(): void
{
$manufacturerVar = $this->manufacturerPresenter->present(
$this->manufacturer,
$this->context->language
);
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredManufacturer = Hook::exec(
'filterManufacturerContent',
['object' => $manufacturerVar],
$id_module = null,
$array_return = false,
$check_exceptions = true,
$use_push = false,
$id_shop = null,
$chain = true
);
if (!empty($filteredManufacturer['object'])) {
$manufacturerVar = $filteredManufacturer['object'];
}
$this->context->smarty->assign([
'manufacturer' => $manufacturerVar,
]);
}
/**
* Assign template vars if displaying the manufacturer list.
*/
protected function assignAll(): void
{
$manufacturersVar = $this->getTemplateVarManufacturers();
if (!empty($manufacturersVar)) {
foreach ($manufacturersVar as $k => $manufacturer) {
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredManufacturer = Hook::exec(
'filterManufacturerContent',
['object' => $manufacturer],
$id_module = null,
$array_return = false,
$check_exceptions = true,
$use_push = false,
$id_shop = null,
$chain = true
);
if (!empty($filteredManufacturer['object'])) {
$manufacturersVar[$k] = $filteredManufacturer['object'];
}
}
}
$this->context->smarty->assign([
'brands' => $manufacturersVar,
]);
}
public function getTemplateVarManufacturers(): array
{
$manufacturers = Manufacturer::getManufacturers(true, $this->context->language->id);
foreach ($manufacturers as &$manufacturer) {
$manufacturer = $this->manufacturerPresenter->present(
$manufacturer,
$this->context->language
);
}
return $manufacturers;
}
public function getListingLabel(): string
{
return $this->label;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->getTranslator()->trans('Brands', [], 'Shop.Theme.Global'),
'url' => $this->context->link->getPageLink('manufacturer'),
];
if (!empty($this->manufacturer)) {
$breadcrumb['links'][] = [
'title' => $this->manufacturer->name,
'url' => $this->context->link->getManufacturerLink($this->manufacturer),
];
}
return $breadcrumb;
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
if (!empty($this->manufacturer)) {
$page['body_classes']['manufacturer-id-' . $this->manufacturer->id] = true;
$page['body_classes']['manufacturer-' . $this->manufacturer->name] = true;
}
return $page;
}
/**
* @return Manufacturer|null
*/
public function getManufacturer(): ?Manufacturer
{
return $this->manufacturer;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\NewProducts\NewProductsProductSearchProvider;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class NewProductsControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'new-products';
/**
* Returns canonical URL for new-products page
*
* @return string
*/
public function getCanonicalURL(): string
{
return $this->buildPaginatedUrl($this->context->link->getPageLink('new-products'));
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->doProductSearch('catalog/listing/new-products', ['entity' => 'new-products']);
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('new-products')
->setSortOrder(new SortOrder('product', 'date_add', 'desc'));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return NewProductsProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): NewProductsProductSearchProvider
{
return new NewProductsProductSearchProvider(
$this->getTranslator()
);
}
public function getListingLabel(): string
{
return $this->trans(
'New products',
[],
'Shop.Theme.Catalog'
);
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('New products', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('new-products'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\PricesDrop\PricesDropProductSearchProvider;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class PricesDropControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'prices-drop';
/**
* Returns canonical URL for prices-drop page
*
* @return string
*/
public function getCanonicalURL(): string
{
return $this->buildPaginatedUrl($this->context->link->getPageLink('prices-drop'));
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->doProductSearch('catalog/listing/prices-drop', ['entity' => 'prices-drop']);
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('prices-drop')
->setSortOrder(new SortOrder('product', 'name', 'asc'));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return PricesDropProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): PricesDropProductSearchProvider
{
return new PricesDropProductSearchProvider(
$this->getTranslator()
);
}
public function getListingLabel(): string
{
return $this->trans(
'Prices drop',
[],
'Shop.Theme.Catalog'
);
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Prices drop', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('prices-drop'),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,128 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Search\SearchProductSearchProvider;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class SearchControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'search';
public $instant_search;
public $ajax_search;
protected $search_string;
protected $search_tag;
/**
* Initialize the controller.
*
* @see FrontController::init()
*/
public function init(): void
{
parent::init();
$this->search_string = Tools::getValue('s');
if (!$this->search_string) {
$this->search_string = Tools::getValue('search_query');
}
$this->search_tag = Tools::getValue('tag');
$this->context->smarty->assign(
[
'search_string' => $this->search_string,
'search_tag' => $this->search_tag,
'subcategories' => [],
]
);
}
/**
* Returns canonical URL for a search page with this term
*
* @return string
*/
public function getCanonicalURL(): string
{
return $this->buildPaginatedUrl($this->context->link->getPageLink('search', null, null, ['s' => $this->search_string]));
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
// Ensure that no search results page is indexed by search engines.
$page['meta']['robots'] = 'noindex';
return $page;
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
parent::initContent();
$this->doProductSearch('catalog/listing/search', ['entity' => 'search']);
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('search')
->setSortOrder(new SortOrder('product', 'position', 'desc'))
->setSearchString($this->search_string)
->setSearchTag($this->search_tag);
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return SearchProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): SearchProductSearchProvider
{
return new SearchProductSearchProvider(
$this->getTranslator()
);
}
public function getListingLabel(): string
{
return $this->getTranslator()->trans('Search results', [], 'Shop.Theme.Catalog');
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->getTranslator()->trans('Search results', [], 'Shop.Theme.Catalog'),
'url' => $this->getCurrentUrl(),
];
return $breadcrumb;
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
use PrestaShop\PrestaShop\Adapter\Presenter\Supplier\SupplierPresenter;
use PrestaShop\PrestaShop\Adapter\Supplier\SupplierProductSearchProvider;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class SupplierControllerCore extends ProductListingFrontController
{
/** @var string */
public $php_self = 'supplier';
/** @var Supplier|null */
protected $supplier;
protected $label;
/** @var SupplierPresenter */
protected $supplierPresenter;
public function canonicalRedirection(string $canonicalURL = ''): void
{
if (Validate::isLoadedObject($this->supplier)) {
parent::canonicalRedirection($this->context->link->getSupplierLink($this->supplier));
} elseif ($canonicalURL) {
parent::canonicalRedirection($canonicalURL);
}
}
/**
* Returns canonical URL for current supplier or a supplier list
*
* @return string
*/
public function getCanonicalURL(): string
{
if (Validate::isLoadedObject($this->supplier)) {
return $this->buildPaginatedUrl($this->context->link->getSupplierLink($this->supplier));
}
return $this->context->link->getPageLink('supplier');
}
/**
* Initialize supplier controller.
*
* @see FrontController::init()
*/
public function init(): void
{
if ($id_supplier = (int) Tools::getValue('id_supplier')) {
$this->supplier = new Supplier($id_supplier, $this->context->language->id);
if (!Validate::isLoadedObject($this->supplier) || !$this->supplier->active) {
$this->redirect_after = '404';
$this->redirect();
} else {
$this->canonicalRedirection();
}
}
// Initialize presenter, we will use it for all cases
$this->supplierPresenter = new SupplierPresenter($this->context->link);
parent::init();
}
/**
* Assign template vars related to page content.
*
* @see FrontController::initContent()
*/
public function initContent(): void
{
if (Configuration::get('PS_DISPLAY_SUPPLIERS')) {
parent::initContent();
if (Validate::isLoadedObject($this->supplier) && $this->supplier->active && $this->supplier->isAssociatedToShop()) {
$this->assignSupplier();
$this->label = $this->trans(
'List of products by supplier %supplier_name%',
[
'%supplier_name%' => $this->supplier->name,
],
'Shop.Theme.Catalog'
);
$this->doProductSearch(
'catalog/listing/supplier',
['entity' => 'supplier', 'id' => $this->supplier->id]
);
} else {
$this->assignAll();
$this->label = $this->trans(
'List of all suppliers',
[],
'Shop.Theme.Catalog'
);
$this->setTemplate('catalog/suppliers', ['entity' => 'suppliers']);
}
} else {
$this->redirect_after = '404';
$this->redirect();
}
}
/**
* Gets the product search query for the controller. This is a set of information that
* a filtering module or the default provider will use to fetch our products.
*
* @return ProductSearchQuery
*/
protected function getProductSearchQuery(): ProductSearchQuery
{
$query = new ProductSearchQuery();
$query
->setQueryType('supplier')
->setIdSupplier($this->supplier->id)
->setSortOrder(new SortOrder('product', 'position', 'asc'));
return $query;
}
/**
* Default product search provider used if no filtering module stood up for the job
*
* @return SupplierProductSearchProvider
*/
protected function getDefaultProductSearchProvider(): SupplierProductSearchProvider
{
return new SupplierProductSearchProvider(
$this->getTranslator(),
$this->supplier
);
}
/**
* Assign template vars if displaying one supplier.
*/
protected function assignSupplier(): void
{
$supplierVar = $this->supplierPresenter->present(
$this->supplier,
$this->context->language
);
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredSupplier = Hook::exec(
'filterSupplierContent',
['object' => $supplierVar],
null,
false,
true,
false,
null,
true
);
if (!empty($filteredSupplier['object'])) {
$supplierVar = $filteredSupplier['object'];
}
$this->context->smarty->assign([
'supplier' => $supplierVar,
]);
}
/**
* Assign template vars if displaying the supplier list.
*/
protected function assignAll(): void
{
$suppliersVar = $this->getTemplateVarSuppliers();
if (!empty($suppliersVar)) {
foreach ($suppliersVar as $k => $supplier) {
// Chained hook call - if multiple modules are hooked here, they will receive the result of the previous one as a parameter
$filteredSupplier = Hook::exec(
'filterSupplierContent',
['object' => $supplier],
null,
false,
true,
false,
null,
true
);
if (!empty($filteredSupplier['object'])) {
$suppliersVar[$k] = $filteredSupplier['object'];
}
}
}
$this->context->smarty->assign([
'suppliers' => $suppliersVar,
]);
}
public function getTemplateVarSuppliers(): array
{
$suppliers = Supplier::getSuppliers(true, $this->context->language->id, true);
foreach ($suppliers as &$supplier) {
$supplier = $this->supplierPresenter->present(
$supplier,
$this->context->language
);
}
return $suppliers;
}
public function getListingLabel(): string
{
return $this->label;
}
public function getBreadcrumbLinks(): array
{
$breadcrumb = parent::getBreadcrumbLinks();
$breadcrumb['links'][] = [
'title' => $this->trans('Suppliers', [], 'Shop.Theme.Catalog'),
'url' => $this->context->link->getPageLink('supplier'),
];
if (!empty($this->supplier)) {
$breadcrumb['links'][] = [
'title' => $this->supplier->name,
'url' => $this->context->link->getSupplierLink($this->supplier),
];
}
return $breadcrumb;
}
/**
* Initializes a set of commonly used variables related to the current page, available for use
* in the template. @see FrontController::assignGeneralPurposeVariables for more information.
*
* @return array
*/
public function getTemplateVarPage(): array
{
$page = parent::getTemplateVarPage();
if (!empty($this->supplier)) {
$page['body_classes']['supplier-id-' . $this->supplier->id] = true;
$page['body_classes']['supplier-' . $this->supplier->name] = true;
}
return $page;
}
/**
* @return Supplier|null
*/
public function getSupplier(): ?Supplier
{
return $this->supplier;
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
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;

14
controllers/index.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
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;