diff --git a/admin-dev/ajax-tab.php b/admin-dev/ajax-tab.php index 77a09f2b8..f3aa7caea 100755 --- a/admin-dev/ajax-tab.php +++ b/admin-dev/ajax-tab.php @@ -36,6 +36,8 @@ if (!isset($_POST['controller']) && isset($_POST['tab'])) $_POST['controller'] = strtolower($_POST['tab']); if (!isset($_REQUEST['controller']) && isset($_REQUEST['tab'])) $_REQUEST['controller'] = strtolower($_REQUEST['tab']); - +// Retrocompatibility with 1.4 +$_REQUEST['ajaxMode'] = $_POST['ajaxMode'] = $_GET['ajaxMode'] = $_REQUEST['ajax'] = $_POST['ajax'] = $_GET['ajax'] = 1; + Dispatcher::getInstance()->setControllerDirectories(array(_PS_ADMIN_DIR_.'/tabs/', _PS_ADMIN_CONTROLLER_DIR_)); Dispatcher::getInstance()->dispatch(); \ No newline at end of file diff --git a/admin-dev/tabs/AdminCustomerThreads.php b/admin-dev/tabs/AdminCustomerThreads.php index 941259f6f..7e50e20f9 100644 --- a/admin-dev/tabs/AdminCustomerThreads.php +++ b/admin-dev/tabs/AdminCustomerThreads.php @@ -120,7 +120,6 @@ class AdminCustomerThreads extends AdminTab } if (isset($_POST['id_employee_forward'])) { - // Todo: need to avoid doubles $messages = Db::getInstance()->executeS(' SELECT ct.*, cm.*, cl.name subject, CONCAT(e.firstname, \' \', e.lastname) employee_name, CONCAT(c.firstname, \' \', c.lastname) customer_name, c.firstname FROM '._DB_PREFIX_.'customer_thread ct diff --git a/admin-dev/tabs/AdminDiscounts.php b/admin-dev/tabs/AdminDiscounts.php deleted file mode 100644 index 5f49c14f3..000000000 --- a/admin-dev/tabs/AdminDiscounts.php +++ /dev/null @@ -1,532 +0,0 @@ - -* @copyright 2007-2011 PrestaShop SA -* @version Release: $Revision: 7060 $ -* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) -* International Registered Trademark & Property of PrestaShop SA -*/ - -class AdminDiscounts extends AdminTab -{ - public function __construct() - { - $this->context = Context::getContext(); - $this->table = 'discount'; - $this->className = 'Discount'; - $this->lang = true; - $this->edit = true; - $this->delete = true; - $this->_select = 'dtl.`name` AS discount_type, - IF(a.id_discount_type = '.(int)Discount::PERCENT.', CONCAT(a.value, " %"), - IF(a.id_discount_type = '.(int)Discount::AMOUNT.', CONCAT(a.value, " ", c.sign), - "--")) as strvalue'; - $this->_join = 'LEFT JOIN `'._DB_PREFIX_.'currency` c ON (c.`id_currency` = a.`id_currency`) - LEFT JOIN `'._DB_PREFIX_.'discount_type` dt ON (dt.`id_discount_type` = a.`id_discount_type`) - LEFT JOIN `'._DB_PREFIX_.'discount_type_lang` dtl ON (dt.`id_discount_type` = dtl.`id_discount_type` AND dtl.`id_lang` = '.(int)$this->context->language->id.')'; - - $typesArray = array(); - $types = Discount::getDiscountTypes($this->context->language->id); - foreach ($types AS $type) - $typesArray[$type['id_discount_type']] = $type['name']; - - $this->fieldsDisplay = array( - 'id_discount' => array('title' => $this->l('ID'), 'align' => 'center', 'width' => 25), - 'name' => array('title' => $this->l('Code'), 'width' => 85, 'prefix' => '', 'suffix' => '', 'filter_key' => 'a!name'), - 'shop_name' => array('title' => $this->l('Shop'), 'width' => 85, 'filter_key' => 's!name'), - 'description' => array('title' => $this->l('Description'), 'width' => 100, 'filter_key' => 'b!description'), - 'discount_type' => array('title' => $this->l('Type'), 'type' => 'select', 'select' => $typesArray, 'filter_key' => 'dt!id_discount_type'), - 'strvalue' => array('title' => $this->l('Value'), 'width' => 50, 'align' => 'right', 'filter_key' => 'a!value'), - 'quantity' => array('title' => $this->l('Qty'), 'width' => 40, 'align' => 'right'), - 'date_to' => array('title' => $this->l('To'), 'width' => 60, 'type' => 'date', 'align' => 'right'), - 'active' => array('title' => $this->l('Status'), 'align' => 'center', 'active' => 'status', 'type' => 'bool', 'orderby' => false), - ); - - $this->optionsList = array( - 'general' => array( - 'title' => $this->l('Discounts options'), - 'fields' => array( - 'PS_VOUCHERS' => array('title' => $this->l('Enable vouchers:'), 'desc' => $this->l('Allow the use of vouchers in shop'), 'cast' => 'intval', 'type' => 'bool'), - ), - ), - ); - - parent::__construct(); - } - - protected function copyFromPost(&$object, $table) - { - parent::copyFromPost($object, $table); - - $object->cumulable = (!isset($_POST['cumulable']) ? false : true); - $object->cumulable_reduction = (!isset($_POST['cumulable_reduction']) ? false : true); - } - - public function postProcess() - { - $token = Tools::getValue('token') ? Tools::getValue('token') : $this->token; - - if ($discountName = Tools::getValue('name') AND Validate::isDiscountName($discountName) AND Discount::discountExists($discountName, Tools::getValue('id_discount'))) - $this->_errors[] = Tools::displayError('A voucher of this name already exists. Please choose another name.'); - - if (Tools::getValue('submitAdd'.$this->table)) - { - if (Tools::getValue('id_discount_type') == 0) - $this->_errors[] = Tools::displayError('Please set a type for this voucher.'); - if (Tools::getValue('id_discount_type') == Discount::AMOUNT AND Tools::getValue('id_currency') == 0) - $this->_errors[] = Tools::displayError('Please set a currency for this voucher.'); - if ((Tools::getValue('id_discount_type') == Discount::PERCENT || Tools::getValue('id_discount_type') == 2) && !Tools::getValue('value')) - $this->_errors[] = Tools::displayError('Please set a amount for this voucher.'); - if (!Validate::isBool_Id(Tools::getValue('id_target'))) - $this->_errors[] = Tools::displayError('Invalid customer or group ID field'); - else - { - $rules = explode('_', Tools::getValue('id_target')); - /* In form, there is one field for two differents fields in object*/ - $_POST[($rules[0] ? 'id_group' : 'id_customer')] = $rules[1]; - } - /* Checking fields validity */ - $this->validateRules(); - if (!sizeof($this->_errors)) - { - $id = (int)(Tools::getValue($this->identifier)); - /* Object update */ - if (isset($id) AND !empty($id)) - { - if ($this->tabAccess['edit'] === '1') - { - $object = new $this->className($id); - if (Validate::isLoadedObject($object)) - { - /* Specific to objects which must not be deleted */ - if ($this->deleted AND $this->beforeDelete($object)) - { - $object->deleted = 1; - $object->update(); - $objectNew = new $this->className(); - $this->copyFromPost($objectNew, $this->table); - $result = $objectNew->add(); - if (Validate::isLoadedObject($objectNew)) - $this->afterDelete($objectNew, $object->id); - } - else - { - if (($categories = Tools::getValue('categoryBox')) === false OR (!empty($categories) AND !is_array($categories))) - $this->_errors[] = Tools::displayError('Please set a category for this voucher.'); - $this->copyFromPost($object, $this->table); - $result = $object->update(true, false, $categories); - } - if (!$result) - $this->_errors[] = Tools::displayError('An error occurred while updating object.').' '.$this->table.''; - elseif ($this->postImage($object->id)) - { - if ($back = Tools::getValue('back')) - Tools::redirectAdmin(urldecode($back).'&conf=4'); - if (Tools::getValue('stay_here') == 'on' || Tools::getValue('stay_here') == 'true' || Tools::getValue('stay_here') == '1') - Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=4&updatescene&token='.$token); - Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=4&token='.$token); - } - } - else - $this->_errors[] = Tools::displayError('An error occurred while updating object.').' '.$this->table.' '.Tools::displayError('(cannot load object)'); - } - else - $this->_errors[] = Tools::displayError('You do not have permission to edit here.'); - } - - /* Object creation */ - else - { - if ($this->tabAccess['add'] === '1') - { - $object = new $this->className(); - $this->copyFromPost($object, $this->table); - $categories = Tools::getValue('categoryBox', null); - if (!$object->add(true, false, $categories)) - $this->_errors[] = Tools::displayError('An error occurred while creating object.').' '.$this->table.''; - elseif (($_POST[$this->identifier] = $object->id /* voluntary */) AND $this->postImage($object->id) AND $this->_redirect) - { - if ($customer = new Customer($object->id_customer) AND Validate::isLoadedObject($customer)) - { - if (Validate::isEmail($customer->email)) - { - $data = array( - '{firstname}' => $customer->firstname, - '{lastname}' => $customer->lastname, - '{email}' => $customer->email, - '{voucher_num}' => $object->name); - - @Mail::Send((int)Configuration::get('PS_LANG_DEFAULT'), 'voucher_new', Mail::l('New voucher'), $data, $customer->email, $customer->firstname.' '.$customer->lastname); - } - } - elseif ($group = new Group($object->id_group) AND Validate::isLoadedObject($group)) - { - $customer = null; - $customers = $group->getCustomers(); - - if ($customers) - foreach ($customers as $customer) - { - $data = array( - '{firstname}' => $customer['firstname'], - '{lastname}' => $customer['lastname'], - '{email}' => $customer['email'], - '{voucher_num}' => $object->name); - - @Mail::Send((int)Configuration::get('PS_LANG_DEFAULT'), 'voucher_new', Mail::l('New voucher'), $data, $customer['email'], $customer['firstname'].' '.$customer['lastname']); - } - } - - Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=3&token='.$token); - } - } - else - $this->_errors[] = Tools::displayError('You do not have permission to add here.'); - } - } - $this->_errors = array_unique($this->_errors); - } - else - return parent::postProcess(); - } - - public function displayForm($isMainTab = true) - { - parent::displayForm(); - - if (!($obj = $this->loadObject(true))) - return; - - echo ' - -
- '.($obj->id ? '' : '').' -
'.$this->l('Vouchers').' - -
- - * - - '.$this->l('Invalid characters: numbers and').' !<>,;?=+()@#"�{}_$%:  -

'.$this->l('The voucher\'s code, at least 3 characters long, which the customer types in during check-out').'

-
- -
- * -
- - -
'; - foreach ($this->_languages as $language) - echo '
- * - '.$this->l('Invalid characters:').' <>;=#{}  -

'.$this->l('Will appear in cart next to voucher code').'

-
'; - $this->displayFlags($this->_languages, $this->_defaultFormLanguage, 'description', 'description'); - echo '
-
- -
- - - - - - '; - $done = array(); - $index = array(); - $indexedCategories = isset($_POST['categoryBox']) ? $_POST['categoryBox'] : ($obj->id ? Discount::getCategories($obj->id) : array()); - $categories = Category::getCategories($this->context->language->id, false); - foreach ($indexedCategories AS $k => $row) - $index[] = $row['id_category']; - $this->recurseCategoryForInclude((int)(Tools::getValue($this->identifier)), $index, $categories, $categories[0][1], 1, $obj->id); - echo ' -
'.$this->l('ID').''.$this->l('Name').'
-

'.$this->l('Mark all checkbox(es) of categories to which the discount is to be applied').' *

-
-
- -
- * -

'.$this->l('Total quantity available (mainly for vouchers open to everyone)').'

-
- -
- * -

'.$this->l('Number of times a single customer can use this voucher').'

-
- -
- *  - -

'.$this->l('0 if not applicable').'

-
-
-

- getFieldValue($obj, 'cumulable') == 1) ? ' checked="checked"' : '').' id="cumulable_on" value="1" /> - -

-
-
-

- getFieldValue($obj, 'cumulable_reduction') == 1) ? ' checked="checked"' : '').' id="cumulable_reduction_on" value="1" /> - -

-
- -
- - -
'.$this->l('Filter:').' - -

'; - includeDatepicker(array('date_from', 'date_to'), true); - echo ' - -
- * -

'.$this->l('Start date/time from which voucher can be used').'
'.$this->l('Format: YYYY-MM-DD HH:MM:SS').'

-
- -
- * -

'.$this->l('End date/time at which voucher is no longer valid').'
'.$this->l('Format: YYYY-MM-DD HH:MM:SS').'

-
- -
- getFieldValue($obj, 'cart_display') ? 'checked="checked" ' : '').'/> - - getFieldValue($obj, 'cart_display') ? 'checked="checked" ' : '').'/> - -
-
- -
- getFieldValue($obj, 'active') ? 'checked="checked" ' : '').'/> - - getFieldValue($obj, 'active') ? 'checked="checked" ' : '').'/> - -

'.$this->l('Enable or disable voucher').'

-
'; - if (Shop::isFeatureActive()) - { - echo '
'; - $this->displayAssoShop(); - echo '
'; - } - echo '
- -
-
* '.$this->l('Required field').'
-
-
'; - } - /** - * Build a categories tree - * - * @param array $indexedCategories Array with categories where product is indexed (in order to check checkbox) - * @param array $categories Categories to list - * @param array $current Current category - * @param integer $id_category Current category id - */ - public static function recurseCategoryForInclude($id_obj, $indexedCategories, $categories, $current, $id_category = 1, $id_category_default = NULL, $has_suite = array()) - { - global $done; - static $irow; - - if (!isset($done[$current['infos']['id_parent']])) - $done[$current['infos']['id_parent']] = 0; - $done[$current['infos']['id_parent']] += 1; - - $todo = sizeof($categories[$current['infos']['id_parent']]); - $doneC = $done[$current['infos']['id_parent']]; - - $level = $current['infos']['level_depth'] + 1; - - echo ' - - - - - - '.$id_category.' - - '; - for ($i = 2; $i < $level; $i++) - echo ''; - echo '   - - '; - - if ($level > 1) - $has_suite[] = ($todo == $doneC ? 0 : 1); - if (isset($categories[$id_category])) - foreach ($categories[$id_category] AS $key => $row) - if ($key != 'infos') - self::recurseCategoryForInclude($id_obj, $indexedCategories, $categories, $categories[$id_category][$key], $key, $id_category_default, $has_suite); - } -} \ No newline at end of file diff --git a/admin-dev/tabs/AdminPerformance.php b/admin-dev/tabs/AdminPerformance.php index e19fb3950..e9a8f5ad0 100644 --- a/admin-dev/tabs/AdminPerformance.php +++ b/admin-dev/tabs/AdminPerformance.php @@ -229,7 +229,7 @@ class AdminPerformance extends AdminTab if (!extension_loaded('xcache')) $warnings[] = $this->l('To use Xcache, you must install the Xcache extension on your server.').' http://xcache.lighttpd.net'; - if (!is_writable(_PS_CACHEFS_DIRECTORY_)) + if(!is_writable(_PS_CACHEFS_DIRECTORY_)) $warnings[] = $this->l('To use CacheFS the directory').' '.realpath(_PS_CACHEFS_DIRECTORY_).' '.$this->l('must be writable'); if ($warnings) diff --git a/admin-dev/themes/template/cart_rules/actions.tpl b/admin-dev/themes/template/cart_rules/actions.tpl new file mode 100644 index 000000000..9b56d434b --- /dev/null +++ b/admin-dev/themes/template/cart_rules/actions.tpl @@ -0,0 +1,85 @@ + +
+    + getFieldValue($currentObject, 'free_shipping')|intval}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'free_shipping')|intval}checked="checked"{/if} /> + +
+
+ +
+    + getFieldValue($currentObject, 'reduction_percent')|intval}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'reduction_amount')|intval}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'reduction_amount')|intval && !$currentTab->getFieldValue($currentObject, 'reduction_percent')|intval}checked="checked"{/if} /> + +
+
+ +
+ +

{l s='Does not apply to the shipping costs'}

+
+
+
+ +
+ + + +
+
+
+ +
+    + getFieldValue($currentObject, 'reduction_product')|intval == 0}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'reduction_product')|intval > 0}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'reduction_product')|intval == -1}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'reduction_product')|intval == -2}checked="checked"{/if} /> + +
+
+ +
+ + +
+
+
+
+ +
+    + getFieldValue($currentObject, 'gift_product')|intval}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'gift_product')|intval}checked="checked"{/if} /> + +
+
+ +
+ + +
+
\ No newline at end of file diff --git a/admin-dev/themes/template/cart_rules/conditions.tpl b/admin-dev/themes/template/cart_rules/conditions.tpl new file mode 100644 index 000000000..79901fd46 --- /dev/null +++ b/admin-dev/themes/template/cart_rules/conditions.tpl @@ -0,0 +1,188 @@ + +
+ + +

{l s='Optional, the cart rule will be available for everyone if you leave this field blank.'}

+
+ +
+ {l s='from'} + + {l s='to'} + +

{l s='Default period is one year.'}

+
+ +
+ + + + +

{l s='You can choose a minimum amount for the cart either with or without the taxes, with or without shipping.'}

+
+ +
+ +
+ +
+ +
+{if $countries.unselected|@count + $countries.selected|@count > 1} +
+ {l s='Country selection'} +
+ + + + + +
+

{l s='Selected countries'}

+

+ + {l s='Remove'} >> + +
+

{l s='Unselected countries'}

+

+ + << {l s='Add'} + +
+
+{/if} +{if $carriers.unselected|@count + $carriers.selected|@count > 1} +
+ {l s='Carrier selection'} +
+ + + + + +
+

{l s='Selected carriers'}

+

+ + {l s='Remove'} >> + +
+

{l s='Unselected carriers'}

+

+ + << {l s='Add'} + +
+
+{/if} +{if $groups.unselected|@count + $groups.selected|@count > 1} +
+ {l s='Customer group selection'} +
+ + + + + +
+

{l s='Selected groups'}

+

+ + {l s='Remove'} >> + +
+

{l s='Unselected groups'}

+

+ + << {l s='Add'} + +
+
+{/if} +{if $cart_rules.unselected|@count + $cart_rules.selected|@count > 1} +
+ {l s='Other cart rules compatibility'} +
+ + + + + +
+

{l s='Combinable cart rules'}

+

+ + {l s='Remove'} >> + +
+

{l s='Uncombinable cart rules'}

+

+ + << {l s='Add'} + +
+
+{/if} +
+ {l s='Product selection'} +
+ {l s='Add a filter on'} + + + {l s='Add'} {l s='Add'} + +
+ + {foreach from=$product_rules item='product_rule'} + {$product_rule} + {/foreach} +
+
\ No newline at end of file diff --git a/admin-dev/themes/template/cart_rules/form.js b/admin-dev/themes/template/cart_rules/form.js new file mode 100644 index 000000000..2ebc1a86c --- /dev/null +++ b/admin-dev/themes/template/cart_rules/form.js @@ -0,0 +1,300 @@ +/* +* 2007-2011 PrestaShop +* +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License (OSL 3.0) +* that is bundled with this package in the file LICENSE.txt. +* It is also available through the world-wide-web at this URL: +* http://opensource.org/licenses/osl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to license@prestashop.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade PrestaShop to newer +* versions in the future. If you wish to customize PrestaShop for your +* needs please refer to http://www.prestashop.com for more information. +* +* @author PrestaShop SA +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision: 6844 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +function addProductRule() +{ + product_rules_counter += 1; + if ($('#product_rule_type').val() != 0) + $.get( + 'ajax-tab.php', + {controller:'AdminCartRules',token:currentToken,newProductRule:1,product_rule_type:$('#product_rule_type').val(),product_rule_id:product_rules_counter}, + function(content) { + if (content != "") + $('#product_rule_table').append(content); + } + ); +} + +function removeProductRule(id) +{ + $('#product_rule_' + id + '_tr').remove(); +} + +function toggleCartRuleFilter(id) +{ + if ($(id).attr('checked')) + $('#' + $(id).attr('id') + '_div').show(400); + else + $('#' + $(id).attr('id') + '_div').hide(200); +} + +function removeCartRuleOption(item) +{ + var id = $(item).attr('id').replace('_remove', ''); + $('#' + id + '_2 option:selected').remove().appendTo('#' + id + '_1'); +} + +function addCartRuleOption(item) +{ + var id = $(item).attr('id').replace('_add', ''); + $('#' + id + '_1 option:selected').remove().appendTo('#' + id + '_2'); +} + +function updateProductRuleShortDescription(item) +{ + var id1 = $(item).attr('id').replace('_add', '').replace('_remove', ''); + var id2 = id1.replace('_select', ''); + var length = $('#' + id1 + '_2 option').length; + if (length == 1) + $('#' + id2 + '_match').val($('#' + id1 + '_2 option').first().text().trim()); + else + $('#' + id2 + '_match').val(length); +} + +var restrictions = new Array('country', 'carrier', 'group', 'cart_rule'); +for (i in restrictions) +{ + toggleCartRuleFilter($('#' + restrictions[i] + '_restriction')); + $('#' + restrictions[i] + '_restriction').click(function() {toggleCartRuleFilter(this);}); + $('#' + restrictions[i] + '_select_remove').click(function() {removeCartRuleOption(this);}); + $('#' + restrictions[i] + '_select_add').click(function() {addCartRuleOption(this);}); +} +toggleCartRuleFilter($('#product_restriction')); +$('#product_restriction').click(function() {toggleCartRuleFilter(this);}); + +function toggleApplyDiscount(percent, amount, apply_to) +{ + if (percent) + { + $('#apply_discount_percent_div').show(400); + if ($('#apply_discount_to_product').attr('checked')) + toggleApplyDiscountTo(); + $('#apply_discount_to_cheapest').removeAttr('disabled'); + $('#apply_discount_to_cheapest').removeAttr('checked'); + } + else + { + $('#apply_discount_percent_div').hide(200); + $('#reduction_percent').val('0'); + $('#apply_discount_to_cheapest').attr('disabled', 'disabled'); + } + + if (amount) + { + $('#apply_discount_amount_div').show(400); + if ($('#apply_discount_to_product').attr('checked')) + toggleApplyDiscountTo(); + $('#apply_discount_to_cheapest').attr('disabled', 'disabled'); + $('#apply_discount_to_cheapest').removeAttr('checked'); + } + else + { + $('#apply_discount_amount_div').hide(200); + $('#reduction_amount').val('0'); + $('#apply_discount_to_cheapest').removeAttr('disabled'); + } + + if (apply_to) + $('#apply_discount_to_div').show(400); + else + { + toggleApplyDiscountTo(); + $('#apply_discount_to_div').hide(200); + } +} + +function toggleApplyDiscountTo() +{ + if ($('#apply_discount_to_product').attr('checked')) + $('#apply_discount_to_product_div').show(400); + else + { + $('#apply_discount_to_product_div').hide(200); + $('#reductionProductFilter').val(''); + if ($('#apply_discount_to_order').attr('checked')) + $('#reduction_product').val('0'); + if ($('#apply_discount_to_cheapest').attr('checked')) + $('#reduction_product').val('-1'); + if ($('#apply_discount_to_selection').attr('checked')) + $('#reduction_product').val('-2'); + } +} + +function toggleGiftProduct() +{ + if ($('#free_gift_on').attr('checked')) + $('#free_gift_div').show(400); + else + { + $('#gift_product').val('0'); + $('#giftProductFilter').val(''); + $('#free_gift_div').hide(200); + } +} + +$('#apply_discount_percent').click(function() {toggleApplyDiscount(true, false, true);}); +if ($('#apply_discount_percent').attr('checked')) + toggleApplyDiscount(true, false, true); + +$('#apply_discount_amount').click(function() {toggleApplyDiscount(false, true, true);}); +if ($('#apply_discount_amount').attr('checked')) + toggleApplyDiscount(false, true, true); + +$('#apply_discount_off').click(function() {toggleApplyDiscount(false, false, false);}); +if ($('#apply_discount_off').attr('checked')) + toggleApplyDiscount(false, false, false); + +$('#apply_discount_to_order').click(function() {toggleApplyDiscountTo();}); +if ($('#apply_discount_to_order').attr('checked')) + toggleApplyDiscountTo(); + +$('#apply_discount_to_product').click(function() {toggleApplyDiscountTo();}); +if ($('#apply_discount_to_product').attr('checked')) + toggleApplyDiscountTo(); + +$('#apply_discount_to_cheapest').click(function() {toggleApplyDiscountTo();}); +if ($('#apply_discount_to_cheapest').attr('checked')) + toggleApplyDiscountTo(); + +$('#apply_discount_to_selection').click(function() {toggleApplyDiscountTo();}); +if ($('#apply_discount_to_selection').attr('checked')) + toggleApplyDiscountTo(); + +$('#free_gift_on').click(function() {toggleGiftProduct();}); +$('#free_gift_off').click(function() {toggleGiftProduct();}); +toggleGiftProduct(); + +// Main form submit +$('#cart_rule_form').submit(function() { + if ($('#customerFilter').val() == '') + $('#id_customer').val('0'); + + for (i in restrictions) + { + if ($('#' + restrictions[i] + '_select_1 option').length == 0) + $('#' + restrictions[i] + '_restriction').removeAttr('checked'); + else + { + $('#' + restrictions[i] + '_select_2 option').each(function(i) { + $(this).attr('selected', 'selected'); + }); + } + } + + $('.product_rule_toselect option').each(function(i) { + $(this).attr('selected', 'selected'); + }); +}); + +$('#giftProductFilter') + .autocomplete( + 'ajax-tab.php', { + minChars: 2, + max: 50, + width: 500, + selectFirst: false, + scroll: false, + dataType: 'json', + formatItem: function(data, i, max, value, term) { + return value; + }, + parse: function(data) { + var mytab = new Array(); + for (var i = 0; i < data.length; i++) + mytab[mytab.length] = { data: data[i], value: (data[i].reference + ' ' + data[i].name).trim() }; + return mytab; + }, + extraParams: { + controller: 'AdminCartRules', + token: currentToken, + giftProductFilter: 1 + } + } + ) + .result(function(event, data, formatted) { + $('#gift_product').val(data.id_product); + $('#giftProductFilter').val((data.reference + ' ' + data.name).trim()); + }); + +$('#reductionProductFilter') + .autocomplete( + 'ajax-tab.php', { + minChars: 2, + max: 50, + width: 500, + selectFirst: false, + scroll: false, + dataType: 'json', + formatItem: function(data, i, max, value, term) { + return value; + }, + parse: function(data) { + var mytab = new Array(); + for (var i = 0; i < data.length; i++) + mytab[mytab.length] = { data: data[i], value: (data[i].reference + ' ' + data[i].name).trim() }; + return mytab; + }, + extraParams: { + controller: 'AdminCartRules', + token: currentToken, + reductionProductFilter: 1 + } + } + ) + .result(function(event, data, formatted) { + $('#reduction_product').val(data.id_product); + $('#reductionProductFilter').val((data.reference + ' ' + data.name).trim()); + }); + +$('#customerFilter') + .autocomplete( + 'ajax-tab.php', { + minChars: 2, + max: 50, + width: 500, + selectFirst: false, + scroll: false, + dataType: 'json', + formatItem: function(data, i, max, value, term) { + return value; + }, + parse: function(data) { + var mytab = new Array(); + for (var i = 0; i < data.length; i++) + mytab[mytab.length] = { data: data[i], value: data[i].cname + ' (' + data[i].email + ')' }; + return mytab; + }, + extraParams: { + controller: 'AdminCartRules', + token: currentToken, + customerFilter: 1 + } + } + ) + .result(function(event, data, formatted) { + $('#id_customer').val(data.id_customer); + $('#customerFilter').val(data.cname + ' (' + data.email + ')'); + }); diff --git a/admin-dev/themes/template/cart_rules/form.tpl b/admin-dev/themes/template/cart_rules/form.tpl new file mode 100644 index 000000000..ffdc6c372 --- /dev/null +++ b/admin-dev/themes/template/cart_rules/form.tpl @@ -0,0 +1,59 @@ +
+
+ {l s='Cart Rule'} + {if $currentObject->id}{/if} + +
+ + {foreach from=$languages item=language} + +
+ {/foreach} +

{l s='Will be displayed in the cart summary as well as on the invoice.'}

+
+ +
+ +

{l s='For you only, never displayed to the customer.'}

+
+ +
+ + +

{l s='Optional, the rule will automatically be applied if you leave this field blank.'}

+
+ +
+ +
+ +
+ +
+ +
+    + getFieldValue($currentObject, 'active')|intval}checked="checked"{/if} /> + +    + getFieldValue($currentObject, 'active')|intval}checked="checked"{/if} /> + +
+
+ {l s='Conditions'} + {include file='cart_rules/conditions.tpl'} +
 

+ {l s='Actions'} + {include file='cart_rules/actions.tpl'} +
+ +
+
+ + \ No newline at end of file diff --git a/admin-dev/themes/template/cart_rules/product_rule.tpl b/admin-dev/themes/template/cart_rules/product_rule.tpl new file mode 100644 index 000000000..fb78f6506 --- /dev/null +++ b/admin-dev/themes/template/cart_rules/product_rule.tpl @@ -0,0 +1,36 @@ + + + + {l s='Remove'} + + + + + + [{$product_rule_type}] {l s='The cart must contain at least'} + + + + + + {l s='product(s) matching'} + + + + + + + {l s='Choose'} {l s='Choose'} + +
+
+ {$product_rule_choose_content} +
+
+ + + + \ No newline at end of file diff --git a/admin-dev/themes/template/cart_rules/product_rule_itemlist.tpl b/admin-dev/themes/template/cart_rules/product_rule_itemlist.tpl new file mode 100644 index 000000000..f00ecdd57 --- /dev/null +++ b/admin-dev/themes/template/cart_rules/product_rule_itemlist.tpl @@ -0,0 +1,32 @@ + + + + + +
+

{l s='Selected'}

+

+ + {l s='Remove'} >> + +
+

{l s='Unselected'}

+

+ + << {l s='Add'} + +
+ + \ No newline at end of file diff --git a/admin-dev/themes/template/categories/list_header.tpl b/admin-dev/themes/template/categories/list_header.tpl index b58e775fd..2c34be918 100644 --- a/admin-dev/themes/template/categories/list_header.tpl +++ b/admin-dev/themes/template/categories/list_header.tpl @@ -82,7 +82,7 @@ {foreach from=$toolbar_btn item=btn key=k}
  • - {$btn.desc} + {$btn.desc}
  • {/foreach} diff --git a/admin-dev/themes/template/customers/form.tpl b/admin-dev/themes/template/customers/form.tpl index 382216c18..003e8d409 100644 --- a/admin-dev/themes/template/customers/form.tpl +++ b/admin-dev/themes/template/customers/form.tpl @@ -59,7 +59,7 @@ {foreach from=$toolbar_btn item=btn key=k}
  • - {$btn.desc} + {$btn.desc}
  • {/foreach} diff --git a/admin-dev/themes/template/helper/form/form.tpl b/admin-dev/themes/template/helper/form/form.tpl index 1ffb85a0a..f1ad5e14c 100644 --- a/admin-dev/themes/template/helper/form/form.tpl +++ b/admin-dev/themes/template/helper/form/form.tpl @@ -67,7 +67,7 @@ {foreach from=$toolbar_btn item=btn key=k}
  • - {$btn.desc} + {$btn.desc}
  • {/foreach} diff --git a/admin-dev/themes/template/helper/list/list_header.tpl b/admin-dev/themes/template/helper/list/list_header.tpl index 91ede11d9..d97308b23 100644 --- a/admin-dev/themes/template/helper/list/list_header.tpl +++ b/admin-dev/themes/template/helper/list/list_header.tpl @@ -61,7 +61,7 @@ {foreach from=$toolbar_btn item=btn key=k}
  • - {$btn.desc} + {$btn.desc}
  • {/foreach} diff --git a/admin-dev/themes/template/helper/options/options.tpl b/admin-dev/themes/template/helper/options/options.tpl index 73ee1ebba..c8c330e1f 100644 --- a/admin-dev/themes/template/helper/options/options.tpl +++ b/admin-dev/themes/template/helper/options/options.tpl @@ -136,13 +136,13 @@ {/foreach} - {elseif $field['type'] == 'textareaLang' } + {elseif $field['type'] == 'textareaLang'} {foreach $field['languages'] AS $id_lang => $value}
    {/foreach} - {elseif $field['type'] == 'selectLang' } + {elseif $field['type'] == 'selectLang'} {foreach $languages as $language}
    + {if isset($field['next'])}   {$field['next']|strval}{/if} {$field['currency_right']} {elseif $field['type'] == 'disabled'} @@ -38,7 +38,7 @@ {elseif $field['type'] == 'maintenance_ip'}
    {$field['script_ip']} - + {$field['link_remove_ip']} {else}
    diff --git a/admin-dev/themes/template/products/combinations.tpl b/admin-dev/themes/template/products/combinations.tpl index 143900e89..eb3fad687 100644 --- a/admin-dev/themes/template/products/combinations.tpl +++ b/admin-dev/themes/template/products/combinations.tpl @@ -167,7 +167,7 @@ {if $currency->format % 2 != 0}{$currency->sign}{/if} - {if $currency->format % 2 == 0 } {$currency->sign} {/if}({l s='overrides Wholesale price on Information tab'}) + {if $currency->format % 2 == 0} {$currency->sign} {/if}({l s='overrides Wholesale price on Information tab'}) {l s='Impact on price:'} @@ -178,7 +178,7 @@   {l s='of'}  {if $currency->format % 2 != 0}{$currency->sign} {/if} - {if $currency->format % 2 == 0 } {$currency->sign}{/if} + {if $currency->format % 2 == 0} {$currency->sign}{/if} {if $country_display_tax_label} {l s='(tax excl.)'} {l s='or'} {if $currency->format % 2 != 0}{$currency->sign} {/if} @@ -218,9 +218,9 @@ {if $ps_use_ecotax} - {l s='Eco-tax:' } + {l s='Eco-tax:'} - {if $currency->format % 2 != 0 }{$currency->sign}{/if} + {if $currency->format % 2 != 0}{$currency->sign}{/if} {if $currency->format % 2 == 0} {$currency->sign}{/if} @@ -229,7 +229,7 @@ {/if} - + @@ -252,7 +252,7 @@
    - {l s='Image:' } + {l s='Image:'}
      {foreach from=$images key=k item=image} diff --git a/admin-dev/themes/template/products/content.tpl b/admin-dev/themes/template/products/content.tpl index 6b58bc623..80e8a85aa 100644 --- a/admin-dev/themes/template/products/content.tpl +++ b/admin-dev/themes/template/products/content.tpl @@ -12,7 +12,7 @@ {foreach from=$toolbar_btn item=btn key=k}
    • - {$btn.desc} + {$btn.desc}
    • {/foreach} diff --git a/admin-dev/themes/template/shipping/content.tpl b/admin-dev/themes/template/shipping/content.tpl index 7d0de5f79..d20147995 100644 --- a/admin-dev/themes/template/shipping/content.tpl +++ b/admin-dev/themes/template/shipping/content.tpl @@ -56,7 +56,7 @@ {foreach $ranges AS $range} {$currency->getSign('left')} - + {$currency->getSign('right')} {/foreach} diff --git a/classes/AdminController.php b/classes/AdminController.php index bd89dad83..0ecd0cd91 100644 --- a/classes/AdminController.php +++ b/classes/AdminController.php @@ -296,7 +296,7 @@ class AdminControllerCore extends Controller if (!empty($action) && method_exists($this, 'ajaxProcess'.Tools::toCamelCase($action))) $this->{'ajaxProcess'.Tools::toCamelCase($action)}(); else if (method_exists($this, 'ajaxProcess')) - $this->ajaxProcess(); + $this->ajaxProcess(); // @TODO We should use a displayAjaxError /*$this->displayErrors(); @@ -1104,7 +1104,7 @@ class AdminControllerCore extends Controller 'content' => $this->content, 'url_post' => self::$currentIndex.'&token='.$this->token, )); - } + } /** * initialize the invalid doom page of death @@ -1703,7 +1703,7 @@ class AdminControllerCore extends Controller * @param integer $id_lang Language id (optional) * @return string */ - protected function getFieldValue($obj, $key, $id_lang = null) + public function getFieldValue($obj, $key, $id_lang = null) { if ($id_lang) $default_value = ($obj->id && isset($obj->{$key}[$id_lang])) ? $obj->{$key}[$id_lang] : ''; @@ -2334,5 +2334,5 @@ EOF; return $output; return $output; - } +} } diff --git a/classes/AdminTab.php b/classes/AdminTab.php index b3a4c9fdc..665183b87 100644 --- a/classes/AdminTab.php +++ b/classes/AdminTab.php @@ -2167,7 +2167,7 @@ abstract class AdminTabCore * @param integer $id_lang Language id (optional) * @return string */ - protected function getFieldValue($obj, $key, $id_lang = NULL, $id_shop = null) + public function getFieldValue($obj, $key, $id_lang = NULL, $id_shop = null) { if (!$id_shop && $obj->isLangMultishop()) $id_shop = Context::getContext()->shop->getID(); diff --git a/classes/Carrier.php b/classes/Carrier.php index 879bc28c1..bbb754f81 100644 --- a/classes/Carrier.php +++ b/classes/Carrier.php @@ -174,6 +174,13 @@ class CarrierCore extends ObjectModel return true; } + public function delete() + { + if (!parent::delete()) + return false; + return Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'cart_rule_carrier WHERE id_carrier = '.(int)$this->id); + } + /** * Change carrier id in delivery prices when updating a carrier * diff --git a/classes/Cart.php b/classes/Cart.php index f874f7d6a..bb08f8e5a 100644 --- a/classes/Cart.php +++ b/classes/Cart.php @@ -212,7 +212,7 @@ class CartCore extends ObjectModel DELETE FROM `'._DB_PREFIX_.'customization` WHERE `id_cart` = '.(int)$this->id); - if (!Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_discount` WHERE `id_cart` = '.(int)($this->id)) + if (!Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart` = '.(int)($this->id)) OR !Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)($this->id))) return false; @@ -247,6 +247,15 @@ class CartCore extends ObjectModel return 0; } + /** + * @deprecated 1.5.0.1 + */ + public function getDiscounts($lite = false, $refresh = false) + { + Tools::displayAsDeprecated(); + return $this->getCartRules(); + } + /** * Return cart discounts * @@ -254,78 +263,49 @@ class CartCore extends ObjectModel * @param bool true will erase the cache * @result array Discounts */ - public function getDiscounts($lite = false, $refresh = false) + public function getCartRules() { - // if discounts are never used - if (!Discount::isFeatureActive()) + // TODO : add cache + + // If the cart has not been saved, then there can't be any cart rule applied + if (!CartRule::isFeatureActive() || !$this->id) return array(); - if (!$this->id) - return array(); - - if (!$refresh) - { - if (!$lite AND isset(self::$_discounts[$this->id])) - return self::$_discounts[$this->id]; - - if ($lite AND isset(self::$_discountsLite[$this->id])) - return self::$_discountsLite[$this->id]; - } - + $total_products_ti = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS); + $total_products_te = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS); + $shipping_ti = $this->getOrderShippingCost(); + $shipping_te = $this->getOrderShippingCost(NULL, false); + $result = Db::getInstance()->executeS(' - SELECT d.*, `id_cart` - FROM `'._DB_PREFIX_.'cart_discount` c - LEFT JOIN `'._DB_PREFIX_.'discount` d ON c.`id_discount` = d.`id_discount` - WHERE `id_cart` = '.(int)($this->id)); + SELECT * + FROM `'._DB_PREFIX_.'cart_cart_rule` cd + LEFT JOIN `'._DB_PREFIX_.'cart_rule` cr ON cd.`id_cart_rule` = cr.`id_cart_rule` + LEFT JOIN `'._DB_PREFIX_.'cart_rule_lang` crl ON (cd.`id_cart_rule` = cr.`id_cart_rule` AND crl.id_lang = '.(int)$this->id_lang.') + WHERE `id_cart` = '.(int)$this->id); - $products = $this->getProducts(); - foreach ($result AS $k => $discount) + foreach ($result as &$row) { - $categories = Discount::getCategories((int)($discount['id_discount'])); - $in_category = false; - foreach ($products AS $product) - if (Product::idIsOnCategoryId((int)($product['id_product']), $categories)) - { - $in_category = true; - break; - } - if (!$in_category) - unset($result[$k]); + $cartRule = new CartRule($row['id_cart_rule'], (int)$this->id_lang); + $row['value_real'] = $cartRule->getValue(true); + $row['value_tax_exc'] = $cartRule->getValue(false); + + // Retro compatibility < 1.5.0.2 + $row['id_discount'] = $row['id_cart_rule']; + $row['description'] = $row['name']; } - if ($lite) - { - self::$_discountsLite[$this->id] = $result; - return $result; - } - - $total_products_wt = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS); - $total_products = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS); - $shipping_wt = $this->getOrderShippingCost(); - $shipping = $this->getOrderShippingCost(NULL, false); - self::$_discounts[$this->id] = array(); - foreach ($result as $row) - { - $discount = new Discount($row['id_discount'], (int)($this->id_lang)); - $row['description'] = $discount->description ? $discount->description : $discount->name; - $row['value_real'] = $discount->getValue(sizeof($result), $total_products_wt, $shipping_wt, $this->id); - $row['value_tax_exc'] = $discount->getValue(sizeof($result), $total_products, $shipping, $this->id, false); - if ($row['value_real'] !== 0) - self::$_discounts[$this->id][] = $row; - else - $this->deleteDiscount($row['id_discount']); - } - return isset(self::$_discounts[$this->id]) ? self::$_discounts[$this->id] : NULL; + return $result; } + // Todo: see uses and change name public function getDiscountsCustomer($id_discount) { - if (!Discount::isFeatureActive()) + if (!CartRule::isFeatureActive()) return 0; return Db::getInstance()->getValue(' SELECT COUNT(*) - FROM `'._DB_PREFIX_.'cart_discount` + FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_discount` = '.(int)($id_discount).' AND `id_cart` = '.(int)($this->id)); } @@ -570,14 +550,17 @@ class CartCore extends ObjectModel } /** - * Add a discount to the cart (NO controls except doubles) - * - * @param integer $id_discount The discount to add to the cart - * @result boolean Update result + * @deprecated 1.5.0.1 */ - public function addDiscount($id_discount) + public function addDiscount($id_discount) { - return Db::getInstance()->AutoExecute(_DB_PREFIX_.'cart_discount', array('id_discount' => (int)($id_discount), 'id_cart' => (int)($this->id)), 'INSERT'); + Tools::displayAsDeprecated(); + return $this->addCartRule($id_discount); + } + + public function addCartRule($id_cart_rule) + { + return Db::getInstance()->AutoExecute(_DB_PREFIX_.'cart_cart_rule', array('id_cart_rule' => (int)$id_cart_rule, 'id_cart' => (int)$this->id), 'INSERT'); } public function containsProduct($id_product, $id_product_attribute = 0, $id_customization = false) @@ -701,6 +684,9 @@ class CartCore extends ObjectModel // refresh cache of self::_products $this->_products = $this->getProducts(true); $this->update(true); + $context = Context::getContext()->cloneContext(); + $context->cart = $this; + CartRule::autoAddToCart($context); if ($product->customizable) return $this->_updateCustomizationQuantity((int)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, $operator); @@ -820,19 +806,18 @@ class CartCore extends ObjectModel return (bool)Db::getInstance()->getValue('SELECT `id_cart` FROM `'._DB_PREFIX_.'orders` WHERE `id_cart` = '.(int)$this->id); } - /* - ** Deletion - */ - /** - * Delete a discount from the cart - * - * @param integer $id_discount Discount ID - * @return boolean result + * @deprecated 1.5.0.1 */ - public function deleteDiscount($id_discount) + public function deleteDiscount($id_discount) { - return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_discount` WHERE `id_discount` = '.(int)$id_discount.' AND `id_cart` = '.(int)$this->id.' LIMIT 1'); + Tools::displayAsDeprecated(); + return $this->removeCartRule($id_discount); + } + + public function removeCartRule($id_cart_rule) + { + return Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart_rule` = '.(int)$id_cart_rule.' AND `id_cart` = '.(int)$this->id.' LIMIT 1'); } /** @@ -982,7 +967,8 @@ class CartCore extends ObjectModel die(Tools::displayError()); // if discounts are never used - if ($type == Cart::ONLY_DISCOUNTS && !Discount::isFeatureActive()) + // Todo: remove and replace by cart rules + if ($type == Cart::ONLY_DISCOUNTS && !CartRule::isFeatureActive()) return 0; // no shipping cost if is a cart with only virtuals products $virtual = $this->isVirtualCart(); @@ -1025,6 +1011,7 @@ class CartCore extends ObjectModel $order_total += $total_price; } $order_total_products = $order_total; + // Todo: consider optimizations if ($type == Cart::ONLY_DISCOUNTS) $order_total = 0; // Wrapping Fees @@ -1039,61 +1026,28 @@ class CartCore extends ObjectModel } $wrapping_fees = Tools::convertPrice(Tools::ps_round($wrapping_fees, 2), Currency::getCurrencyInstance((int)($this->id_currency))); } - if ($type != Cart::ONLY_PRODUCTS) + + $order_total_discount = 0; + if ($type != Cart::ONLY_PRODUCTS && CartRule::isFeatureActive()) { - $discounts = array(); - if (Discount::isFeatureActive()) - { - /* Firstly get all discounts, looking for a free shipping one (in order to substract shipping fees to the total amount) */ - if ($discountIds = $this->getDiscounts(true)) - { - foreach ($discountIds AS $id_discount) - { - $discount = new Discount((int)($id_discount['id_discount'])); - if (Validate::isLoadedObject($discount)) - { - $discounts[] = $discount; - if ($discount->id_discount_type == Discount::FREE_SHIPPING) - foreach($products AS $product) - { - $categories = Discount::getCategories($discount->id); - if (count($categories) AND Product::idIsOnCategoryId($product['id_product'], $categories)) - { - if($type == Cart::ONLY_DISCOUNTS) - $order_total -= $shipping_fees; - $shipping_fees = 0; - break; - } - } - } - } - /* Secondly applying all vouchers to the correct amount */ - $shrunk = false; - foreach ($discounts AS $discount) - if ($discount->id_discount_type != Discount::FREE_SHIPPING) - { - $order_total -= Tools::ps_round((float)($discount->getValue(sizeof($discounts), $order_total_products, $shipping_fees, $this->id, (int)($withTaxes))), 2); - if ($discount->id_discount_type == Discount::AMOUNT) - if (in_array($discount->behavior_not_exhausted, array(1,2))) - $shrunk = true; - } - - $order_total_discount = 0; - if ($shrunk AND $order_total < (-$wrapping_fees - $order_total_products - $shipping_fees)) - $order_total_discount = -$wrapping_fees - $order_total_products - $shipping_fees; - else - $order_total_discount = $order_total; - } - } + $result = $this->getCartRules(); + foreach (ObjectModel::hydrateCollection('CartRule', $result, Configuration::get('PS_LANG_DEFAULT')) AS $cartRule) + $order_total_discount += Tools::ps_round($cartRule->getValue($withTaxes)); + $order_total_discount = min(Tools::ps_round($order_total_discount), $wrapping_fees + $order_total_products + $shipping_fees); + $order_total -= $order_total_discount; } - if ($type == Cart::ONLY_SHIPPING) return $shipping_fees; - if ($type == Cart::ONLY_WRAPPING) return $wrapping_fees; - if ($type == Cart::BOTH) $order_total += $shipping_fees + $wrapping_fees; - if ($order_total < 0 AND $type != Cart::ONLY_DISCOUNTS) return 0; - if ($type == Cart::ONLY_DISCOUNTS AND isset($order_total_discount)) - return Tools::ps_round((float)($order_total_discount), 2); - return Tools::ps_round((float)($order_total), 2); + if ($type == Cart::ONLY_SHIPPING) + return $shipping_fees; + if ($type == Cart::ONLY_WRAPPING) + return $wrapping_fees; + if ($type == Cart::BOTH) + $order_total += $shipping_fees + $wrapping_fees; + if ($order_total < 0 AND $type != Cart::ONLY_DISCOUNTS) + return 0; + if ($type == Cart::ONLY_DISCOUNTS) + return $order_total_discount; + return Tools::ps_round((float)$order_total, 2); } /** @@ -1110,33 +1064,6 @@ class CartCore extends ObjectModel if (!$default_country) $default_country = Context::getContext()->country; - // Checking discounts in cart - $products = $this->getProducts(); - if (Discount::isFeatureActive()) - $discounts = $this->getDiscounts(true); - else - $discounts = null; - if ($discounts) - foreach ($discounts AS $id_discount) - if ($id_discount['id_discount_type'] == Discount::FREE_SHIPPING) - { - if ($id_discount['minimal'] > 0) - { - $total_cart = 0; - - $categories = Discount::getCategories((int)($id_discount['id_discount'])); - if (sizeof($categories)) - foreach($products AS $product) - if (Product::idIsOnCategoryId((int)($product['id_product']), $categories)) - $total_cart += $product['total_wt']; - - if ($total_cart >= $id_discount['minimal']) - return 0; - } - else - return 0; - } - // Order total in default currency without fees $order_total = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING); @@ -1250,9 +1177,9 @@ class CartCore extends ObjectModel $free_fees_price = 0; if (isset($configuration['PS_SHIPPING_FREE_PRICE'])) $free_fees_price = Tools::convertPrice((float)($configuration['PS_SHIPPING_FREE_PRICE']), Currency::getCurrencyInstance((int)($this->id_currency))); - $orderTotalwithDiscounts = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING); - if ($orderTotalwithDiscounts >= (float)($free_fees_price) AND (float)($free_fees_price) > 0) - return $shipping_cost; + // $orderTotalwithDiscounts = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING); + // if ($orderTotalwithDiscounts >= (float)($free_fees_price) AND (float)($free_fees_price) > 0) + // return $shipping_cost; if (isset($configuration['PS_SHIPPING_FREE_WEIGHT']) AND $this->getTotalWeight() >= (float)($configuration['PS_SHIPPING_FREE_WEIGHT']) AND (float)($configuration['PS_SHIPPING_FREE_WEIGHT']) > 0) return $shipping_cost; @@ -1290,9 +1217,10 @@ class CartCore extends ObjectModel if (isset($configuration['PS_SHIPPING_HANDLING']) AND $carrier->shipping_handling) $shipping_cost += (float)($configuration['PS_SHIPPING_HANDLING']); + // TODO : $products does not exists // Additional Shipping Cost per product - foreach($products AS $product) - $shipping_cost += $product['additional_shipping_cost'] * $product['cart_quantity']; + // foreach($products AS $product) + // $shipping_cost += $product['additional_shipping_cost'] * $product['cart_quantity']; $shipping_cost = Tools::convertPrice($shipping_cost, Currency::getCurrencyInstance((int)($this->id_currency))); @@ -1357,90 +1285,16 @@ class CartCore extends ObjectModel } return self::$_totalWeight[$this->id]; } - + /** - * Check discount validity - * - * @return mixed Return a string if an error occurred and false otherwise - */ - function checkDiscountValidity($discountObj, $discounts, $order_total, $products, $checkCartDiscount = false, - Customer $customer = null, Shop $shop = null) + * @deprecated 1.5.0.1 + */ + public function checkDiscountValidity($discountObj, $discounts, $order_total, $products, $checkCartDiscount = false) { - if (!$shop) - $shop = Context::getContext()->shop; - if (!$customer) - $customer = Context::getContext()->customer; - if (!$order_total) - return Tools::displayError('Cannot add voucher if order is free.'); - if (!$discountObj->active) - return Tools::displayError('This voucher has already been used or is disabled.'); - if (!$discountObj->quantity) - return Tools::displayError('This voucher has expired (usage limit attained).'); - if ($discountObj->id_discount_type == Discount::AMOUNT AND $this->id_currency != $discountObj->id_currency) - return Tools::displayError('This voucher can only be used in the following currency:').' - '.Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT `name` FROM `'._DB_PREFIX_.'currency` WHERE id_currency = '.(int)$discountObj->id_currency); - if ($checkCartDiscount - AND ( - $this->getDiscountsCustomer($discountObj->id) >= $discountObj->quantity_per_user - OR (Order::getDiscountsCustomer($customer->id, $discountObj->id) + $this->getDiscountsCustomer($discountObj->id) >= $discountObj->quantity_per_user) >= $discountObj->quantity_per_user - ) - ) - return Tools::displayError('You cannot use this voucher anymore (usage limit attained).'); - if (strtotime($discountObj->date_from) > time()) - return Tools::displayError('This voucher is not yet valid'); - if (strtotime($discountObj->date_to) < time()) - return Tools::displayError('This voucher has expired.'); - if (!$discountObj->isAssociatedToShop((int)$shop->id)) - return Tools::displayError('This voucher is not available with this shop.'); - if (sizeof($discounts) >= 1 AND $checkCartDiscount) - { - if (!$discountObj->cumulable) - return Tools::displayError('This voucher is not valid with other current discounts.'); - foreach ($discounts as $discount) - if (!$discount['cumulable']) - return Tools::displayError('Voucher is not valid with other discounts.'); - - foreach($discounts as $discount) - if($discount['id_discount'] == $discountObj->id) - return Tools::displayError('This voucher is already in your cart'); - } - - $groups = Customer::getGroupsStatic($this->id_customer); - - if (($discountObj->id_customer OR $discountObj->id_group) AND ((($this->id_customer != $discountObj->id_customer) OR ($this->id_customer == 0)) AND !in_array($discountObj->id_group, $groups))) - { - if (!$customer->isLogged()) - return Tools::displayError('You cannot use this voucher.').' - '.Tools::displayError('Please log in.'); - return Tools::displayError('You cannot use this voucher.'); - } - - $onlyProductWithDiscount = true; - if (!$discountObj->cumulable_reduction) - { - foreach ($products as $product) - if (!$product['reduction_applies'] AND !$product['on_sale']) - $onlyProductWithDiscount = false; - } - if (!$discountObj->cumulable_reduction AND $onlyProductWithDiscount) - return Tools::displayError('This voucher is not valid for marked or reduced products.'); - $total_cart = 0; - $categories = Discount::getCategories($discountObj->id); - $returnErrorNoProductCategory = true; - foreach($products AS $product) - { - if(count($categories)) - if (Product::idIsOnCategoryId($product['id_product'], $categories)) - { - if ((!$discountObj->cumulable_reduction AND !$product['reduction_applies'] AND !$product['on_sale']) OR $discountObj->cumulable_reduction) - $total_cart += $discountObj->include_tax ? $product['total_wt'] : $product['total']; - $returnErrorNoProductCategory = false; - } - } - if ($returnErrorNoProductCategory) - return Tools::displayError('This discount does not apply to that product category.'); - if ($total_cart < $discountObj->minimal) - return Tools::displayError('The order total is not high enough or this voucher cannot be used with those products.'); - return false; + Tools::displayAsDeprecated(); + $context = Context::getContext()->cloneContext(); + $context->cart = $this; + return $discountObj->checkValidity($context); } /** @@ -1468,7 +1322,7 @@ class CartCore extends ObjectModel $total_free_ship = 0; if ($free_ship = Tools::convertPrice((float)(Configuration::get('PS_SHIPPING_FREE_PRICE')), new Currency((int)($this->id_currency)))) { - $discounts = $this->getDiscounts(); + $discounts = $this->getCartRules(); $total_free_ship = $free_ship - ($this->getOrderTotal(true, Cart::ONLY_PRODUCTS) + $this->getOrderTotal(true, Cart::ONLY_DISCOUNTS)); foreach ($discounts as $discount) if ($discount['id_discount_type'] == Discount::FREE_SHIPPING) @@ -1485,7 +1339,7 @@ class CartCore extends ObjectModel 'formattedAddresses' => $formattedAddresses, 'carrier' => new Carrier($this->id_carrier, $id_lang), 'products' => $this->getProducts(false), - 'discounts' => $this->getDiscounts(false, true), + 'discounts' => $this->getCartRules(), 'is_virtual_cart' => (int)$this->isVirtualCart(), 'total_discounts' => $this->getOrderTotal(true, Cart::ONLY_DISCOUNTS), 'total_discounts_tax_exc' => $this->getOrderTotal(false, Cart::ONLY_DISCOUNTS), diff --git a/classes/CartRule.php b/classes/CartRule.php new file mode 100644 index 000000000..ed1aa1770 --- /dev/null +++ b/classes/CartRule.php @@ -0,0 +1,624 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision: 7040 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class CartRuleCore extends ObjectModel +{ + public $id; + public $name; + public $id_customer; + public $date_from; + public $date_to; + public $description; + public $quantity = 1; + public $quantity_per_user = 1; + public $priority = 1; + public $code; + public $minimum_amount; + public $minimum_amount_tax; + public $minimum_amount_currency; + public $minimum_amount_shipping; + public $country_restriction; + public $carrier_restriction; + public $group_restriction; + public $cart_rule_restriction; + public $product_restriction; + public $free_shipping; + public $reduction_percent; + public $reduction_amount; + public $reduction_tax; + public $reduction_currency; + public $reduction_product; + public $gift_product; + public $active = 1; + public $date_add; + public $date_upd; + + protected $table = 'cart_rule'; + protected $identifier = 'id_cart_rule'; + + protected $fieldsRequired = array('date_from', 'date_to'); + protected $fieldsSize = array('code' => 254, 'description' => 65534); + protected $fieldsValidate = array( + 'id_customer' => 'isUnsignedId', + 'date_from' => 'isDate', + 'date_to' => 'isDate', + 'description' => 'isCleanHtml', + 'quantity' => 'isUnsignedInt', + 'quantity_per_user' => 'isUnsignedInt', + 'priority' => 'isUnsignedInt', + 'code' => 'isCleanHtml', + 'minimum_amount' => 'isFloat', + 'minimum_amount_tax' => 'isBool', + 'minimum_amount_currency' => 'isInt', + 'minimum_amount_shipping' => 'isBool', + 'country_restriction' => 'isBool', + 'carrier_restriction' => 'isBool', + 'group_restriction' => 'isBool', + 'cart_rule_restriction' => 'isBool', + 'product_restriction' => 'isBool', + 'free_shipping' => 'isBool', + 'reduction_percent' => 'isFloat', + 'reduction_amount' => 'isFloat', + 'reduction_tax' => 'isBool', + 'reduction_currency' => 'isUnsignedId', + 'reduction_product' => 'isInt', + 'gift_product' => 'isUnsignedId', + 'active' => 'isBool', + 'date_add' => 'isDate', + 'date_upd' => 'isDate' + ); + protected $fieldsRequiredLang = array('name'); + protected $fieldsSizeLang = array('name' => 254); + protected $fieldsValidateLang = array('name' => 'isCleanHtml'); + + public function getFields() + { + $this->validateFields(); + $fields['id_customer'] = (int)$this->id_customer; + $fields['priority'] = (int)$this->priority; + $fields['code'] = pSQL($this->code); + $fields['quantity'] = (int)$this->quantity; + $fields['quantity_per_user'] = (int)$this->quantity_per_user; + $fields['date_from'] = pSQL($this->date_from); + $fields['date_to'] = pSQL($this->date_to); + $fields['description'] = pSQL($this->description); + $fields['minimum_amount'] = (float)$this->minimum_amount; + $fields['minimum_amount_tax'] = (int)$this->minimum_amount_tax; + $fields['minimum_amount_currency'] = (int)$this->minimum_amount_currency; + $fields['minimum_amount_shipping'] = (int)$this->minimum_amount_shipping; + $fields['country_restriction'] = (int)$this->country_restriction; + $fields['carrier_restriction'] = (int)$this->carrier_restriction; + $fields['group_restriction'] = (int)$this->group_restriction; + $fields['cart_rule_restriction'] = (int)$this->cart_rule_restriction; + $fields['product_restriction'] = (int)$this->product_restriction; + $fields['free_shipping'] = (int)$this->free_shipping; + $fields['reduction_percent'] = (float)$this->reduction_percent; + $fields['reduction_amount'] = (float)$this->reduction_amount; + $fields['reduction_tax'] = (int)$this->reduction_tax; + $fields['reduction_currency'] = (int)$this->reduction_currency; + $fields['reduction_product'] = (int)$this->reduction_product; + $fields['gift_product'] = (int)$this->gift_product; + $fields['active'] = (int)$this->active; + $fields['date_add'] = pSQL($this->date_add); + $fields['date_upd'] = pSQL($this->date_upd); + return $fields; + } + + public function getTranslationsFieldsChild() + { + if (!$this->validateFieldsLang()) + return false; + return $this->getTranslationsFields(array('name')); + } + + public function add($autodate = true, $nullValues = false) + { + if (!parent::add($autodate, $nullValues)) + return false; + + Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', '1'); + return true; + } + + public function delete() + { + if (!parent::delete()) + return false; + + Configuration::updateGlobalValue('PS_CART_RULE_FEATURE_ACTIVE', CartRule::isCurrentlyUsed($this->table, true)); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart_rule` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_carrier` WHERE `id_cart_rule` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_group` WHERE `id_cart_rule` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_country` WHERE `id_cart_rule` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_cart_rule` WHERE `id_cart_rule_1` = '.(int)$this->id.' OR `id_cart_rule_2` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule` WHERE `id_cart_rule` = '.(int)$this->id); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` WHERE `id_product_rule` NOT IN (SELECT `id_product_rule` FROM `'._DB_PREFIX_.'cart_rule_product_rule`)'); + } + + public static function getIdByCode($code) + { + if (!Validate::isDiscountName($code)) + return false; + return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT `id_cart_rule` FROM `'._DB_PREFIX_.'cart_rule` WHERE `code` = \''.pSQL($code).'\''); + } + + public function getProductRules() + { + if (!Validate::isLoadedObject($this) OR $this->product_restriction == 0) + return array(); + + $productRules = array(); + $result = Db::getInstance()->ExecuteS(' + SELECT * + FROM '._DB_PREFIX_.'cart_rule_product_rule pr + LEFT JOIN '._DB_PREFIX_.'cart_rule_product_rule_value prv ON pr.id_product_rule = prv.id_product_rule + WHERE pr.id_cart_rule = '.(int)$this->id, false); + while ($row = Db::getInstance()->nextRow($result)) + { + if (!isset($productRules[$row['id_product_rule']])) + $productRules[$row['id_product_rule']] = array('quantity' => $row['quantity'], 'type' => $row['type'], 'values' => array()); + $productRules[$row['id_product_rule']]['values'][] = $row['id_item']; + } + return $productRules; + } + + // Todo : Add shop management + public function checkValidity(Context $context, $alreadyInCart = false) + { + if (!CartRule::isFeatureActive()) + return false; + + if (!$this->active) + return Tools::displayError('This voucher is disabled'); + if (!$this->quantity) + return Tools::displayError('This voucher has already been used '); + if (strtotime($this->date_from) > time()) + return Tools::displayError('This voucher is not valid yet'); + if (strtotime($this->date_to) < time()) + return Tools::displayError('This voucher has expired'); + + if ($context->cart->id_customer) + { + $quantityUsed = Db::getInstance()->getValue(' + SELECT count(*) + FROM '._DB_PREFIX_.'orders o + LEFT JOIN '._DB_PREFIX_.'order_discount od ON o.id_order = od.id_order + WHERE o.id_customer = '.$context->cart->id_customer.' + AND od.id_cart_rule = '.(int)$this->id.' + AND '.(int)Configuration::get('PS_OS_ERROR').' != ( + SELECT oh.id_order_state + FROM '._DB_PREFIX_.'order_history oh + WHERE oh.id_order = o.id_order + ORDER BY oh.date_add DESC + LIMIT 1 + )'); + if ($quantityUsed + 1 > $this->quantity_per_user) + return Tools::displayError('You cannot use this voucher anymore (usage limit reached)'); + } + + $otherCartRules = $context->cart->getCartRules(); + if (count($otherCartRules)) + foreach ($otherCartRules as $otherCartRule) + { + if ($otherCartRule['id_cart_rule'] == $this->id && !$alreadyInCart) + return Tools::displayError('This voucher is already in your cart'); + if ($this->carrier_restriction AND $otherCartRule['cart_rule_restriction']) + { + $combinable = Db::getInstance()->getValue(' + SELECT id_cart_rule_1 + FROM '._DB_PREFIX_.'cart_rule_combination + WHERE (id_cart_rule_1 = '.(int)$this->id.' AND id_cart_rule_2 = '.(int)$otherCartRule['id_cart_rule'].') + OR (id_cart_rule_2 = '.(int)$this->id.' AND id_cart_rule_1 = '.(int)$otherCartRule['id_cart_rule'].')'); + if (!$combinable) + { + $cartRule = new CartRule($otherCartRule['cart_rule_restriction'], $context->cart->id_lang); + return Tools::displayError('This voucher is not combinable with an other voucher already in your cart:').' '.$this->name; + } + } + } + + // Get an intersection of the customer groups and the cart rule groups (if the customer is not logged in, the default group is 1) + if ($this->group_restriction) + { + $id_cart_rule = (int)Db::getInstance()->getValue(' + SELECT crg.id_cart_rule + FROM '._DB_PREFIX_.'cart_rule_group crg + WHERE crg.id_cart_rule = '.(int)$this->id.' + AND crg.id_group '.($context->cart->id_customer ? 'IN (SELECT cg.id_group FROM '._DB_PREFIX_.'customer_group cg WHERE cg.id_customer = '.(int)$context->cart->id_customer.')' : '= 1')); + if (!$id_cart_rule) + return Tools::displayError('You cannot use this voucher'); + } + + // Check if the customer delivery address is usable with the cart rule + if ($this->country_restriction AND $context->cart->id_address_delivery) + { + $id_cart_rule = (int)Db::getInstance()->getValue(' + SELECT crc.id_cart_rule + FROM '._DB_PREFIX_.'cart_rule_country crc + WHERE crc.id_cart_rule = '.(int)$this->id.' + AND crc.id_country = (SELECT a.id_country FROM '._DB_PREFIX_.'address a WHERE a.id_address = '.(int)$context->cart->id_address_delivery.' LIMIT 1)'); + if (!$id_cart_rule) + return Tools::displayError('You cannot use this voucher in your country of delivery'); + } + + // Check if the carrier chosen by the customer is usable with the cart rule + if ($this->carrier_restriction AND $context->cart->id_carrier) + { + $id_cart_rule = (int)Db::getInstance()->getValue(' + SELECT crc.id_cart_rule + FROM '._DB_PREFIX_.'cart_rule_carrier crc + WHERE crc.id_cart_rule = '.(int)$this->id.' + AND crc.id_carrier = '.(int)$context->cart->id_carrier); + if (!$id_cart_rule) + return Tools::displayError('You cannot use this voucher with this carrier'); + } + + // Check if the products chosen by the customer are usable with the cart rule + if ($this->product_restriction) + { + $productRules = $this->getProductRules(); + foreach ($productRules as $productRule) + { + switch ($productRule['type']) + { + case 'attributes': + $cartAttributes = Db::getInstance()->ExecuteS(' + SELECT cp.quantity, pac.`id_attribute` + FROM `'._DB_PREFIX_.'cart_product` cp + LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON cp.id_product_attribute = pac.id_product_attribute + WHERE cp.`id_cart` = '.(int)$context->cart->id.' + AND cp.id_product_attribute > 0'); + $matchingProducts = 0; + foreach ($cartAttributes as $cartAttribute) + if (in_array($cartAttribute['id_attribute'], $productRule['values'])) + $matchingProducts += $cartAttribute['quantity']; + if ($matchingProducts < $productRule['quantity']) + return Tools::displayError('You cannot use this voucher with these products'); + break; + case 'products': + $cartProducts = Db::getInstance()->ExecuteS(' + SELECT cp.quantity, cp.`id_product` + FROM `'._DB_PREFIX_.'cart_product` cp + WHERE cp.`id_cart` = '.(int)$context->cart->id); + $matchingProducts = 0; + foreach ($cartProducts as $cartProduct) + if (in_array($cartProduct['id_product'], $productRule['values'])) + $matchingProducts += $cartProduct['quantity']; + if ($matchingProducts < $productRule['quantity']) + return Tools::displayError('You cannot use this voucher with these products'); + break; + case 'categories': + $cartCategories = Db::getInstance()->ExecuteS(' + SELECT cp.quantity, catp.`id_category` + FROM `'._DB_PREFIX_.'cart_product` cp + LEFT JOIN `'._DB_PREFIX_.'category_product` catp ON cp.id_product = catp.id_product + WHERE cp.`id_cart` = '.(int)$context->cart->id); + $matchingProducts = 0; + foreach ($cartCategories as $cartCategory) + if (in_array($cartCategory['id_category'], $productRule['values'])) + $matchingProducts += $cartCategory['quantity']; + if ($matchingProducts < $productRule['quantity']) + return Tools::displayError('You cannot use this voucher with these products'); + break; + } + } + } + + // Check if the cart rule is only usable by a specific customer, and if the current customer is the right one + if ($this->id_customer && $context->cart->id_customer != $this->id_customer) + { + if (!Context::getContext()->customer->isLogged()) + return Tools::displayError('You cannot use this voucher').' - '.Tools::displayError('Please log in'); + return Tools::displayError('You cannot use this voucher'); + } + + if ($this->minimum_amount) + { + $minimum_amount = $this->minimum_amount; + if ($this->minimum_amount_currency != Configuration::get('PS_CURRENCY_DEFAULT')) + { + $minimumAmountCurrency = new Currency($this->minimum_amount); + $minimum_amount = $this->minimum_amount / $minimumAmountCurrency->convertion_rate; + } + $cartTotal = $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_PRODUCTS); + if ($this->minimum_amount_shipping) + $cartTotal += $context->cart->getOrderTotal($this->minimum_amount_tax, Cart::ONLY_SHIPPING); + if ($cartTotal < $minimum_amount) + return Tools::displayError('You do not reach the minimum amount required to use this voucher'); + } + } + + // The reduction value is POSITIVE + public function getValue($useTax, Context $context = NULL) + { + if (!CartRule::isFeatureActive()) + return 0; + if (!$context) + $context = Context::getContext(); + + $reductionValue = 0; + + // Free shipping on selected carriers + if ($this->free_shipping) + { + if (!$this->carrier_restriction) + $reductionValue += $context->cart->getOrderShippingCost($context->cart->id_carrier, $useTax = true, $context->country); + elseif ($context->cart->id_carrier) + { + $id_cart_rule = (int)Db::getInstance()->getValue(' + SELECT crc.id_cart_rule + FROM '._DB_PREFIX_.'cart_rule_carrier crc + WHERE crc.id_cart_rule = '.(int)$this->id.' + AND crc.id_carrier = '.(int)$context->cart->id_carrier); + if ($id_cart_rule) + $reductionValue += $context->cart->getOrderShippingCost($context->cart->id_carrier, $useTax, $context->country); + } + } + + // Discount (%) on the whole order + if ($this->reduction_percent AND $this->reduction_product == 0) + { + $reductionValue += $context->cart->getOrderTotal($useTax, Cart::ONLY_PRODUCTS) * $this->reduction_percent / 100; + } + + // Discount (%) on a specific product + if ($this->reduction_percent AND $this->reduction_product > 0) + { + foreach ($context->cart->getProducts() as $product) + if ($product['id_product'] == $this->reduction_product) + $reductionValue += ($useTax ? $product['total_wt'] : $product['total']) * $this->reduction_percent / 100; + } + + // Discount (%) on the cheapest product + if ($this->reduction_percent AND $this->reduction_product == -1) + { + $minPrice = false; + foreach ($context->cart->getProducts() as $product) + { + $price = ($useTax ? $product['price_wt'] : $product['price']); + if ($price > 0 && ($minPrice === false || $minPrice > $price)) + $minPrice = $price; + $reductionValue += $minPrice * $this->reduction_percent / 100; + } + } + + // Discount (¤) + if ($this->reduction_amount) + { + $reduction_amount = $this->reduction_amount; + // If we need to convert the voucher value to the cart currency + if ($this->reduction_currency != $context->currency->id) + { + $voucherCurrency = new Currency($this->reduction_currency); + // First we convert the voucher value to the default currency + $reduction_amount /= $voucherCurrency->conversion_rate; + // Then we convert the voucher value in the default currency into the cart currency + $reduction_amount *= $context->currency->conversion_rate; + + $reduction_amount = Tools::ps_round($reduction_amount); + } + + // If it has the same tax application that you need, then it's the right value, whatever the product! + if ($this->reduction_tax == $useTax) + $reductionValue += $reduction_amount; + else + { + if ($this->reduction_product > 0) + { + // Todo: optimize with an array_search (and do the same in the other foreach of this function) + foreach ($context->cart->getProducts() as $product) + if ($product['id_product'] == $this->reduction_product) + { + $product_price_ti = $product['price_wt']; + $product_price_te = $product['price']; + $product_vat_amount = $product_price_ti - $product_price_te; + $product_vat_rate = $product_vat_amount / $product_price_te; + if ($this->reduction_tax && !$useTax) + $reductionValue += $reduction_amount / (1 + $product_vat_rate); + elseif (!$this->reduction_tax && $useTax) + $reductionValue += $reduction_amount * (1 + $product_vat_rate); + } + } + // Discount (¤) on the whole order + elseif ($this->reduction_product == 0) + { + $cart_amount_ti = $context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); + $cart_amount_te = $context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); + $cart_vat_amount = $cart_amount_ti - $cart_amount_te; + $cart_average_vat_rate = $cart_vat_amount / $cart_amount_te; + if ($this->reduction_tax && !$useTax) + $reductionValue += $reduction_amount / (1 + $cart_average_vat_rate); + elseif (!$this->reduction_tax && $useTax) + $reductionValue += $reduction_amount * (1 + $cart_average_vat_rate); + } + // Todo: discount on the cheapest (but this is not meaningful) + } + } + + // Free gift + if ($this->gift_product) + { + foreach ($context->cart->getProducts() as $product) + if ($product['id_product'] == $this->gift_product) + $reductionValue += ($useTax ? $product['price_wt'] : $product['price']); + } + + return $reductionValue; + } + + protected function getCartRuleCombinations() + { + $array = array(); + $array['selected'] = Db::getInstance()->ExecuteS(' + SELECT cr.*, crl.*, 1 as selected + FROM '._DB_PREFIX_.'cart_rule cr + LEFT JOIN '._DB_PREFIX_.'cart_rule_lang crl ON (cr.id_cart_rule = crl.id_cart_rule AND crl.id_lang = '.(int)Context::getContext()->language->id.') + WHERE cr.id_cart_rule != '.(int)$this->id.' + AND ( + cr.cart_rule_restriction = 0 + OR cr.id_cart_rule IN ( + SELECT IF(id_cart_rule_1 = '.(int)$this->id.', id_cart_rule_2, id_cart_rule_1) + FROM '._DB_PREFIX_.'cart_rule_combination + WHERE '.(int)$this->id.' = id_cart_rule_1 + OR '.(int)$this->id.' = id_cart_rule_2 + ) + )'); + $array['unselected'] = Db::getInstance()->ExecuteS(' + SELECT cr.*, crl.*, 1 as selected + FROM '._DB_PREFIX_.'cart_rule cr + LEFT JOIN '._DB_PREFIX_.'cart_rule_lang crl ON (cr.id_cart_rule = crl.id_cart_rule AND crl.id_lang = '.(int)Context::getContext()->language->id.') + WHERE cr.cart_rule_restriction = 1 + AND cr.id_cart_rule != '.(int)$this->id.' + AND cr.id_cart_rule NOT IN ( + SELECT IF(id_cart_rule_1 = '.(int)$this->id.', id_cart_rule_2, id_cart_rule_1) + FROM '._DB_PREFIX_.'cart_rule_combination + WHERE '.(int)$this->id.' = id_cart_rule_1 + OR '.(int)$this->id.' = id_cart_rule_2 + )'); + return $array; + } + + public function getAssociatedRestrictions($type, $active = 1) + { + $array = array('selected' => array(), 'unselected' => array()); + + if (!in_array($type, array('country', 'carrier', 'group', 'cart_rule'))) + return false; + + if (!Validate::isLoadedObject($this) OR $this->{$type.'_restriction'} == 0) + { + $array['selected'] = Db::getInstance()->ExecuteS(' + SELECT t.*, tl.*, 1 as selected + FROM '._DB_PREFIX_.''.$type.' t + LEFT JOIN '._DB_PREFIX_.''.$type.'_lang tl ON t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int)Context::getContext()->language->id.' + WHERE 1 + '.($active ? 'AND t.active = 1' : '').' + '.($type == 'cart_rule' ? 'AND t.id_cart_rule != '.(int)$this->id : '').' + ORDER BY name ASC'); + } + else + { + if ($type == 'cart_rule') + $array = $this->getCartRuleCombinations(); + else + { + $result = Db::getInstance()->ExecuteS(' + SELECT t.*, tl.*, IF(crt.id_'.$type.' IS NULL, 0, 1) as selected + FROM '._DB_PREFIX_.''.$type.' t + LEFT JOIN '._DB_PREFIX_.''.$type.'_lang tl ON t.id_'.$type.' = tl.id_'.$type.' AND tl.id_lang = '.(int)Context::getContext()->language->id.' + LEFT JOIN (SELECT id_'.$type.' FROM '._DB_PREFIX_.'cart_rule_'.$type.' WHERE id_cart_rule = '.(int)$this->id.') crt ON t.id_'.$type.' = crt.id_'.$type.' + '.($active ? 'WHERE t.active = 1' : '').' + ORDER BY name ASC', + false); + while ($row = Db::getInstance()->nextRow()) + $array[($row['selected'] || $this->{$type.'_restriction'} == 0) ? 'selected' : 'unselected'][] = $row; + } + } + return $array; + } + + public static function autoRemoveFromCart($context = NULL) + { + if (!CartRule::isFeatureActive()) + return; + + $errors = array(); + if (!$context) + $context = Context::getContext(); + $result = $context->cart->getCartRules(); + $cartRules = ObjectModel::hydrateCollection('CartRule', $result); + foreach ($cartRules as $cartRule) + { + if ($error = $cartRule->checkValidity($context, true)) + { + $context->cart->removeCartRule($cartRule->id); + $context->cart->update(); + $errors[] = $error; + } + } + return $errors; + } + + public static function autoAddToCart($context = NULL) + { + if ($context === NULL) + $context = Context::getContext(); + if (!CartRule::isFeatureActive() || !Validate::isLoadedObject($context->cart)) + return; + + $result = Db::getInstance()->ExecuteS(' + SELECT cr.* + FROM '._DB_PREFIX_.'cart_rule cr + LEFT JOIN '._DB_PREFIX_.'cart_rule_carrier crca ON cr.id_cart_rule = crca.id_cart_rule + LEFT JOIN '._DB_PREFIX_.'cart_rule_country crco ON cr.id_cart_rule = crco.id_cart_rule + WHERE cr.active = 1 + AND cr.code = "" + AND cr.quantity > 0 + AND cr.date_from < "'.date('Y-m-d H:i:s').'" + AND cr.date_to > "'.date('Y-m-d H:i:s').'" + AND cr.id_cart_rule NOT IN (SELECT id_cart_rule FROM '._DB_PREFIX_.'cart_cart_rule WHERE id_cart = '.(int)$context->cart->id.') + AND ( + cr.id_customer = 0 + '.($context->customer->id ? 'OR cr.id_customer = '.(int)$context->cart->id_customer : '').' + ) + AND ( + cr.carrier_restriction = 0 + '.($context->cart->id_carrier ? 'OR crca.id_carrier = '.(int)$context->cart->id_carrier : '').' + ) + AND ( + cr.group_restriction = 0 + '.($context->customer->id ? 'OR 0 < ( + SELECT cg.id_group + FROM '._DB_PREFIX_.'customer_group cg + LEFT JOIN '._DB_PREFIX_.'cart_rule_group crg ON cg.id_group = crg.id_group + WHERE cr.id_cart_rule = crg.id_cart_rule + AND cg.id_customer = '.(int)$context->customer->id.' + )' : '').' + ) + AND ( + cr.reduction_product = 0 + OR cr.reduction_product IN ( + SELECT id_product + FROM '._DB_PREFIX_.'cart_product + WHERE id_cart = '.(int)$context->cart->id.' + ) + ) + ORDER BY priority'); + + $cartRules = ObjectModel::hydrateCollection('CartRule', $result); + + // Todo: consider optimization (we can avoid many queries in checkValidity) + foreach ($cartRules as $cartRule) + if (!$cartRule->checkValidity($context)) + $context->cart->addCartRule($cartRule->id); + } + + public static function isFeatureActive() + { + return (bool)Configuration::get('PS_CART_RULE_FEATURE_ACTIVE'); + } +} \ No newline at end of file diff --git a/classes/Country.php b/classes/Country.php index 6e0a5beb8..bee4178ef 100644 --- a/classes/Country.php +++ b/classes/Country.php @@ -122,6 +122,13 @@ class CountryCore extends ObjectModel $this->validateFieldsLang(); return $this->getTranslationsFields(array('name')); } + + public function delete() + { + if (!parent::delete()) + return false; + return Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'cart_rule_country WHERE id_country = '.(int)$this->id); + } /** * Return available countries diff --git a/classes/Discount.php b/classes/Discount.php index 0dab84343..cb90e0d7e 100644 --- a/classes/Discount.php +++ b/classes/Discount.php @@ -25,7 +25,7 @@ * International Registered Trademark & Property of PrestaShop SA */ -class DiscountCore extends ObjectModel +class DiscountCore extends CartRule { public $id; @@ -310,14 +310,14 @@ class DiscountCore extends ObjectModel } /** - * Return discount value - * - * @param integer $nb_discounts Number of discount currently in cart - * @param boolean $order_total_products Total cart products amount - * @return mixed Return a float value or '!' if reduction is 'Shipping free' + * @deprecated 1.5.0.1 */ - public function getValue($nb_discounts = 0, $order_total_products = 0, $shipping_fees = 0, $idCart = false, $useTax = true, Currency $currency = null, Shop $shop = null) + public function getValue($nb_discounts = 0, $order_total_products = 0, $shipping_fees = 0, $id_cart = false, $useTax = true, Currency $currency = null, Shop $shop = null) { + Tools::displayAsDeprecated(); + $context = Context::getContext(); + $context->cart = new Cart($id_cart); + return parent::getValue($useTax, $context); if (!self::isFeatureActive()) return 0; @@ -509,7 +509,7 @@ class DiscountCore extends ObjectModel */ public static function getVouchersToCartDisplay($id_lang, $id_customer = 0) { - if (!self::isFeatureActive()) + if (!CartRule::isFeatureActive()) return array(); $sql = ' @@ -584,6 +584,7 @@ class DiscountCore extends ObjectModel */ public static function isFeatureActive() { - return Configuration::get('PS_DISCOUNT_FEATURE_ACTIVE'); + Tools::displayAsDeprecated(); + return CartRule::isFeatureActive(); } } \ No newline at end of file diff --git a/classes/FrontController.php b/classes/FrontController.php index 7537629ba..b79894441 100755 --- a/classes/FrontController.php +++ b/classes/FrontController.php @@ -134,6 +134,10 @@ class FrontControllerCore extends Controller if (isset($_GET['logout']) OR ($this->context->customer->logged AND Customer::isBanned($this->context->customer->id))) { $this->context->customer->logout(); + + // Login information have changed, so we check if the cart rules still apply + CartRule::autoRemoveFromCart(); + Tools::redirect(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : NULL); } elseif (isset($_GET['mylogout'])) @@ -285,7 +289,6 @@ class FrontControllerCore extends Controller 'customerName' => ($this->context->customer->logged ? $this->context->cookie->customer_firstname.' '.$this->context->cookie->customer_lastname : false) )); - // TODO for better performances (cache usage), remove these assign and use a smarty function to get the right media server in relation to the full ressource name $assignArray = array( 'img_ps_dir' => _PS_IMG_, 'img_cat_dir' => _THEME_CAT_DIR_, diff --git a/classes/Group.php b/classes/Group.php index 486b780af..aa8e7207c 100644 --- a/classes/Group.php +++ b/classes/Group.php @@ -166,6 +166,7 @@ class GroupCore extends ObjectModel return false; if (parent::delete()) { + Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_group` WHERE `id_group` = '.(int)$this->id); Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'customer_group` WHERE `id_group` = '.(int)$this->id); Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'category_group` WHERE `id_group` = '.(int)$this->id); Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'group_reduction` WHERE `id_group` = '.(int)$this->id); diff --git a/classes/Order.php b/classes/Order.php index 57f7d30b3..4f4ebfa2c 100644 --- a/classes/Order.php +++ b/classes/Order.php @@ -897,7 +897,13 @@ class OrderCore extends ObjectModel */ public function addDiscount($id_discount, $name, $value) { - return Db::getInstance()->AutoExecute(_DB_PREFIX_.'order_discount', array('id_order' => (int)($this->id), 'id_discount' => (int)($id_discount), 'name' => pSQL($name), 'value' => (float)($value)), 'INSERT'); + Tools::displayAsDeprecated(); + return Order::addCartRule($id_discount, $name, $value); + } + + public function addCartRule($id_cart_rule, $name, $value) + { + return Db::getInstance()->AutoExecute(_DB_PREFIX_.'order_cart_rule', array('id_order' => (int)$this->id, 'id_cart_rule' => (int)$id_cart_rule, 'name' => pSQL($name), 'value' => (float)$value), 'INSERT'); } public function getNumberOfDays() diff --git a/classes/OrderCartRule.php b/classes/OrderCartRule.php new file mode 100644 index 000000000..b2d0e3e5d --- /dev/null +++ b/classes/OrderCartRule.php @@ -0,0 +1,71 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision: 6844 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class OrderDiscountCore extends ObjectModel +{ + /** @var integer */ + public $id_order_cart_rule; + + /** @var integer */ + public $id_order; + + /** @var integer */ + public $id_cart_rule; + + /** @var string */ + public $name; + + /** @var integer */ + public $value; + + protected $tables = array ('order_cart_rule'); + + protected $fieldsRequired = array ('id_order', 'name', 'value'); + protected $fieldsValidate = array ('id_order' => 'isUnsignedId', 'name' => 'isGenericName', 'value' => 'isInt'); + + /* MySQL does not allow 'order detail' for a table name */ + protected $table = 'order_cart_rule'; + protected $identifier = 'id_order_cart_rule'; + + protected $webserviceParameters = array( + 'fields' => array( + 'id_order' => array('xlink_resource' => 'orders'), + ), + ); + + public function getFields() + { + $this->validateFields(); + + $fields['id_order'] = (int)($this->id_order); + $fields['name'] = pSQL($this->name); + $fields['value'] = (int)($this->value); + + return $fields; + } +} + diff --git a/classes/OrderDiscount.php b/classes/OrderDiscount.php index 2fd819e23..37fd8fc42 100644 --- a/classes/OrderDiscount.php +++ b/classes/OrderDiscount.php @@ -25,47 +25,23 @@ * International Registered Trademark & Property of PrestaShop SA */ -class OrderDiscountCore extends ObjectModel +class OrderDiscountCore extends OrderCartRule { - /** @var integer */ - public $id_order_discount; - - /** @var integer */ - public $id_order; - - /** @var integer */ - public $id_discount; - - /** @var string */ - public $name; - - /** @var integer */ - public $value; - - protected $tables = array ('order_discount'); - - protected $fieldsRequired = array ('id_order', 'name', 'value'); - protected $fieldsValidate = array ('id_order' => 'isUnsignedId', 'name' => 'isGenericName', 'value' => 'isInt'); - - /* MySQL does not allow 'order detail' for a table name */ - protected $table = 'order_discount'; - protected $identifier = 'id_order_discount'; - - protected $webserviceParameters = array( - 'fields' => array( - 'id_order' => array('xlink_resource' => 'orders'), - ), - ); - - public function getFields() + public function __get($key) { - $this->validateFields(); - - $fields['id_order'] = (int)($this->id_order); - $fields['name'] = pSQL($this->name); - $fields['value'] = (int)($this->value); - - return $fields; - } + if ($key == 'id_order_discount') + return $this->id_order_cart_rule; + if ($key == 'id_discount') + return $this->id_cart_rule; + return $this->{$key}; + } + + public function __set($key, $value) + { + if ($key == 'id_order_discount') + $this->id_order_cart_rule = $value; + if ($key == 'id_discount') + $this->id_cart_rule = $value; + $this->{$key} = $value; + } } - diff --git a/classes/OrderHistory.php b/classes/OrderHistory.php index 345d7dea7..d699c6bc0 100644 --- a/classes/OrderHistory.php +++ b/classes/OrderHistory.php @@ -89,7 +89,7 @@ class OrderHistoryCore extends ObjectModel if ($newOS->logable AND (!$oldOrderStatus OR !$oldOrderStatus->logable)) ProductSale::addProductSale($product['id_product'], $product['cart_quantity']); /* If becoming unlogable => removing sale */ - else if (!$newOS->logable AND ($oldOrderStatus AND $oldOrderStatus->logable)) + elseif (!$newOS->logable AND ($oldOrderStatus AND $oldOrderStatus->logable)) ProductSale::removeProductSale($product['id_product'], $product['cart_quantity']); if (!Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && !$isValidated AND $newOS->logable AND isset($oldOrderStatus) AND $oldOrderStatus AND $oldOrderStatus->id == Configuration::get('PS_OS_ERROR')) diff --git a/classes/PaymentModule.php b/classes/PaymentModule.php index 912f12de8..c8fbc840a 100644 --- a/classes/PaymentModule.php +++ b/classes/PaymentModule.php @@ -245,53 +245,42 @@ abstract class PaymentModuleCore extends Module '; } // end foreach ($products) - // Insert discounts from cart into order_discount table - $discounts = $cart->getDiscounts(); - $discountsList = ''; - $total_discount_value = 0; - $shrunk = false; - foreach ($discounts AS $discount) + $cartRulesList = ''; + $result = $cart->getCartRules(); + $cartRules = ObjectModel::hydrateCollection('CartRule', $result, (int)$order->id_lang); + foreach ($cartRules AS $cartRule) { - $objDiscount = new Discount((int)$discount['id_discount']); - $value = $objDiscount->getValue(sizeof($discounts), $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS), $order->total_shipping, $cart->id); - if ($objDiscount->id_discount_type == Discount::AMOUNT AND in_array($objDiscount->behavior_not_exhausted, array(1,2))) - $shrunk = true; + $value = $cartRule->getValue(true); + // Todo: repair shrunk + // if ($shrunk AND ($total_discount_value + $value) > ($order->total_products_wt + $order->total_shipping + $order->total_wrapping)) + // { + // $amount_to_add = ($order->total_products_wt + $order->total_shipping + $order->total_wrapping) - $total_discount_value; + // if ($cartRule->id_discount_type == Discount::AMOUNT AND $cartRule->behavior_not_exhausted == 2) + // { + // $voucher = new Discount(); + // foreach ($cartRule AS $key => $discountValue) + // $voucher->$key = $discountValue; + // $voucher->name = 'VSRK'.(int)$order->id_customer.'O'.(int)$order->id; + // $voucher->value = (float)$value - $amount_to_add; + // $voucher->add(); + // $params['{voucher_amount}'] = Tools::displayPrice($voucher->value, $currency, false); + // $params['{voucher_num}'] = $voucher->name; + // $params['{firstname}'] = $customer->firstname; + // $params['{lastname}'] = $customer->lastname; + // $params['{id_order}'] = $order->id; + // @Mail::Send((int)$order->id_lang, 'voucher', Mail::l('New voucher regarding your order #').$order->id, $params, $customer->email, $customer->firstname.' '.$customer->lastname); + // } + // } - if ($shrunk AND ($total_discount_value + $value) > ($order->total_products_wt + $order->total_shipping + $order->total_wrapping)) - { - $amount_to_add = ($order->total_products_wt + $order->total_shipping + $order->total_wrapping) - $total_discount_value; - if ($objDiscount->id_discount_type == Discount::AMOUNT AND $objDiscount->behavior_not_exhausted == 2) - { - $voucher = new Discount(); - foreach ($objDiscount AS $key => $discountValue) - $voucher->$key = $discountValue; - $voucher->name = 'VSRK'.(int)$order->id_customer.'O'.(int)$order->id; - $voucher->value = (float)$value - $amount_to_add; - $voucher->add(); - $params['{voucher_amount}'] = Tools::displayPrice($voucher->value, $currency, false); - $params['{voucher_num}'] = $voucher->name; - $params['{firstname}'] = $customer->firstname; - $params['{lastname}'] = $customer->lastname; - $params['{id_order}'] = $order->id; - @Mail::Send((int)$order->id_lang, 'voucher', Mail::l('New voucher regarding your order #').$order->id, $params, $customer->email, $customer->firstname.' '.$customer->lastname); - } - } - else - $amount_to_add = $value; - $order->addDiscount($objDiscount->id, $objDiscount->name, $amount_to_add); - $total_discount_value += $amount_to_add; + $order->addCartRule($cartRule->id, $cartRule->name, $value); if ($id_order_state != Configuration::get('PS_OS_ERROR') AND $id_order_state != Configuration::get('PS_OS_CANCELED')) - $objDiscount->quantity = $objDiscount->quantity - 1; - $objDiscount->update(); + $cartRule->quantity = $cartRule->quantity - 1; + $cartRule->update(); - $discountsList .= - ' - '. - $this->l('Voucher code:').' '.$objDiscount->name. - ' - '. - ($value != 0.00 ? '-' : '').Tools::displayPrice($value, $currency, false). - ' + $cartRulesList .= ' + + '.$this->l('Voucher name:').' '.$cartRule->name.' + '.($value != 0.00 ? '-' : '').Tools::displayPrice($value, $currency, false).' '; } @@ -384,7 +373,7 @@ abstract class PaymentModuleCore extends Module '{carrier}' => $carrier->name, '{payment}' => Tools::substr($order->payment, 0, 32), '{products}' => $productsList, - '{discounts}' => $discountsList, + '{discounts}' => $cartRulesList, '{total_paid}' => Tools::displayPrice($order->total_paid, $currency, false), '{total_products}' => Tools::displayPrice($order->total_paid - $order->total_shipping - $order->total_wrapping + $order->total_discounts, $currency, false), '{total_discounts}' => Tools::displayPrice($order->total_discounts, $currency, false), diff --git a/controllers/admin/AdminCartRulesController.php b/controllers/admin/AdminCartRulesController.php new file mode 100644 index 000000000..62f9b7ae0 --- /dev/null +++ b/controllers/admin/AdminCartRulesController.php @@ -0,0 +1,326 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision: 7060 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class AdminCartRulesControllerCore extends AdminController +{ + public function __construct() + { + $this->table = 'cart_rule'; + $this->className = 'CartRule'; + $this->lang = true; + $this->addRowAction('delete'); + $this->addRowAction('edit'); + $this->bulk_actions = array('delete' => array('text' => $this->l('Delete selected'), 'confirm' => $this->l('Delete selected items?'))); + + $this->fieldsDisplay = array( + 'id_cart_rule' => array('title' => $this->l('ID'), 'align' => 'center', 'width' => 25), + 'name' => array('title' => $this->l('Code')), + 'priority' => array('title' => $this->l('Priority')), + 'code' => array('title' => $this->l('Code')), + 'quantity' => array('title' => $this->l('Quantity')), + 'date_to' => array('title' => $this->l('Until')), + 'active' => array('title' => $this->l('Status'), 'align' => 'center', 'active' => 'status', 'type' => 'bool', 'orderby' => false), + ); + + parent::__construct(); + } + + public function postProcess() + { + if (Tools::isSubmit('submitAddcart_rule')) + { + // These are checkboxes (which aren't sent through POST when they are not check), so they are forced to 0 + foreach (array('country', 'carrier', 'group', 'cart_rule', 'product') as $type) + if (!Tools::getValue($type.'_restriction')) + $_POST[$type.'_restriction'] = 0; + + // Idiot-proof control + if (strtotime(Tools::getValue('date_from')) > strtotime(Tools::getValue('date_to'))) + $this->_errors[] = Tools::displayError('The voucher cannot end before it begins'); + if ((int)Tools::getValue('minimum_amount') < 0) + $this->_errors[] = Tools::displayError('Minimum amount cannot be lower than 0'); + if ((float)Tools::getValue('reduction_percent') < 0 || (float)Tools::getValue('reduction_percent') > 100) + $this->_errors[] = Tools::displayError('Reduction percent must range from 0% to 100%'); + if ((int)Tools::getValue('reduction_amount') < 0) + $this->_errors[] = Tools::displayError('Reduction amount cannot be lower than 0'); + } + + return parent::postProcess(); + } + + public function afterUpdate($currentObject) + { + // 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 (array('country', 'carrier', 'group', 'product_rule') as $type) + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_'.$type.'` WHERE `id_cart_rule` = '.(int)$id_cart_rule); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_product_rule_value` WHERE `id_product_rule` NOT IN (SELECT `id_product_rule` FROM `'._DB_PREFIX_.'cart_rule_product_rule`)'); + Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_rule_combination` WHERE `id_cart_rule_1` = '.(int)$id_cart_rule.' OR `id_cart_rule_2` = '.(int)$id_cart_rule); + + $this->afterAdd($currentObject); + } + + // TODO Move this function into CartRule + public function afterAdd($currentObject) + { + // Add restrictions for generic entities like country, carrier and group + foreach (array('country', 'carrier', 'group') as $type) + if (Tools::getValue($type.'_restriction') && is_array($array = Tools::getValue($type.'_select')) && count($array)) + { + $values = array(); + 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 = array(); + 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($array = Tools::getValue('product_rule')) && count($array)) + { + foreach ($array as $id) + { + Db::getInstance()->Execute('INSERT INTO `'._DB_PREFIX_.'cart_rule_product_rule` (`id_cart_rule`, `quantity`, `type`) + VALUES ('.(int)$currentObject->id.', '.(int)Tools::getValue('product_rule_'.$id.'_quantity').', "'.pSQL(Tools::getValue('product_rule_'.$id.'_type')).'")'); + $id_product_rule = Db::getInstance()->Insert_ID(); + + $values = array(); + foreach (Tools::getValue('product_rule_select_'.$id) as $id) + $values[] = '('.(int)$id_product_rule.','.(int)$id.')'; + 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 ($currentObject->cart_rule_restriction == 0) + { + 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 + )'); + } + // 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) + else + { + $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.id_cart_rule NOT IN ( + SELECT IF(id_cart_rule_1 = '.(int)$currentObject->id.', id_cart_rule_2, id_cart_rule_1) + FROM '._DB_PREFIX_.'cart_rule_combination + WHERE '.(int)$currentObject->id.' = id_cart_rule_1 + OR '.(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 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.' + )'); + } + } + } + + public function getProductRuleDisplay($product_rule_id, $product_rule_type, $product_rule_quantity = 1, $selected = array()) + { + Context::getContext()->smarty->assign( + array( + 'product_rule_id' => (int)$product_rule_id, + 'product_rule_type' => $product_rule_type, + 'product_rule_quantity' => (int)$product_rule_quantity + ) + ); + + switch ($product_rule_type) + { + case 'attributes': + $attributes = array('selected' => array(), 'unselected' => array()); + $result = 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', false); + while ($row = Db::getInstance()->nextRow($result)) + $attributes[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row; + Context::getContext()->smarty->assign('product_rule_itemlist', $attributes); + $choose_content = Context::getContext()->smarty->fetch('cart_rules/product_rule_itemlist.tpl'); + Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content); + break; + case 'products': + // Todo: Consider optimization + $products = array('selected' => array(), 'unselected' => array()); + $result = Db::getInstance()->ExecuteS(' + SELECT name, id_product as id + FROM '._DB_PREFIX_.'product_lang pl + WHERE id_lang = '.(int)Context::getContext()->language->id.' + ORDER BY name', false); + while ($row = Db::getInstance()->nextRow($result)) + $products[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row; + Context::getContext()->smarty->assign('product_rule_itemlist', $products); + $choose_content = Context::getContext()->smarty->fetch('cart_rules/product_rule_itemlist.tpl'); + Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content); + break; + case 'categories': + // Todo: Consider optimization (with code below) + $categories = array('selected' => array(), 'unselected' => array()); + $result = Db::getInstance()->ExecuteS(' + SELECT name, id_category as id + FROM '._DB_PREFIX_.'category_lang pl + WHERE id_lang = '.(int)Context::getContext()->language->id.' + ORDER BY name', false); + while ($row = Db::getInstance()->nextRow($result)) + $categories[in_array($row['id'], $selected) ? 'selected' : 'unselected'][] = $row; + Context::getContext()->smarty->assign('product_rule_itemlist', $categories); + $choose_content = Context::getContext()->smarty->fetch('cart_rules/product_rule_itemlist.tpl'); + Context::getContext()->smarty->assign('product_rule_choose_content', $choose_content); + // $translations = array( + // 'Home' => $this->l('Home'), + // 'selected' => $this->l('selected'), + // 'Collapse All' => $this->l('Collapse All'), + // 'Expand All' => $this->l('Expand All'), + // 'Check All' => $this->l('Check All'), + // 'Uncheck All' => $this->l('Uncheck All'), + // 'search' => $this->l('Search a category') + // ); + // Context::getContext()->smarty->assign('product_rule_choose_content', Helper::renderAdminCategorieTree($translations, $selected, 'categoryBox', false, true)); + break; + default: + die; + } + + return Context::getContext()->smarty->fetch('cart_rules/product_rule.tpl'); + } + + public function ajaxProcess() + { + if (Tools::isSubmit('newProductRule')) + die ($this->getProductRuleDisplay(Tools::getValue('product_rule_id'), Tools::getValue('product_rule_type'))); + + if (Tools::isSubmit('customerFilter')) + { + $q = trim(Tools::getValue('q')); + $customers = Db::getInstance()->ExecuteS(' + SELECT `id_customer`, `email`, CONCAT(`firstname`, \' \', `lastname`) as cname + FROM `'._DB_PREFIX_.'customer` + WHERE `deleted` = 0 AND is_guest = 0 AND active = 1 + AND ( + `id_customer` = '.(int)$q.' + OR `email` LIKE "%'.pSQL($q).'%" + OR `firstname` LIKE "%'.pSQL($q).'%" + OR `lastname` LIKE "%'.pSQL($q).'%" + ) + ORDER BY `firstname`, `lastname` ASC + LIMIT 50'); + die(Tools::jsonEncode($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(Tools::jsonEncode($products)); + } + } + + public function getProductRulesDisplay($cartRule) + { + $i = 1; + $productRulesArray = array(); + $productRules = $cartRule->getProductRules(); + foreach ($productRules as $productRule) + $productRulesArray[] = $this->getProductRuleDisplay($i++, $productRule['type'], $productRule['quantity'], $productRule['values']); + return $productRulesArray; + } + + public function initForm() + { + // Todo: change for "Media" version + $this->addJs(_PS_JS_DIR_.'jquery/plugins/fancybox/jquery.fancybox.js'); + $this->addJs(_PS_JS_DIR_.'jquery/plugins/autocomplete/jquery.autocomplete.js'); + $this->addCss(_PS_JS_DIR_.'jquery/plugins/fancybox/jquery.fancybox.css'); + $this->addCss(_PS_JS_DIR_.'jquery/plugins/autocomplete/jquery.autocomplete.css'); + + $currentObject = $this->loadObject(true); + + // All the filter are prefilled with the correct information + $customerFilter = ''; + if (Validate::isUnsignedId($currentObject->id_customer) AND $customer = new Customer($currentObject->id_customer) AND Validate::isLoadedObject($customer)) + $customerFilter = $customer->firstname.' '.$customer->lastname.' ('.$customer->email.')'; + $giftProductFilter = ''; + if (Validate::isUnsignedId($currentObject->gift_product) AND $product = new Product($currentObject->gift_product, false, Context::getContext()->language->id) AND Validate::isLoadedObject($product)) + $giftProductFilter = trim($product->reference.' '.$product->name); + $reductionProductFilter = ''; + if (Validate::isUnsignedId($currentObject->reduction_product) AND $product = new Product($currentObject->reduction_product, false, Context::getContext()->language->id) AND Validate::isLoadedObject($product)) + $reductionProductFilter = trim($product->reference.' '.$product->name); + + $product_rules = $this->getProductRulesDisplay($currentObject); + + Context::getContext()->smarty->assign( + array( + 'languages' => Language::getLanguages(), + 'defaultDateFrom' => date('Y-m-d H:00:00'), + 'defaultDateTo' => date('Y-m-d H:00:00', strtotime('+1 year')), + 'customerFilter' => $customerFilter, + 'giftProductFilter' => $giftProductFilter, + 'reductionProductFilter' => $reductionProductFilter, + 'defaultCurrency' => Configuration::get('PS_CURRENCY_DEFAULT'), + 'currencies' => Currency::getCurrencies(), + 'countries' => $currentObject->getAssociatedRestrictions('country', 1), + 'carriers' => $currentObject->getAssociatedRestrictions('carrier', 1), + 'groups' => $currentObject->getAssociatedRestrictions('group', 0), + 'cart_rules' => $currentObject->getAssociatedRestrictions('cart_rule', 1), + 'product_rules' => $product_rules, + 'product_rules_counter' => count($product_rules), + 'attribute_groups' => AttributeGroup::getAttributesGroups(Context::getContext()->language->id), + 'currentIndex' => self::$currentIndex, + 'currentToken' => $this->token, + 'currentObject' => $currentObject, + 'currentTab' => $this + ) + ); + + $this->content .= Context::getContext()->smarty->fetch('cart_rules/form.tpl'); + + // Todo: replace by the new "includeDatepicker" version + $ob_content = ob_get_contents(); + ob_clean(); + includeDatepicker(array('date_from', 'date_to'), true); + $this->content .= ob_get_contents(); + ob_clean(); + echo $ob_content; + + return parent::initForm(); + } +} \ No newline at end of file diff --git a/controllers/front/AuthController.php b/controllers/front/AuthController.php index be10d2e5a..57d413dc5 100644 --- a/controllers/front/AuthController.php +++ b/controllers/front/AuthController.php @@ -276,6 +276,10 @@ class AuthControllerCore extends FrontController $this->context->cart->id_address_invoice = Address::getFirstCustomerAddressId((int)($customer->id)); $this->context->cart->update(); Module::hookExec('authentication'); + + // Login information have changed, so we check if the cart rules still apply + CartRule::autoRemoveFromCart(); + if (!$this->ajax) { if ($back = Tools::getValue('back')) @@ -565,15 +569,15 @@ class AuthControllerCore extends FrontController protected function sendConfirmationMail(Customer $customer) { return Mail::Send( - $this->context->language->id, - 'account', + $this->context->language->id, + 'account', Mail::l('Welcome!'), array( - '{firstname}' => $customer->firstname, - '{lastname}' => $customer->lastname, - '{email}' => $customer->email, - '{passwd}' => Tools::getValue('passwd')), - $customer->email, + '{firstname}' => $customer->firstname, + '{lastname}' => $customer->lastname, + '{email}' => $customer->email, + '{passwd}' => Tools::getValue('passwd')), + $customer->email, $customer->firstname.' '.$customer->lastname ); } diff --git a/controllers/front/CartController.php b/controllers/front/CartController.php index 143696a69..fc42b7cf3 100644 --- a/controllers/front/CartController.php +++ b/controllers/front/CartController.php @@ -58,9 +58,6 @@ class CartControllerCore extends FrontController public function postProcess() { - // Check cart discounts - $this->processRemoveDiscounts(); - if ($this->isTokenValid()) $this->errors[] = Tools::displayError('Invalid token'); @@ -72,8 +69,6 @@ class CartControllerCore extends FrontController else if (Tools::getIsset('delete')) $this->processDeleteProductInCart(); - $this->processRemoveDiscounts(); - // Make redirection if (!$this->errors && !$this->ajax) { @@ -107,6 +102,7 @@ class CartControllerCore extends FrontController $this->context->cart->gift_message = ''; $this->context->cart->update(); } + CartRule::autoRemoveFromCart(); } /** @@ -147,29 +143,6 @@ class CartControllerCore extends FrontController else if (!$product->checkQty($this->qty)) $this->errors[] = Tools::displayError('There is not enough product in stock.'); - // Check vouchers compatibility - if ($mode == 'add' && (($product->specificPrice && (float)$product->specificPrice['reduction']) || $product->on_sale)) - { - $discounts = $this->context->cart->getDiscounts(); - $hasUndiscountedProduct = null; - foreach ($discounts as $discount) - { - if (is_null($hasUndiscountedProduct)) - { - $hasUndiscountedProduct = false; - foreach ($this->context->cart->getProducts() as $product) - if ($product['reduction_applies'] === false) - { - $hasUndiscountedProduct = true; - break; - } - } - if (!$discount['cumulable_reduction'] && ($discount['id_discount_type'] != Discount::PERCENT || !$hasUndiscountedProduct)) - $this->errors[] = Tools::displayError('Cannot add this product because current voucher does not allow additional discounts.'); - - } - } - // If no errors, process product addition if (!$this->errors && $mode == 'add') { @@ -198,6 +171,7 @@ class CartControllerCore extends FrontController $this->errors[] = Tools::displayError('You already have the maximum quantity available for this product.'); } } + CartRule::autoRemoveFromCart(); } /** @@ -205,18 +179,8 @@ class CartControllerCore extends FrontController */ protected function processRemoveDiscounts() { - $orderTotal = $this->context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); - $cartProducts = $this->context->cart->getProducts(); - foreach ($this->context->cart->getDiscounts() as $discount) - { - $discountObj = new Discount($discount['id_discount'], $this->context->language->id); - if ($error = $this->context->cart->checkDiscountValidity($discountObj, $discounts, $orderTotal, $cartProducts, false)) - { - $this->context->cart->deleteDiscount($discount['id_discount']); - $this->context->cart->update(); - $this->errors[] = $error; - } - } + Tools::displayAsDeprecated(); + $this->errors = array_merge($this->errors, CartRule::autoRemoveFromCart()); } /** diff --git a/controllers/front/ParentOrderController.php b/controllers/front/ParentOrderController.php index 60966096a..c6b35d512 100644 --- a/controllers/front/ParentOrderController.php +++ b/controllers/front/ParentOrderController.php @@ -88,38 +88,40 @@ class ParentOrderControllerCore extends FrontController if ($this->nbProducts) { - if (Tools::isSubmit('submitAddDiscount') && Tools::getValue('discount_name') && Discount::isFeatureActive()) + if (CartRule::isFeatureActive()) { - $discountName = Tools::getValue('discount_name'); - if (!Validate::isDiscountName($discountName)) - $this->errors[] = Tools::displayError('Voucher name invalid.'); - else + if (Tools::isSubmit('submitAddDiscount')) { - $discount = new Discount((int)(Discount::getIdByName($discountName))); - if (Validate::isLoadedObject($discount)) - { - if ($tmpError = $this->context->cart->checkDiscountValidity($discount, $this->context->cart->getDiscounts(), $this->context->cart->getOrderTotal(), $this->context->cart->getProducts(), true)) - $this->errors[] = $tmpError; - } + if (!($code = Tools::getValue('discount_name'))) + $this->errors[] = Tools::displayError('You must enter a voucher code'); + elseif (!Validate::isDiscountName($code)) + $this->errors[] = Tools::displayError('Voucher code invalid'); else - $this->errors[] = Tools::displayError('Voucher name invalid.'); - if (!count($this->errors)) { - $this->context->cart->addDiscount((int)($discount->id)); - Tools::redirect('index.php?controller=order-opc'); + if ($cartRule = new CartRule(CartRule::getIdByCode($code)) AND Validate::isLoadedObject($cartRule)) + { + if ($error = $cartRule->checkValidity($this->context)) + $this->errors[] = $error; + else + { + $this->context->cart->addCartRule($cartRule->id); + Tools::redirect('index.php?controller=order-opc'); + } + } + else + $this->errors[] = Tools::displayError('Voucher name invalid.'); } + $this->context->smarty->assign(array( + 'errors' => $this->errors, + 'discount_name' => Tools::safeOutput($code) + )); + } + elseif ($id_cart_rule = (int)Tools::getValue('deleteDiscount') AND Validate::isUnsignedId($id_cart_rule)) + { + $this->context->cart->removeCartRule($id_cart_rule); + Tools::redirect('index.php?controller=order-opc'); } - $this->context->smarty->assign(array( - 'errors' => $this->errors, - 'discount_name' => Tools::safeOutput($discountName) - )); } - else if (isset($_GET['deleteDiscount']) && Validate::isUnsignedId($_GET['deleteDiscount']) && Discount::isFeatureActive()) - { - $this->context->cart->deleteDiscount((int)($_GET['deleteDiscount'])); - Tools::redirect('index.php?controller=order-opc'); - } - /* Is there only virtual product in cart */ if ($isVirtualCart = $this->context->cart->isVirtualCart()) $this->setNoCarrier(); @@ -222,7 +224,11 @@ class ParentOrderControllerCore extends FrontController Module::hookExec('processCarrier', array('cart' => $this->context->cart)); - return $this->context->cart->update(); + if (!$this->context->cart->update()) + return false; + + // Carrier has changed, so we check if the cart rules still apply + CartRule::autoRemoveFromCart(); } protected function _assignSummaryInformations() @@ -249,7 +255,7 @@ class ParentOrderControllerCore extends FrontController if ($free_ship = Tools::convertPrice((float)(Configuration::get('PS_SHIPPING_FREE_PRICE')), new Currency($this->context->cart->id_currency))) { - $discounts = $this->context->cart->getDiscounts(); + $discounts = $this->context->cart->getCartRules(); $total_free_ship = $free_ship - ($summary['total_products_wt'] + $summary['total_discounts']); foreach ($discounts as $discount) if ($discount['id_discount_type'] == Discount::FREE_SHIPPING) @@ -338,7 +344,12 @@ class ParentOrderControllerCore extends FrontController } /* Update cart addresses only if needed */ if (isset($update) && $update) + { $this->context->cart->update(); + + // Address has changed, so we check if the cart rules still apply + CartRule::autoRemoveFromCart(); + } /* If delivery address is valid in cart, assign it to Smarty */ if (isset($this->context->cart->id_address_delivery)) diff --git a/img/admin/choose.gif b/img/admin/choose.gif new file mode 100644 index 000000000..e5df19aaf Binary files /dev/null and b/img/admin/choose.gif differ diff --git a/img/t/AdminCartRules.gif b/img/t/AdminCartRules.gif new file mode 100644 index 000000000..32390cd0f Binary files /dev/null and b/img/t/AdminCartRules.gif differ diff --git a/install-dev/sql/db.sql b/install-dev/sql/db.sql index e709c8adb..6370e3e00 100644 --- a/install-dev/sql/db.sql +++ b/install-dev/sql/db.sql @@ -187,11 +187,88 @@ CREATE TABLE `PREFIX_cart` ( KEY `id_shop` (`id_shop`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; -CREATE TABLE `PREFIX_cart_discount` ( +CREATE TABLE `PREFIX_cart_rule` ( + `id_cart_rule` int(10) unsigned NOT NULL auto_increment, + `id_customer` int unsigned NOT NULL default 0, + `date_from` datetime NOT NULL, + `date_to` datetime NOT NULL, + `description` text, + `quantity` int(10) unsigned NOT NULL default 0, + `quantity_per_user` int(10) unsigned NOT NULL default 0, + `priority` int(10) unsigned NOT NULL default 1, + `code` varchar(254) NOT NULL, + `minimum_amount` decimal(17,2) NOT NULL default 0, + `minimum_amount_tax` tinyint(1) NOT NULL default 0, + `minimum_amount_currency` int unsigned NOT NULL default 0, + `minimum_amount_shipping` tinyint(1) NOT NULL default 0, + `country_restriction` tinyint(1) unsigned NOT NULL default 0, + `carrier_restriction` tinyint(1) unsigned NOT NULL default 0, + `group_restriction` tinyint(1) unsigned NOT NULL default 0, + `cart_rule_restriction` tinyint(1) unsigned NOT NULL default 0, + `product_restriction` tinyint(1) unsigned NOT NULL default 0, + `free_shipping` tinyint(1) NOT NULL default 0, + `reduction_percent` decimal(4,2) NOT NULL default 0, + `reduction_amount` decimal(17,2) NOT NULL default 0, + `reduction_tax` tinyint(1) unsigned NOT NULL default 0, + `reduction_currency` int(10) unsigned NOT NULL default 0, + `reduction_product` int(10) NOT NULL default 0, + `gift_product` int(10) unsigned NOT NULL default 0, + `active` tinyint(1) unsigned NOT NULL default 0, + `date_add` datetime NOT NULL, + `date_upd` datetime NOT NULL, + PRIMARY KEY (`id_cart_rule`) +); + +CREATE TABLE `PREFIX_cart_rule_lang` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_lang` int(10) unsigned NOT NULL, + `name` varchar(254) NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_lang`) +); + +CREATE TABLE `PREFIX_cart_rule_country` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_country` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_country`) +); + +CREATE TABLE `PREFIX_cart_rule_group` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_group` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_group`) +); + +CREATE TABLE `PREFIX_cart_rule_carrier` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_carrier` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_carrier`) +); + +CREATE TABLE `PREFIX_cart_rule_combination` ( + `id_cart_rule_1` int(10) unsigned NOT NULL, + `id_cart_rule_2` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule_1`, `id_cart_rule_2`) +); + +CREATE TABLE `PREFIX_cart_rule_product_rule` ( + `id_product_rule` int(10) unsigned NOT NULL auto_increment, + `id_cart_rule` int(10) unsigned NOT NULL, + `quantity` int(10) unsigned NOT NULL default 1, + `type` ENUM('products', 'categories', 'attributes') NOT NULL, + PRIMARY KEY (`id_product_rule`) +); + +CREATE TABLE `PREFIX_cart_rule_product_rule_value` ( + `id_product_rule` int(10) unsigned NOT NULL, + `id_item` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_product_rule`, `id_item`) +); + +CREATE TABLE `PREFIX_cart_cart_rule` ( `id_cart` int(10) unsigned NOT NULL, - `id_discount` int(10) unsigned NOT NULL, - KEY `cart_discount_index` (`id_cart`,`id_discount`), - KEY `id_discount` (`id_discount`) + `id_cart_rule` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart`,`id_cart_rule`), + KEY (`id_cart_rule`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; CREATE TABLE `PREFIX_cart_product` ( @@ -1027,15 +1104,15 @@ CREATE TABLE `PREFIX_order_detail` ( KEY `id_order_id_order_detail` (`id_order`, `id_order_detail`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; -CREATE TABLE `PREFIX_order_discount` ( - `id_order_discount` int(10) unsigned NOT NULL auto_increment, +CREATE TABLE `PREFIX_order_cart_rule` ( + `id_order_cart_rule` int(10) unsigned NOT NULL auto_increment, `id_order` int(10) unsigned NOT NULL, - `id_discount` int(10) unsigned NOT NULL, + `id_cart_rule` int(10) unsigned NOT NULL, `name` varchar(32) NOT NULL, `value` decimal(17,2) NOT NULL default '0.00', - PRIMARY KEY (`id_order_discount`), - KEY `order_discount_order` (`id_order`), - KEY `id_discount` (`id_discount`) + PRIMARY KEY (`id_order_cart_rule`), + KEY `id_order` (`id_order`), + KEY `id_cart_rule` (`id_cart_rule`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; CREATE TABLE `PREFIX_order_history` ( diff --git a/install-dev/sql/db_settings_lite.sql b/install-dev/sql/db_settings_lite.sql index 6ff91da14..3c8e5ac36 100644 --- a/install-dev/sql/db_settings_lite.sql +++ b/install-dev/sql/db_settings_lite.sql @@ -230,7 +230,7 @@ INSERT INTO `PREFIX_configuration` (`id_configuration`, `name`, `value`, `date_a (142, 'PS_SCENE_FEATURE_ACTIVE', '1', NOW(), NOW()), (143, 'PS_VIRTUAL_PROD_FEATURE_ACTIVE', '0', NOW(), NOW()), (144, 'PS_CUSTOMIZATION_FEATURE_ACTIVE', '0', NOW(), NOW()), -(145, 'PS_DISCOUNT_FEATURE_ACTIVE', '0', NOW(), NOW()), +(145, 'PS_CART_RULE_FEATURE_ACTIVE', '0', NOW(), NOW()), (146, 'PS_GROUP_FEATURE_ACTIVE', '0', NOW(), NOW()), (147, 'PS_PACK_FEATURE_ACTIVE', '0', NOW(), NOW()), (148, 'PS_ALIAS_FEATURE_ACTIVE', '1', NOW(), NOW()), @@ -848,7 +848,7 @@ INSERT INTO `PREFIX_tab` (`id_tab`, `class_name`, `id_parent`, `position`) VALUE (9, 'AdminTools', 0, 10),(82, 'AdminStores', 9, 11),(60, 'AdminTracking', 1, 3),(10, 'AdminManufacturers', 1, 4),(34, 'AdminSuppliers', 1, 5),(11, 'AdminAttributesGroups', 1, 6), (36, 'AdminFeatures', 1, 7),(58, 'AdminScenes', 1, 8),(66, 'AdminTags', 1, 9),(68, 'AdminAttachments', 1, 10),(12, 'AdminAddresses', 2, 1),(63, 'AdminGroups', 2, 2), (65, 'AdminCarts', 2, 3),(42, 'AdminInvoices', 3, 1),(55, 'AdminDeliverySlip', 3, 2),(47, 'AdminReturn', 3, 3),(49, 'AdminSlip', 3, 4),(59, 'AdminMessages', 3, 5), -(13, 'AdminStatuses', 3, 6),(54, 'AdminOrderMessage', 3, 7),(14, 'AdminDiscounts', 4, 4),(15, 'AdminCurrencies', 4, 1),(16, 'AdminTaxes', 4, 2), +(13, 'AdminStatuses', 3, 6),(54, 'AdminOrderMessage', 3, 7),(14, 'AdminCartRules', 4, 4),(15, 'AdminCurrencies', 4, 1),(16, 'AdminTaxes', 4, 2), (17, 'AdminCarriers', 5, 1),(46, 'AdminStates', 5, 2),(18, 'AdminCountries', 5, 3),(19, 'AdminZones', 5, 5),(20, 'AdminRangePrice', 5, 6), (21, 'AdminRangeWeight', 5, 7),(51, 'AdminStatsConf', 6, 1),(61, 'AdminSearchEngines', 6, 2),(62, 'AdminReferrers', 6, 3), (22, 'AdminModulesPositions', 7, 4),(30, 'AdminProfiles', 29, 1),(31, 'AdminAccess', 29, 2),(28, 'AdminContacts', 29, 3),(39, 'AdminContact', 8, 1), @@ -874,7 +874,7 @@ INSERT INTO `PREFIX_access` (`id_profile`, `id_tab`, `view`, `add`, `edit`, `del INSERT INTO `PREFIX_tab_lang` (`id_lang`, `id_tab`, `name`) VALUES (1, 1, 'Catalog'),(1, 2, 'Customers'),(1, 3, 'Orders'),(1, 4, 'Payment'), (1, 5, 'Shipping'),(1, 6, 'Stats'),(1, 7, 'Modules'),(1, 8, 'Preferences'),(1, 9, 'Tools'),(1, 10, 'Manufacturers'),(1, 11, 'Attributes and Groups'), -(1, 12, 'Addresses'),(1, 13, 'Statuses'),(1, 14, 'Vouchers'),(1, 15, 'Currencies'),(1, 16, 'Taxes'),(1, 17, 'Carriers'),(1, 18, 'Countries'), +(1, 12, 'Addresses'),(1, 13, 'Statuses'),(1, 14, 'Cart Rules'),(1, 15, 'Currencies'),(1, 16, 'Taxes'),(1, 17, 'Carriers'),(1, 18, 'Countries'), (1, 19, 'Zones'),(1, 20, 'Price Ranges'),(1, 21, 'Weight Ranges'),(1, 22, 'Positions'),(1, 23, 'Database'),(1, 24, 'E-mail'),(1, 26, 'Images'), (1, 27, 'Products'),(1, 28, 'Contacts'),(1, 29, 'Employees'),(1, 30, 'Profiles'),(1, 31, 'Permissions'),(1, 32, 'Languages'),(1, 33, 'Translations'), (1, 34, 'Suppliers'),(1, 35, 'Tabs'),(1, 36, 'Features'),(1, 37, 'Quick Access'),(1, 38, 'Themes'),(1, 39, 'Contact Information'),(1, 40, 'Keyword Typos'), @@ -897,7 +897,7 @@ INSERT INTO `PREFIX_tab_lang` (`id_lang`, `id_tab`, `name`) VALUES INSERT INTO `PREFIX_tab_lang` (`id_lang`, `id_tab`, `name`) VALUES (2, 1, 'Catalogue'),(2, 2, 'Clients'),(2, 3, 'Commandes'),(2, 4, 'Paiement'),(2, 5, 'Transport'), (2, 6, 'Stats'),(2, 7, 'Modules'),(2, 8, 'Préférences'),(2, 9, 'Outils'),(2, 10, 'Marques'),(2, 11, 'Attributs et groupes'),(2, 12, 'Adresses'),(2, 13, 'Statuts'), -(2, 14, 'Bons de réduction'),(2, 15, 'Devises'),(2, 16, 'Taxes'),(2, 17, 'Transporteurs'),(2, 18, 'Pays'),(2, 19, 'Zones'),(2, 20, 'Tranches de prix'), +(2, 14, 'Règles paniers'),(2, 15, 'Devises'),(2, 16, 'Taxes'),(2, 17, 'Transporteurs'),(2, 18, 'Pays'),(2, 19, 'Zones'),(2, 20, 'Tranches de prix'), (2, 21, 'Tranches de poids'),(2, 22, 'Positions'),(2, 23, 'Base de données'),(2, 24, 'Emails'),(2, 26, 'Images'),(2, 27, 'Produits'),(2, 28, 'Contacts'), (2, 29, 'Employés'),(2, 30, 'Profils'),(2, 31, 'Permissions'),(2, 32, 'Langues'),(2, 33, 'Traductions'),(2, 34, 'Fournisseurs'),(2, 35, 'Onglets'), (2, 36, 'Caractéristiques'),(2, 37, 'Accès rapide'),(2, 38, 'Thèmes'),(2, 39, 'Coordonnées'),(2, 40, 'Alias'),(2, 41, 'Import'),(2, 42, 'Factures'), @@ -994,7 +994,7 @@ INSERT IGNORE INTO `PREFIX_tab_lang` (`id_tab`, `id_lang`, `name`) FROM `PREFIX_lang` CROSS JOIN `PREFIX_tab`); INSERT INTO `PREFIX_quick_access` (`id_quick_access`, `link`, `new_window`) VALUES -(1, 'index.php', 0),(2, '../', 1),(3, 'index.php?controller=AdminCatalog&addcategory', 0),(4, 'index.php?controller=AdminCatalog&addproduct', 0),(5, 'index.php?controller=AdminDiscounts&adddiscount', 0); +(1, 'index.php', 0),(2, '../', 1),(3, 'index.php?controller=AdminCatalog&addcategory', 0),(4, 'index.php?controller=AdminCatalog&addproduct', 0),(5, 'index.php?controller=AdminCartRules&addcart_rule', 0); INSERT INTO `PREFIX_quick_access_lang` (`id_quick_access`, `id_lang`, `name`) VALUES (1, 1, 'Home'),(1, 2, 'Accueil'),(1, 3, 'Inicio'),(1, 4, 'Start'),(1, 5, 'Home page'), diff --git a/install-dev/sql/upgrade/1.5.0.1.sql b/install-dev/sql/upgrade/1.5.0.1.sql index 7e78ee3d5..333b626ce 100644 --- a/install-dev/sql/upgrade/1.5.0.1.sql +++ b/install-dev/sql/upgrade/1.5.0.1.sql @@ -222,3 +222,97 @@ ALTER TABLE `PREFIX_carrier` ADD `position` INT( 10 ) UNSIGNED NOT NULL DEFAULT ALTER TABLE `PREFIX_order_state` ADD COLUMN `shipped` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `delivery`; UPDATE `PREFIX_order_state` SET `shipped` = 1 WHERE id_order_states IN (4, 5); + + +CREATE TABLE `PREFIX_cart_rule` ( + `id_cart_rule` int(10) unsigned NOT NULL auto_increment, + `id_customer` int unsigned NOT NULL default 0, + `date_from` datetime NOT NULL, + `date_to` datetime NOT NULL, + `description` text, + `quantity` int(10) unsigned NOT NULL default 0, + `quantity_per_user` int(10) unsigned NOT NULL default 0, + `priority` int(10) unsigned NOT NULL default 1, + `code` varchar(254) NOT NULL, + `minimum_amount` decimal(17,2) NOT NULL default 0, + `minimum_amount_tax` tinyint(1) NOT NULL default 0, + `minimum_amount_currency` int unsigned NOT NULL default 0, + `minimum_amount_shipping` tinyint(1) NOT NULL default 0, + `country_restriction` tinyint(1) unsigned NOT NULL default 0, + `carrier_restriction` tinyint(1) unsigned NOT NULL default 0, + `group_restriction` tinyint(1) unsigned NOT NULL default 0, + `cart_rule_restriction` tinyint(1) unsigned NOT NULL default 0, + `product_restriction` tinyint(1) unsigned NOT NULL default 0, + `free_shipping` tinyint(1) NOT NULL default 0, + `reduction_percent` decimal(4,2) NOT NULL default 0, + `reduction_amount` decimal(17,2) NOT NULL default 0, + `reduction_tax` tinyint(1) unsigned NOT NULL default 0, + `reduction_currency` int(10) unsigned NOT NULL default 0, + `reduction_product` int(10) NOT NULL default 0, + `gift_product` int(10) unsigned NOT NULL default 0, + `active` tinyint(1) unsigned NOT NULL default 0, + `date_add` datetime NOT NULL, + `date_upd` datetime NOT NULL, + PRIMARY KEY (`id_cart_rule`) +); + +CREATE TABLE `PREFIX_cart_rule_lang` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_lang` int(10) unsigned NOT NULL, + `name` varchar(254) NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_lang`) +); + +CREATE TABLE `PREFIX_cart_rule_country` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_country` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_country`) +); + +CREATE TABLE `PREFIX_cart_rule_group` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_group` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_group`) +); + +CREATE TABLE `PREFIX_cart_rule_carrier` ( + `id_cart_rule` int(10) unsigned NOT NULL, + `id_carrier` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule`, `id_carrier`) +); + +CREATE TABLE `PREFIX_cart_rule_combination` ( + `id_cart_rule_1` int(10) unsigned NOT NULL, + `id_cart_rule_2` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_cart_rule_1`, `id_cart_rule_2`) +); + +CREATE TABLE `PREFIX_cart_rule_product_rule` ( + `id_product_rule` int(10) unsigned NOT NULL auto_increment, + `id_cart_rule` int(10) unsigned NOT NULL, + `quantity` int(10) unsigned NOT NULL default 1, + `type` ENUM('products', 'categories', 'attributes') NOT NULL, + PRIMARY KEY (`id_product_rule`) +); + +CREATE TABLE `PREFIX_cart_rule_product_rule_value` ( + `id_product_rule` int(10) unsigned NOT NULL, + `id_item` int(10) unsigned NOT NULL, + PRIMARY KEY (`id_product_rule`, `id_item`) +); + +ALTER TABLE `PREFIX_cart_discount` CHANGE `id_discount` `id_cart_rule` int(10) unsigned NOT NULL; +ALTER TABLE `PREFIX_order_discount` CHANGE `id_discount` `id_cart_rule` int(10) unsigned NOT NULL; +ALTER TABLE `PREFIX_order_discount` CHANGE `id_order_discount` `id_order_cart_rule` int(10) unsigned NOT NULL; + +RENAME TABLE `PREFIX_order_discount` TO `PREFIX_order_cart_rule`; +RENAME TABLE `PREFIX_cart_discount` TO `PREFIX_cart_cart_rule`; + +CREATE VIEW `PREFIX_order_discount` AS SELECT *, id_cart_rule as id_discount FROM `PREFIX_order_cart_rule`; +CREATE VIEW `PREFIX_cart_discount` AS SELECT *, id_cart_rule as id_discount FROM `PREFIX_cart_cart_rule`; + +INSERT INTO `PREFIX_configuration` (`name`, `value`, `date_add`, `date_upd`) ( + SELECT 'PS_CART_RULE_FEATURE_ACTIVE', `value`, NOW(), NOW() FROM `PREFIX_configuration` WHERE `name` = 'PS_DISCOUNT_FEATURE_ACTIVE' LIMIT 1 +); + +UPDATE `PREFIX_tab` SET class_name = 'AdminCartRules' WHERE class_name = 'AdminDiscounts'; diff --git a/modules/blockcart/blockcart.php b/modules/blockcart/blockcart.php index bfdeb7058..04de4bd43 100644 --- a/modules/blockcart/blockcart.php +++ b/modules/blockcart/blockcart.php @@ -85,7 +85,7 @@ class BlockCart extends Module 'customizedDatas' => Product::getAllCustomizedDatas((int)($params['cart']->id)), 'CUSTOMIZE_FILE' => _CUSTOMIZE_FILE_, 'CUSTOMIZE_TEXTFIELD' => _CUSTOMIZE_TEXTFIELD_, - 'discounts' => $params['cart']->getDiscounts(false, Tools::isSubmit('id_product')), + 'discounts' => $params['cart']->getCartRules(false, Tools::isSubmit('id_product')), 'nb_total_products' => (int)($nbTotalProducts), 'shipping_cost' => Tools::displayPrice($params['cart']->getOrderTotal($useTax, Cart::ONLY_SHIPPING), $currency), 'show_wrapping' => $wrappingCost > 0 ? true : false, diff --git a/themes/prestashop/shopping-cart.tpl b/themes/prestashop/shopping-cart.tpl index 6d4b10df5..f1df89ccb 100644 --- a/themes/prestashop/shopping-cart.tpl +++ b/themes/prestashop/shopping-cart.tpl @@ -270,13 +270,18 @@ {foreach from=$discounts item=discount name=discountLoop} - {$discount.name} - {$discount.description} - {l s='Delete'} + {$discount.name} - {if $discount.value_real > 0} - {if !$priceDisplay}{displayPrice price=$discount.value_real*-1}{else}{displayPrice price=$discount.value_tax_exc*-1}{/if} - {/if} + {if !$priceDisplay}{displayPrice price=$discount.value_real*-1}{else}{displayPrice price=$discount.value_tax_exc*-1}{/if} + + + + {l s='Delete'} + + 1 + + + {if !$priceDisplay}{displayPrice price=$discount.value_real*-1}{else}{displayPrice price=$discount.value_tax_exc*-1}{/if} {/foreach}