diff --git a/admin-dev/ajax.php b/admin-dev/ajax.php index 80ccb5169..c7efeba15 100644 --- a/admin-dev/ajax.php +++ b/admin-dev/ajax.php @@ -358,7 +358,7 @@ if (isset($_GET['ajaxStates']) AND isset($_GET['id_country'])) LEFT JOIN '._DB_PREFIX_.'country c ON (s.`id_country` = c.`id_country`) WHERE s.id_country = '.(int)(Tools::getValue('id_country')).' AND s.active = 1 AND c.`contains_states` = 1 ORDER BY s.`name` ASC'); - + if (is_array($states) AND !empty($states)) { $list = ''; @@ -504,30 +504,6 @@ if (Tools::isSubmit('toggleScreencast')) $context->employee->save(); } -if (Tools::isSubmit('ajaxAddZipCode') OR Tools::isSubmit('ajaxRemoveZipCode')) -{ - require_once(PS_ADMIN_DIR.'/tabs/AdminCounty.php'); - - $zipcodes = Tools::getValue('zipcodes'); - $id_county = (int)Tools::getValue('id_county'); - - $county = new County($id_county); - if (!Validate::isLoadedObject($county)) - die('error'); - - if (Tools::isSubmit('ajaxAddZipCode')) - { - if ($county->isZipCodeRangePresent($zipcodes)) - die('error:'.Tools::displayError('This Zip Code is already in use.')); - if ($county->addZipCodes($zipcodes)) - die(AdminCounty::renderZipCodeList($county->getZipCodes())); - } - else if (Tools::isSubmit('ajaxRemoveZipCode') AND $county->removeZipCodes($zipcodes)) - die(AdminCounty::renderZipCodeList($county->getZipCodes())); - - die('error'); -} - if (Tools::isSubmit('helpAccess')) { $item = Tools::getValue('item'); @@ -562,14 +538,14 @@ if (Tools::isSubmit('getHookableList')) if ($moduleInstance->isHookableOn($hook_name)) array_push($hookableList[$hook_name], $module); } - + } die(Tools::jsonEncode($hookableList)); } if (Tools::isSubmit('getHookableModuleList')) { - + include('../init.php'); $hook_name = Tools::getValue('hook'); $hookableModulesList = array(); @@ -582,9 +558,9 @@ if (Tools::isSubmit('getHookableModuleList')) $mod = new $module['name'](); if ($mod->isHookableOn($hook_name)) $hookableModulesList[] = array('id' => (int)$mod->id, 'name' => $mod->displayName, 'display' => Module::hookExec($hook_name, array(), (int)$mod->id)); - } + } } - die(Tools::jsonEncode($hookableModulesList)); + die(Tools::jsonEncode($hookableModulesList)); } if (Tools::isSubmit('saveHook')) @@ -599,7 +575,7 @@ if (Tools::isSubmit('saveHook')) $hook = trim($hook); if (!$hook) continue; - + $sql = 'DELETE FROM '._DB_PREFIX_.'hook_module WHERE id_hook = (SELECT id_hook FROM '._DB_PREFIX_.'hook WHERE `name` = \''.pSQL($hook).'\' LIMIT 1) AND id_shop = '.$id_shop; @@ -615,7 +591,7 @@ if (Tools::isSubmit('saveHook')) } $value = rtrim($value, ','); Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'hook_module (id_module, id_shop, id_hook, position) VALUES '.$value); - + } } die('{"hasError" : false, "errors" : ""}'); @@ -624,18 +600,18 @@ if (Tools::isSubmit('saveHook')) if (Tools::isSubmit('getAdminHomeElement')) { $result = array(); - + $protocol = Tools::usingSecureMode() ? 'https' : 'http'; $isoUser = Context::getContext()->language->iso_code; $isoCountry = Context::getContext()->country->iso_code; $stream_context = stream_context_create(array('http' => array('method'=>"GET", 'timeout' => 5))); - + // SCREENCAST if (@fsockopen('www.prestashop.com', 80, $errno, $errst, 3)) $result['screencast'] = 'OK'; else $result['screencast'] = 'NOK'; - + // PREACTIVATION $content = @file_get_contents($protocol.'://www.prestashop.com/partner/preactivation/preactivation-block.php?version=1.0&shop='.urlencode(Configuration::get('PS_SHOP_NAME')).'&protocol='.$protocol.'&url='.urlencode($_SERVER['HTTP_HOST']).'&iso_country='.$isoCountry.'&iso_lang='.Tools::strtolower($isoUser).'&id_lang='.(int)Context::getContext()->language->id.'&email='.urlencode(Configuration::get('PS_SHOP_EMAIL')).'&date_creation='._PS_CREATION_DATE_.'&v='._PS_VERSION_.'&security='.md5(Configuration::get('PS_SHOP_EMAIL')._COOKIE_IV_), false, $stream_context); if (!$content) @@ -661,7 +637,7 @@ if (Tools::isSubmit('getAdminHomeElement')) else $result['partner_preactivation'] = 'NOK'; } - + // PREACTIVATION PAYPAL WARNING $content = @file_get_contents('https://www.prestashop.com/partner/preactivation/preactivation-warnings.php?version=1.0&partner=paypal&iso_country='.Tools::strtolower(Context::getContext()->country->iso_code).'&iso_lang='.Tools::strtolower(Context::getContext()->language->iso_code).'&id_lang='.(int)Context::getContext().'&email='.urlencode(Configuration::get('PS_SHOP_EMAIL')).'&security='.md5(Configuration::get('PS_SHOP_EMAIL')._COOKIE_IV_), false, $stream_context); $content = explode('|', $content); @@ -681,20 +657,20 @@ if (Tools::isSubmit('getAdminHomeElement')) $result['discover_prestashop'] = $content[1]; else $result['discover_prestashop'] = 'NOK'; - + if (@fsockopen('www.prestashop.com', 80, $errno, $errst, 3)) $result['discover_prestashop'] .= ''; - + $content = @file_get_contents($protocol.'://www.prestashop.com/partner/paypal/paypal-tips.php?protocol='.$protocol.'&iso_country='.$isoCountry.'&iso_lang='.Tools::strtolower($isoUser).'&id_lang='.(int)Context::getContext()->language->id, false, $stream_context); $content = explode('|', $content); if ($content[0] == 'OK') $result['discover_prestashop'] .= $content[1]; - } - + } + die(Tools::jsonEncode($result)); } -if (Tools::isSubmit('getChildrenCategories') && Tools::getValue('id_category_parent')) +if (Tools::isSubmit('getChildrenCategories') && Tools::getValue('id_category_parent')) { $children_categories = Category::getChildrenWithNbSelectedSubCat(Tools::getValue('id_category_parent'), Tools::getValue('selectedCat', array()), Context::getContext()->language->id); die(Tools::jsonEncode($children_categories)); diff --git a/admin-dev/tabs/AdminAttributeGenerator.php b/admin-dev/tabs/AdminAttributeGenerator.php index ea3b9cffd..d52164457 100644 --- a/admin-dev/tabs/AdminAttributeGenerator.php +++ b/admin-dev/tabs/AdminAttributeGenerator.php @@ -228,7 +228,7 @@ class AdminAttributeGenerator extends AdminTab i18n_tax_exc = "'.$this->l('Tax Excl.:').'"; i18n_tax_inc = "'.$this->l('Tax Incl.:').'"; - var product_tax = "'.Tax::getProductTaxRate($this->product->id, NULL).'"; + var product_tax = "'.$this->product->getTaxesRate().'"; function calcPrice(element, element_has_tax) { diff --git a/admin-dev/tabs/AdminProducts.php b/admin-dev/tabs/AdminProducts.php index af4395a95..590e8e23c 100644 --- a/admin-dev/tabs/AdminProducts.php +++ b/admin-dev/tabs/AdminProducts.php @@ -131,7 +131,7 @@ class AdminProducts extends AdminTab $nb = count($this->_list); if ($this->_list) { - + /* update product final price */ for ($i = 0; $i < $nb; $i++) @@ -1059,7 +1059,7 @@ class AdminProducts extends AdminTab else { $image = new Image($id_image); - + if (!$new_path = $image->getPathForCreation()) $this->_errors[] = Tools::displayError('An error occurred during new folder creation'); if (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS') OR !move_uploaded_file($_FILES['image_product']['tmp_name'], $tmpName)) @@ -1179,7 +1179,7 @@ class AdminProducts extends AdminTab $this->updateAccessories($object); $this->updateDownloadProduct($object); $this->updateAssoShop((int)$object->id); - + if (!$this->updatePackItems($object)) $this->_errors[] = Tools::displayError('An error occurred while adding products to the pack.'); elseif (!$object->updateCategories($_POST['categoryBox'], true)) @@ -1662,7 +1662,7 @@ class AdminProducts extends AdminTab $specificPrices = SpecificPrice::getByProductId((int)($obj->id)); $specificPricePriorities = SpecificPrice::getPriority((int)($obj->id)); - $taxRate = TaxRulesGroup::getTaxesRate($obj->id_tax_rules_group, $this->context->country->id, 0, 0); + $taxRate = $obj->getTaxesRate(Tax::initializeAddress()); $tmp = array(); foreach ($shops as $shop) @@ -2613,11 +2613,11 @@ class AdminProducts extends AdminTab
'; - - + + if ((int)Configuration::get('PS_STOCK_MANAGEMENT')) { - + if (!$has_attribute) { if ($obj->id) @@ -2675,7 +2675,7 @@ class AdminProducts extends AdminTab echo ' '.$this->l('The stock management is disabled').' '; - + echo '
@@ -2751,23 +2751,23 @@ class AdminProducts extends AdminTab else $selectedCat = Product::getProductCategoriesFull($obj->id, $this->_defaultFormLanguage); } - + echo ' - + '; // Translations are not automatic for the moment ;) $trads = 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'), + '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') ); echo Helper::renderAdminCategorieTree($trads, $selectedCat).' @@ -2840,7 +2840,7 @@ class AdminProducts extends AdminTab echo '
'; - echo '

'.($obj->id ? $this->youEditFieldFor() : '').'

+ echo '

'.($obj->id ? $this->youEditFieldFor() : '').'

@@ -2959,7 +2959,7 @@ class AdminProducts extends AdminTab unitPriceWithTax(\'unit\'); '; $categoryBox = Tools::getValue('categoryBox', array()); - + } function displayFormImages($obj, $token = NULL) @@ -3070,7 +3070,7 @@ class AdminProducts extends AdminTab '; foreach ($shops as $shop) echo ''.$shop['name'].''; - } + } echo ' '.$this->l('Cover').' '.$this->l('Action').' @@ -3111,7 +3111,7 @@ class AdminProducts extends AdminTab if (Shop::isMultiShopActivated()) foreach ($shops AS $shop) echo 'isAssociatedToShop($shop['id_shop']) ? 'checked="1"' : '').' />'; - echo ' + echo ' '.$this->l('Modify this image').' @@ -3172,7 +3172,7 @@ class AdminProducts extends AdminTab function displayFormAttributes($obj, $languages, $defaultLanguage) { - + $attributeJs = array(); $attributes = Attribute::getAttributes($this->context->language->id, true); foreach ($attributes AS $k => $attribute) @@ -3181,7 +3181,7 @@ class AdminProducts extends AdminTab $attributes_groups = AttributeGroup::getAttributesGroups($this->context->language->id); $default_country = new Country((int)Configuration::get('PS_COUNTRY_DEFAULT')); - + $images = Image::getImages($this->context->language->id, $obj->id); if ($obj->id) { @@ -3726,7 +3726,7 @@ class AdminProducts extends AdminTab } else if ($(\'#curPackItemId\').val() == \'\' || $(\'#curPackItemQty\').val() == \'\') { - alert(\''.$this->l('Thanks to set a quantity to add a product.').'\'); + alert(\''.$this->l('Thanks to set a quantity to add a product.').'\'); return false; } diff --git a/admin-dev/tabs/AdminTaxRulesGroup.php b/admin-dev/tabs/AdminTaxRulesGroup.php index 6342d81d5..33c874a47 100755 --- a/admin-dev/tabs/AdminTaxRulesGroup.php +++ b/admin-dev/tabs/AdminTaxRulesGroup.php @@ -25,543 +25,462 @@ * International Registered Trademark & Property of PrestaShop SA */ - -class AdminTaxRulesGroup extends AdminTab +class + AdminTaxRulesGroup extends AdminTab { + public $tax_rule; + public $_errors_tax_rule; + public function __construct() { + global $cookie; $this->table = 'tax_rules_group'; $this->className = 'TaxRulesGroup'; $this->edit = true; $this->delete = true; + $this->ajax = false; $this->fieldsDisplay = array( 'id_tax_rules_group' => array('title' => $this->l('ID'), 'align' => 'center', 'width' => 25), 'name' => array('title' => $this->l('Name'), 'width' => 140), 'active' => array('title' => $this->l('Enabled'), 'width' => 25, 'align' => 'center', 'active' => 'status', 'type' => 'bool', 'orderby' => false)); - parent::__construct(); + parent::__construct(); } - public function displayTop() + /** + * retrieve a tax rule via ajax + * @return tax rule in json format + */ + public function ajaxProcessGetTaxRule() { - echo '
- '.$this->l('The tax rules allow you to define a product or a carrier with different taxes depending on their location (country, state, etc.).').' + $id_rule = (int)Tools::getValue('id_tax_rule'); - '.$this->l('In the majority of cases, the rules created by default by PrestaShop should be enough.').' '. - $this->l('If, however, you need to change them, here is an example that will help you understand how it works:'). - '

' - .$this->l('You want to apply a tax of 19.6% to a product in France and Europe, but not apply this tax to other countries. Follow these steps:').' - -
- '.$this->l('Later, if you need to apply a different tax to Spain, you can simply edit the rule "19.6% tax rule" and change the tax associated with Spain.').'
- '.$this->l('Note: The default rate applied to your product will be based on your store\'s default country.').' -

'; + if ($tax_rule = TaxRule::retrieveById($id_rule)) + die(Tools::jsonEncode($tax_rule)); + else + die('error'); } - public function displayForm($isMainTab = true) + + public function ajaxPreProcess() {} + + public function postProcess() + { + $action = Tools::getValue('action'); + if ($action == 'delete_rule') + { + $id_rule = (int)Tools::getValue('id_tax_rule'); + $tax_rule = new TaxRule($id_rule); + + if (Validate::isLoadedObject($tax_rule)) + { + $tax_rule->delete(); + Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$tax_rule->id_tax_rules_group.'&conf=4&update'.$this->table.'&token='.$this->token); + } + } + else if ($action == 'create_rule') + { + $zipcode = Tools::getValue('zipcode'); + $id_rule = (int)Tools::getValue('id_tax_rule'); + + $tr = new TaxRule(); + + // update or creation? + if (isset($id_rule)) + $tr->id = $id_rule; + + $tr->id_tax = (int)Tools::getValue('tax'); + $tr->id_tax_rules_group = (int)Tools::getValue('id_tax_rules_group'); + $tr->id_country = (int)Tools::getValue('country'); + $tr->id_state = (int)Tools::getValue('states'); + list($tr->zipcode_from, $tr->zipcode_to) = $tr->breakDownZipCode($zipcode); + $tr->behavior = (int)Tools::getValue('behavior'); + $tr->description = Tools::getValue('description'); + + $this->_errors_tax_rule = $this->validateTaxRule($tr); + + if (sizeof($this->_errors_tax_rule) == 0) + { + if (!$tr->save()) + die('error'); + } + + $this->tax_rule = $tr; + Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$tr->id_tax_rules_group.'&conf=4&update'.$this->table.'&token='.$this->token); + } else + parent::postProcess(); + } + + /** + * check if the tax rule could be added in the database + * @param TaxRule $tr + */ + protected function validateTaxRule(TaxRule $tr) { - parent::displayForm(); + // TODO: check if the rule already exists + return $tr->validateController(); + } + + protected function displayJS() + { + global $cookie; + + $javascript = << + function populateStates(id_country, id_state) + { + $.ajax({ + url: "ajax.php", + cache: false, + data: "ajaxStates=1&id_country="+id_country+"&id_state="+id_state, + success: function(html){ + if (html == "false") + { + $("#state-label").hide(); + $("#state-select").hide(); + } + else + { + $("#state-label").show(); + $("#state-select").show(); + $("#states").html(html); + } + } + }); + } + + function loadTaxRule(id_tax_rule) + { + $.ajax({ + url: "ajax-tab.php", + cache: false, + dataType: "json", + data: "action=get_tax_rule&id_tax_rule="+id_tax_rule+"&tab=AdminTaxRulesGroup&token=$this->token", + success: function(data){ + $('#rule_form').show(); + $('#id_tax_rule').val(data.id_tax_rule); + $('#country').val(data.id_country); + $('#state').val(data.id_state); + + zipcode = 0; + if (data.zipcode_from != 0) + { + zipcode = data.zipcode_from; + + if (data.zipcode_to != 0) + zipcode = zipcode +"-"+data.zipcode_to + } + + $('#zipcode').val(zipcode); + $('#behavior').val(data.behavior); + $('#tax').val(data.id_tax); + $('#description').val(data.description); + + populateStates(data.id_country, data.id_state); + }, + error: function(data) + { + + } + }); + } + + function initForm() + { + $('#id_tax_rule').val(''); + $('#country').val(0); + $('#state').val(0); + $('#zipcode').val(0); + $('#behavior').val(0); + $('#tax').val(0); + $('#description').val(''); + + populateStates(0,0); + } + +EOT; + + echo $javascript; + } + + + public function displayTaxRulesErrors() + { + if ($nbErrors = count($this->_errors_tax_rule) AND $this->_includeContainer) + { + + echo ' +
X'; + if (count($this->_errors_tax_rule) == 1) + echo $this->_errors_tax_rule[0]; + else + { + echo $nbErrors.' '.$this->l('errors').'
    '; + foreach ($this->_errors_tax_rule AS $error) + echo '
  1. '.$error.'
  2. '; + echo '
'; + } + echo '
'; + } + } + + + public function display() + { + if ((Tools::getValue('submitAdd'.$this->table) AND sizeof($this->_errors_tax_rule)) OR isset($_GET['add'.$this->table])) + { + if ($this->tabAccess['add'] === '1') + $this->displayForm(); + else + echo $this->l('You do not have permission to add here'); + } + else parent::display(); + } + + /** + * displays the tax rules group form + */ + protected function displayRuleGroupForm() + { + global $cookie, $currentIndex; + parent::displayForm(); if (!($obj = $this->loadObject(true))) return; - $tax_rules = isset($obj->id) ? $tax_rules = TaxRule::getTaxRulesByGroupId($obj->id) : array(); - $param_product = Tools::getValue('id_product') ? '&id_product='.Tools::getValue('id_product') : ''; + // if the user come from the product page + $param_product = Tools::getValue('id_product') ? '&id_product='.Tools::getValue('id_product') : ''; - echo '
- '.($obj->id ? '' : '').' -
'.$this->l('Tax Rules').' - - '; + echo ' + '.($obj->id ? '' : '').' +
'.$this->l('Tax Rules').' + + '; - echo ' -
+ echo ' +
* '.$this->l('Invalid characters:').' <>;=#{}  -

-
'; +

+
'; - echo ' - + echo ' +
getFieldValue($obj, 'active') ? 'checked="checked" ' : '').'/> getFieldValue($obj, 'active') ? 'checked="checked" ' : '').'/> -
'; - if (Shop::isMultiShopActivated()) - { - echo '
'; - $this->displayAssoShop('group_shop'); - echo '
'; - } - echo ' -
-   +
+
-
'; - echo '
'; + + '; + } - echo '
- - - - '.$this->renderZones($tax_rules, $this->context->language->id); - echo ' -
-    - + +
+ + + +   +
'.$this->l('You can define a range (eg: 75000-75015) or a simple zipcode').'
+
+ + +
+ '.$behavior_select.' +
+ '.$this->l('Define the behavior if an address matches multiple rules:').'
+ '.$this->l('This Tax Only:').' '.$this->l('Will apply only this tax').'
+ '.$this->l('Combine:').' '.$this->l('Combine taxes (eg: 10% + 5% => 15%)').'
+ '.$this->l('One After Another:').' '.$this->l('Apply taxes one after another (eg: 100€ + 10% => 110€ + 5% => 115.5€)'). + '
+
+ + +
+ '.$tax_select.' '.$this->l('(Total tax:').'9%'.') +
+ + +
+   +
+ +
+ +
-
- '; - } + +

+ '; + } + + /** + * display the list of rules + * + * @param int $id_rule_group + */ + protected function displayRulesList($id_rule_group) + { + echo ' + + + + + + + + + + + + + '.$this->displayTaxRules((int)$id_rule_group).' + +
'.$this->l('Country').''.$this->l('State').''.$this->l('ZipCodes').''.$this->l('Behavior').''.$this->l('Tax').''.$this->l('Description').''.$this->l('Actions').'
'; + } - public function renderZones($tax_rules, $id_lang) - { - $html = ''; - $zones = Zone::getZones(true); - foreach ($zones AS $key => $zone) - { - $html .= '
-

'.$zone['name'].'

- - '.$this->renderCountries($tax_rules, $zone['id_zone'], $id_lang).' -
'; - } + /** + * display the tax rules list table body + * + * @param int $id_rule_group + */ + protected function displayTaxRules($id_rule_group) + { + global $currentIndex, $cookie; - return $html; - } + $html = ''; + $tax_rules = TaxRule::getTaxRulesByGroupId((int)$cookie->id_lang, (int)$id_rule_group); + if (sizeof($tax_rules) == 0) + { + $html .= ' + '.$this->l('No rules defined').' + '; + } else { + foreach ($tax_rules as $tax_rule) + { + // format fields for display + $state_name = ($tax_rule['state_name'] == '' ? '*' : $tax_rule['state_name']); - public function renderCountries($tax_rules, $id_zone, $id_lang) - { + $zipcodes = '*'; + if (isset($tax_rule['zipcode_from']) && $tax_rule['zipcode_from'] != 0) + { + $zipcodes = $tax_rule['zipcode_from']; + if (isset($tax_rule['zipcode_to']) && $tax_rule['zipcode_to'] != 0 && $tax_rule['zipcode_to'] != $tax_rule['zipcode_from']) + { + $zipcodes .= '-'.$tax_rule['zipcode_to']; + } + } - $html = ' - - - - - -
'.$this->l('All').''.$this->renderTaxesSelect($id_lang, '', array('id' => 'zone_'.(int)$id_zone)).'

'; + $tax = ((float)$tax_rule['rate'] == 0 ? '-' : (float)$tax_rule['rate'].'%'); - $html .= ' - - - - - - - - - - '; - $countries = Country::getCountriesByZoneId((int)$id_zone, (int)$id_lang); - $countCountries = sizeof($countries); - $i = 1; + $behavior = ($tax_rule['behavior'] == 0 ? $this->l('This tax only') : $this->l('Compute with others')); - foreach ($countries AS $country) - { - $id_tax = 0; + // render fields + $html .= ' + + + + + + + - - - - - '; - if ($country['contains_states']) { - $html .= $this->renderStates($tax_rules, (int)$id_zone, (int)$country['id_country'], (int)$id_lang); + + '; } - - $i++; - } - $html .= ' - -
'.$this->l('Country / State / County').''.$this->l('Tax to apply').'
'.Tools::htmlentitiesUTF8($tax_rule['country_name']).''.Tools::htmlentitiesUTF8($state_name).''.Tools::htmlentitiesUTF8($zipcodes).''.Tools::htmlentitiesUTF8($behavior).''.Tools::htmlentitiesUTF8($tax).''.Tools::htmlentitiesUTF8($tax_rule['description']).' + + '.$this->l('Edit').' + + + '.$this->l('Delete').' + - if (array_key_exists($country['id_country'], $tax_rules) AND array_key_exists(0, $tax_rules[$country['id_country']])) - $id_tax = (int)$tax_rules[$country['id_country']][0][0]['id_tax']; - - $html .= ' -
'.($country['contains_states'] ? '' : '').' - - '.$this->renderTaxesSelect($id_lang, $id_tax, array('class' => 'tax_'.$id_zone, 'id' => 'tax_'.$country['id_country'].'_0', 'name' => 'tax_'.$country['id_country'].'_0' )).'
- '; + } return $html; - } + } - public function renderStates($tax_rules, $id_zone, $id_country, $id_lang) - { - $states = State::getStatesByIdCountry((int)$id_country); - $countStates = sizeof($states); - $i = 1; - $html = ''; - foreach ($states AS $state) + /** + * @param boolean $firstCall + */ + public function displayForm($firstCall = true) + { + if (!($obj = $this->loadObject(true))) + return; + + parent::displayForm(); + $this->displayRuleGroupForm(); + $this->displayJS(); + + // display tax rules only if the group has already been created + if ($obj->id) { - $id_tax = 0; - $selected = PS_PRODUCT_TAX; - - if (array_key_exists($id_country, $tax_rules) - AND array_key_exists($state['id_state'], $tax_rules[$id_country]) - AND array_key_exists(0, $tax_rules[$id_country][$state['id_state']])) - { - $id_tax = (int)$tax_rules[$id_country][$state['id_state']][0]['id_tax']; - $selected = (int)$tax_rules[$id_country][$state['id_state']][0]['state_behavior']; - } - - $disable = (PS_PRODUCT_TAX == $selected ? 'disabled' : ''); - $html .= ' - - '.(State::hasCounties($state['id_state']) ? '' : '').' - - - '.$this->renderTaxesSelect($id_lang, $id_tax, array('class' => 'tax_'.$id_zone, - 'id' => 'tax_'.$id_country.'_'.$state['id_state'], - 'name' => 'tax_'.$id_country.'_'.$state['id_state'], - 'disabled' => $disable )).' -  - - - - '; - - if (State::hasCounties($state['id_state'])) - $html .= $this->renderCounties($tax_rules, $id_zone, $id_country, $state['id_state'], $id_lang); - - $i++; + echo '
'; + $this->displayTaxRulesErrors(); + $this->displayRuleForm($obj->id); + $this->displayRulesList($obj->id); } - - return $html; - } - - public function renderCounties($tax_rules, $id_zone, $id_country, $id_state, $id_lang) - { - $counties = County::getCounties((int)$id_state); - $countCounties = sizeof($counties); - $i = 1; - $html = ''; - foreach ($counties AS $county) - { - $id_tax = 0; - $selected = County::USE_STATE_TAX; - if (array_key_exists($id_country, $tax_rules) - AND array_key_exists($id_state, $tax_rules[$id_country]) - AND array_key_exists($county['id_county'], $tax_rules[$id_country][$id_state])) - { - $id_tax = (int)$tax_rules[$id_country][$id_state][$county['id_county']]['id_tax']; - $selected = (int)$tax_rules[$id_country][$id_state][$county['id_county']]['county_behavior']; - } - - $disable = (County::USE_STATE_TAX == $selected ? 'disabled' : ''); - $html .= ' - - - - - '.$this->renderTaxesSelect($id_lang, $id_tax, array('class' => 'tax_'.$id_zone, - 'id' => 'tax_'.$id_country.'_'.$id_state.'_'.$county['id_county'], - 'name' => 'tax_'.$id_country.'_'.$id_state.'_'.$county['id_county'], - 'disabled' => $disable )).' -  - - - - '; - } - - return $html; - } - - public function renderTaxesSelect($id_lang, $default_value, $html_options) - { - $opt = ''; - foreach( array('id', 'class', 'name', 'disabled') AS $prop) - if (array_key_exists($prop, $html_options) && !empty($html_options[$prop])) - $opt .= $prop.'="'.$html_options[$prop].'"'; - - $html = ''; - - return $html; - } - - protected function afterAdd($object) - { - $this->afterUpdate($object); - } - - - protected function afterUpdate($object) - { - - TaxRule::deleteByGroupId($object->id); - - foreach(Country::getCountries($this->context->language->id, true) AS $country) - { - $id_tax = (int)Tools::getValue('tax_'.$country['id_country'].'_0'); - - // default country rule - if (!empty($id_tax)) - { - $tr = new TaxRule(); - $tr->id_tax_rules_group = $object->id; - $tr->id_country = (int)$country['id_country']; - $tr->id_state = 0; - $tr->id_county = 0; - $tr->id_tax = $id_tax; - $tr->state_behavior = 0; - $tr->county_behavior = 0; - $tr->save(); - } - - // state specific rule - if (!empty($country['contains_states'])) - { - foreach ($country['states'] AS $state) - { - $state_behavior = (int)Tools::getValue('behavior_state_'.$state['id_state']); - if ($state_behavior != PS_PRODUCT_TAX) - { - $tr = new TaxRule(); - $tr->id_tax_rules_group = $object->id; - $tr->id_country = (int)$country['id_country']; - $tr->id_state = (int)$state['id_state']; - $tr->id_county = 0; - $tr->id_tax = (int)Tools::getValue('tax_'.$country['id_country'].'_'.$state['id_state']); - $tr->state_behavior = $state_behavior; - $tr->county_behavior = 0; - $tr->save(); - } - - // county specific rule - if (State::hasCounties($state['id_state'])) - { - $counties = County::getCounties($state['id_state']); - foreach ($counties AS $county) - { - $county_behavior = (int)Tools::getValue('behavior_county_'.$county['id_county']); - - if ($county_behavior != County::USE_STATE_TAX) - { - $tr = new TaxRule(); - $tr->id_tax_rules_group = $object->id; - $tr->id_country = (int)$country['id_country']; - $tr->id_state = (int)$state['id_state']; - $tr->id_county = (int)$county['id_county']; - $tr->id_tax = (int)Tools::getValue('tax_'.$country['id_country'].'_'.$state['id_state'].'_'.$county['id_county']); - $tr->state_behavior = 0; - $tr->county_behavior = $county_behavior; - $tr->save(); - } - } - } - } - } - - } - } - - - public function postProcess() - { - - if (!isset($this->table)) - return false; - - // set token - $token = Tools::getValue('token') ? Tools::getValue('token') : $this->token; - - if (Tools::getValue('submitAdd'.$this->table)) - { - $id_product = Tools::getValue('id_product'); - - /* 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)) - { - // Create new one with old objet values - $objectNew = new $this->className($object->id); - $objectNew->id = NULL; - $objectNew->date_add = ''; - $objectNew->date_upd = ''; - - // Update old object to deleted - $object->deleted = 1; - $object->update(); - - // Update new object with post values - $this->copyFromPost($objectNew, $this->table); - $result = $objectNew->add(); - if (Validate::isLoadedObject($objectNew)) - $this->afterDelete($objectNew, $object->id); - } - else - { - $this->copyFromPost($object, $this->table); - $result = $object->update(); - $this->afterUpdate($object); - } - - if (!$result) - $this->_errors[] = Tools::displayError('An error occurred while updating object.').' '.$this->table.' ('.Db::getInstance()->getMsgError().')'; - elseif ($this->postImage($object->id) AND !sizeof($this->_errors)) - { - $parent_id = (int)(Tools::getValue('id_parent', 1)); - - // Save and stay on same form - if (Tools::isSubmit('submitAdd'.$this->table.'AndStay')) - Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=4&update'.$this->table.'&token='.$token); - - // Default behavior (save and back) - $id_product = (int)Tools::getValue('id_product'); - if ($id_product) - Tools::redirectAdmin('?tab=AdminCatalog&id_product='.$id_product.'&updateproduct&token='.Tools::getAdminToken('AdminCatalog'.(int)(Tab::getIdFromClassName('AdminCatalog')).(int)$this->context->employee->id)); - - Tools::redirectAdmin(self::$currentIndex.($parent_id ? '&'.$this->identifier.'='.$object->id : '').'&conf=3&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); - if (!$object->add()) - $this->_errors[] = Tools::displayError('An error occurred while creating object.').' '.$this->table.' ('.Db::getInstance()->getMsgError().')'; - elseif (($_POST[$this->identifier] = $object->id /* voluntary */) AND $this->postImage($object->id) AND !sizeof($this->_errors) AND $this->_redirect) - { - $parent_id = (int)(Tools::getValue('id_parent', 1)); - $this->afterAdd($object); - // Save and stay on same form - if (Tools::isSubmit('submitAdd'.$this->table.'AndStay')) - Tools::redirectAdmin(self::$currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=3&update'.$this->table.'&token='.$token); - - $id_product = (int)Tools::getValue('id_product'); - if ($id_product) - Tools::redirectAdmin('?tab=AdminCatalog&id_product='.$id_product.'&updateproduct&token='.Tools::getAdminToken('AdminCatalog'.(int)(Tab::getIdFromClassName('AdminCatalog')).(int)$this->context->employee->id)); - - Tools::redirectAdmin(self::$currentIndex.($parent_id ? '&'.$this->identifier.'='.$object->id : '').'&conf=3&token='.$token); - // Default behavior (save and back) - Tools::redirectAdmin(self::$currentIndex.($parent_id ? '&'.$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); - } - parent::postProcess(); - } + } } diff --git a/admin-dev/tabs/AdminTracking.php b/admin-dev/tabs/AdminTracking.php index 4e9eb4a23..f0a8a7f47 100644 --- a/admin-dev/tabs/AdminTracking.php +++ b/admin-dev/tabs/AdminTracking.php @@ -156,9 +156,9 @@ class AdminTracking extends AdminTab '; foreach ($this->_list['obj'] AS $k => $prod) { - $product = new Product((int)($prod['id_product'])); + $product = new Product((int)$prod['id_product'], false); $product->name = $product->name[(int)$this->context->language->id]; - $taxrate = Tax::getProductTaxRate($product->id); + $taxrate = $product->getTaxesRate(); echo ' @@ -222,13 +222,15 @@ class AdminTracking extends AdminTab else $prod['combination_name'] = $prod['group_name'].' : '.$prod['attribute_name']; + $attributes[$prod['id_product_attribute']] = $prod; $prevAttributeId = $prod['id_product_attribute']; } foreach ($attributes AS $prod) { - $taxrate = Tax::getProductTaxRate($prod['id_product']); + $product = new Product((int)$prod['id_product'], false); + $tax_rate = $product->getTaxesRate(); echo ' diff --git a/classes/Autoload.php b/classes/Autoload.php index 46175918e..a788e2e5e 100644 --- a/classes/Autoload.php +++ b/classes/Autoload.php @@ -77,6 +77,8 @@ class Autoload */ public function load($classname) { + // echo("Please load $classname.
"); + // regenerate the class index if the requested class is not found in the index or if the requested file doesn't exists if (!isset($this->index[$classname]) || ($this->index[$classname] && !file_exists($this->root_dir.$this->index[$classname]))) $this->generateIndex(); @@ -87,6 +89,7 @@ class Autoload // If requested class does not exist, load associated core class if (isset($this->index[$classname]) && !$this->index[$classname]) { + require_once($this->root_dir.$this->index[$classname.'Core']); if (file_exists($this->root_dir.'override/'.$this->index[$classname.'Core'])) { @@ -104,10 +107,14 @@ class Autoload { // request a non Core Class load the associated Core class if exists if (isset($this->index[$classname.'Core'])) + { require_once($this->root_dir.$this->index[$classname.'Core']); + } if (isset($this->index[$classname])) + { require_once($this->root_dir.$this->index[$classname]); + } } } // Call directly ProductCore, ShopCore class diff --git a/classes/Carrier.php b/classes/Carrier.php index 27625a4da..62364880e 100644 --- a/classes/Carrier.php +++ b/classes/Carrier.php @@ -70,7 +70,7 @@ class CarrierCore extends ObjectModel /** @var boolean Free carrier */ public $is_free = false; - + /** @var int shipping behavior: by weight or by price */ public $shipping_method = 0; @@ -82,7 +82,7 @@ class CarrierCore extends ObjectModel /** @var boolean Need Range */ public $need_range = 0; - + protected $langMultiShop = true; protected $fieldsRequired = array('name', 'active'); @@ -488,7 +488,7 @@ class CarrierCore extends ObjectModel } } } - + $row['name'] = (strval($row['name']) != '0' ? $row['name'] : Configuration::get('PS_SHOP_NAME')); $row['price'] = ($shippingMethod == Carrier::SHIPPING_METHOD_FREE ? 0 : $cart->getOrderShippingCost((int)$row['id_carrier'])); $row['price_tax_exc'] = ($shippingMethod == Carrier::SHIPPING_METHOD_FREE ? 0 : $cart->getOrderShippingCost((int)$row['id_carrier'], false)); @@ -590,7 +590,7 @@ class CarrierCore extends ObjectModel $where .= 'AND id_shop IS NULL AND id_group_shop = '.$shopGroupID; else $where .= 'AND id_shop = '.$shopID; - + return Db::getInstance()->delete(_DB_PREFIX_.'delivery', $where); } @@ -610,7 +610,7 @@ class CarrierCore extends ObjectModel $keys[] = 'id_shop'; if (!in_array('id_group_shop', $keys)) $keys[] = 'id_group_shop'; - + if (!$shop) $shop = Context::getContext()->shop; $shopID = $shop->getID(); @@ -623,7 +623,7 @@ class CarrierCore extends ObjectModel $values['id_shop'] = ($shopID) ? $shopID : null; if (!isset($values['id_group_shop'])) $values['id_group_shop'] = ($shopGroupID) ? $shopGroupID : null; - + $sql .= '('; foreach ($values as $v) { @@ -650,7 +650,7 @@ class CarrierCore extends ObjectModel { if (!Validate::isUnsignedId($oldId)) die(Tools::displayError()); - + if (!$this->id) return false; @@ -670,7 +670,7 @@ class CarrierCore extends ObjectModel VALUES ('.$this->id.','.(float)$val['delimiter1'].','.(float)$val['delimiter2'].')'; Db::getInstance()->Execute($sql); $rangeID = (int)Db::getInstance()->Insert_ID(); - + $rangePriceID = ($range == 'range_price') ? $rangeID : 'NULL'; $rangeWeightID = ($range == 'range_weight') ? $rangeID : 'NULL'; $sql = 'INSERT INTO '._DB_PREFIX_.$range.' (id_carrier, id_shop, id_group_shop, id_range_price, id_range_weight, id_zone, price) @@ -769,10 +769,24 @@ class CarrierCore extends ObjectModel return self::$_cache_tax_rule[$id_carrier]; } - + + + /** + * Return the taxes rate associated to the carrier + * + * @since 1.5 + * @param Address $address + */ + public function getTaxesRate(Address $address) + { + $tax_manager = TaxManagerFactory::getManager($address, $this->id_tax_rules_group); + $tax_calculator = $tax_manager->getTaxCalculator(); + return $tax_calculator->getTaxesRate(); + } + /** * This tricky method generate a sql clause to check if ranged data are overloaded by multishop - * + * * @since 1.5.0 * @param string $rangeTable * @param Shop $shop @@ -791,7 +805,7 @@ class CarrierCore extends ObjectModel $where = 'AND ((d2.id_group_shop IS NULL OR d2.id_group_shop = '.$shopGroupID.') AND d2.id_shop IS NULL)'; else $where = 'AND (d2.id_shop = '.$shopID.' OR (d2.id_group_shop = '.$shopGroupID.' AND d2.id_shop IS NULL) OR (d2.id_group_shop IS NULL AND d2.id_shop IS NULL))'; - + $sql = 'AND '.$alias.'.id_delivery = ( SELECT d2.id_delivery FROM '._DB_PREFIX_.'delivery d2 diff --git a/classes/Cart.php b/classes/Cart.php index 145f821d8..5e5ce22c1 100644 --- a/classes/Cart.php +++ b/classes/Cart.php @@ -28,11 +28,11 @@ class CartCore extends ObjectModel { public $id; - + public $id_group_shop; - + public $id_shop; - + /** @var integer Customer delivery address ID */ public $id_address_delivery; @@ -75,7 +75,7 @@ class CartCore extends ObjectModel public $checkedTos = false; public $pictures; public $textFields; - + protected static $_nbProducts = array(); protected static $_isVirtualCart = array(); @@ -129,7 +129,7 @@ class CartCore extends ObjectModel $fields['id_group_shop'] = (int)$this->id_group_shop; $fields['id_shop'] = (int)$this->id_shop; - + $fields['id_address_delivery'] = (int)($this->id_address_delivery); $fields['id_address_invoice'] = (int)($this->id_address_invoice); $fields['id_currency'] = (int)($this->id_currency); @@ -187,35 +187,35 @@ class CartCore extends ObjectModel { if ($this->OrderExists()) //NOT delete a cart which is associated with an order return false; - + $uploadedFiles = Db::getInstance()->ExecuteS(' - SELECT cd.`value` + SELECT cd.`value` FROM `'._DB_PREFIX_.'customized_data` cd INNER JOIN `'._DB_PREFIX_.'customization` c ON (cd.`id_customization`= c.`id_customization`) WHERE cd.`type`= 0 AND c.`id_cart`='.(int)$this->id); - + foreach ($uploadedFiles as $mustUnlink) { unlink(_PS_UPLOAD_DIR_.$mustUnlink['value'].'_small'); unlink(_PS_UPLOAD_DIR_.$mustUnlink['value']); } - + Db::getInstance()->Execute(' - DELETE FROM `'._DB_PREFIX_.'customized_data` + DELETE FROM `'._DB_PREFIX_.'customized_data` WHERE `id_customization` IN ( - SELECT `id_customization` - FROM `'._DB_PREFIX_.'customization` + SELECT `id_customization` + FROM `'._DB_PREFIX_.'customization` WHERE `id_cart`='.(int)$this->id.' )'); - + Db::getInstance()->Execute(' - DELETE FROM `'._DB_PREFIX_.'customization` + 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_discount` WHERE `id_cart` = '.(int)($this->id)) OR !Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)($this->id))) return false; - + return parent::delete(); } @@ -344,7 +344,7 @@ class CartCore extends ObjectModel if (is_int($id_product)) { foreach ($this->_products as $product) - if ($product['id_product'] == $id_product) + if ($product['id_product'] == $id_product) return array($product); return array(); } @@ -352,7 +352,7 @@ class CartCore extends ObjectModel } if (!$id_country) $id_country = Context::getContext()->country->id; - + $sql = 'SELECT cp.`id_product_attribute`, cp.`id_product`, cu.`id_customization`, cp.`quantity` AS cart_quantity, cp.id_shop, cu.`quantity` AS customization_quantity, pl.`name`, pl.`description_short`, pl.`available_now`, pl.`available_later`, p.`id_product`, p.`id_category_default`, p.`id_supplier`, p.`id_manufacturer`, p.`on_sale`, p.`ecotax`, p.`additional_shipping_cost`, p.`available_for_order`, p.`price`, p.`weight`, p.`width`, p.`height`, p.`depth`, p.`out_of_stock`, p.`active`, p.`date_add`, p.`date_upd`, IFNULL(pa.`minimal_quantity`, p.`minimal_quantity`) as minimal_quantity, @@ -369,7 +369,8 @@ class CartCore extends ObjectModel LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (pa.`id_product_attribute` = cp.`id_product_attribute`) LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group` AND tr.`id_country` = '.(int)$id_country.' - AND tr.`id_state` = 0) + AND tr.`id_state` = 0 + AND tr.`zipcode_from` = 0) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)$this->id_lang.') LEFT JOIN `'._DB_PREFIX_.'customization` cu ON (p.`id_product` = cu.`id_product`) @@ -381,6 +382,7 @@ class CartCore extends ObjectModel AND p.`id_product` IS NOT NULL GROUP BY unique_id ORDER BY cp.date_add ASC'; + $result = Db::getInstance()->ExecuteS($sql); // Reset the cache before the following return, or else an empty cart will add dozens of queries @@ -414,7 +416,7 @@ class CartCore extends ObjectModel $row['price'] = Product::getPriceStatic((int)$row['id_product'], false, isset($row['id_product_attribute']) ? (int)($row['id_product_attribute']) : NULL, 2, NULL, false, true, (int)($row['cart_quantity']), false, ((int)($this->id_customer) ? (int)($this->id_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')}) ? (int)($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')}) : NULL), $specificPriceOutput); // Here taxes are computed only once the quantity has been applied to the product price $row['price_wt'] = Product::getPriceStatic((int)$row['id_product'], true, isset($row['id_product_attribute']) ? (int)($row['id_product_attribute']) : NULL, 2, NULL, false, true, (int)($row['cart_quantity']), false, ((int)($this->id_customer) ? (int)($this->id_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')}) ? (int)($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')}) : NULL)); $tax_rate = Tax::getProductTaxRate((int)$row['id_product'], (int)($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); - + $row['total_wt'] = Tools::ps_round($row['price'] * (float)$row['cart_quantity'] * (1 + (float)($tax_rate) / 100), 2); $row['total'] = $row['price'] * (int)($row['cart_quantity']); } @@ -428,7 +430,7 @@ class CartCore extends ObjectModel $row['total_wt'] = $row['price_wt'] * (int)($row['cart_quantity']); $row['total'] = Tools::ps_round($row['price'] * (int)($row['cart_quantity']), 2); } - + $row2 = Db::getInstance()->getRow(' SELECT i.`id_image`, il.`legend` FROM `'._DB_PREFIX_.'image` i @@ -447,7 +449,7 @@ class CartCore extends ObjectModel if (!$row2) $row2 = array('id_image' => false, 'legend' => false); $row = array_merge($row, $row2); - + $row['reduction_applies'] = ($specificPriceOutput AND (float)$specificPriceOutput['reduction']); $row['id_image'] = Product::defineProductImage($row,$this->id_lang); $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']); @@ -513,8 +515,8 @@ class CartCore extends ObjectModel if (isset(self::$_nbProducts[$id]) && self::$_nbProducts[$id] !== NULL) return self::$_nbProducts[$id]; self::$_nbProducts[$id] = (int)(Db::getInstance()->getValue(' - SELECT SUM(`quantity`) - FROM `'._DB_PREFIX_.'cart_product` + SELECT SUM(`quantity`) + FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)($id))); return self::$_nbProducts[$id]; } @@ -579,7 +581,7 @@ class CartCore extends ObjectModel $result = $this->containsProduct($id_product, $id_product_attribute, (int)$id_customization); /* Update quantity if product already exist */ - + if ($result) { if ($operator == 'up') @@ -652,7 +654,7 @@ class CartCore extends ObjectModel // refresh cache of self::_products $this->_products = $this->getProducts(true); $this->update(true); - + if ($product->customizable) return $this->_updateCustomizationQuantity((int)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, $operator); else @@ -673,15 +675,15 @@ class CartCore extends ObjectModel if ($field['quantity'] == 0) { Db::getInstance()->Execute(' - UPDATE `'._DB_PREFIX_.'customization` - SET `quantity` = '.(int)($quantity).', + UPDATE `'._DB_PREFIX_.'customization` + SET `quantity` = '.(int)($quantity).', `id_product_attribute` = '.(int)$id_product_attribute.', `in_cart` = 1 WHERE `id_customization` = '.(int)$field['id_customization']); } } } - + /* Deletion */ if (!empty($id_customization) AND (int)($quantity) < 1) return $this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute); @@ -694,7 +696,7 @@ class CartCore extends ObjectModel if ($operator == 'down' AND (int)($result['quantity']) - (int)($quantity) < 1) return Db::getInstance()->Execute('DELETE FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization); return Db::getInstance()->Execute(' - UPDATE `'._DB_PREFIX_.'customization` + UPDATE `'._DB_PREFIX_.'customization` SET `quantity` = `quantity` '.($operator == 'up' ? '+ ' : '- ').(int)($quantity).' WHERE `id_customization` = '.(int)($id_customization)); } @@ -726,15 +728,15 @@ class CartCore extends ObjectModel AND cu.id_product = '.(int)$id_product.' AND in_cart = 0'); - if ($exising_customization) + if ($exising_customization) { - // If the customization field is alreay filled, delete it + // If the customization field is alreay filled, delete it foreach($exising_customization as $customization) { if ($customization['type'] == $type && $customization['index'] == $index) { Db::getInstance()->Execute(' - DELETE FROM `'._DB_PREFIX_.'customized_data` + DELETE FROM `'._DB_PREFIX_.'customized_data` WHERE id_customization = '.(int)$customization['id_customization'].' AND type = '.(int)$customization['type'].' AND `index` = '.(int)$customization['index']); @@ -750,7 +752,7 @@ class CartCore extends ObjectModel }else { Db::getInstance()->Execute('INSERT INTO `'._DB_PREFIX_.'customization` (`id_cart`, `id_product`, `id_product_attribute`, `quantity`) VALUES ('.(int)($this->id).', '.(int)($id_product).', '.(int)($id_product_attribute).', '.(int)($quantity).')'); - $id_customization = Db::getInstance()->Insert_ID(); + $id_customization = Db::getInstance()->Insert_ID(); } $query = 'INSERT INTO `'._DB_PREFIX_.'customized_data` (`id_customization`, `type`, `index`, `value`) VALUES ('.(int)$id_customization.', '.(int)$type.', '.(int)$index.', \''.pSql($field).'\')'; @@ -922,7 +924,7 @@ class CartCore extends ObjectModel // no shipping cost if is a cart with only virtuals products $virtual = $this->isVirtualCart(); - if ($virtual AND $type == Cart::ONLY_SHIPPING) + if ($virtual AND $type == Cart::ONLY_SHIPPING) return 0; if ($virtual AND $type == Cart::BOTH) $type = Cart::BOTH_WITHOUT_SHIPPING; @@ -1042,7 +1044,7 @@ class CartCore extends ObjectModel if (!$default_country) $default_country = Context::getContext()->country; - + // Checking discounts in cart $products = $this->getProducts(); $discounts = $this->getDiscounts(true); @@ -1087,7 +1089,7 @@ class CartCore extends ObjectModel if (!Validate::isLoadedObject($default_country)) $default_country = new Country(Configuration::get('PS_COUNTRY_DEFAULT'), Configuration::get('PS_LANG_DEFAULT')); $id_zone = (int)$default_country->id_zone; - } + } // If no carrier, select default one if (!$id_carrier) @@ -1173,14 +1175,14 @@ class CartCore extends ObjectModel die(Tools::displayError('Fatal error: "no default carrier"')); if (!$carrier->active) return $shipping_cost; - + // Free fees if free carrier if ($carrier->is_free == 1) - return 0; - + return 0; + // Select carrier tax if ($useTax AND !Tax::excludeTaxeOption()) - $carrierTax = Tax::getCarrierTaxRate((int)$carrier->id, (int)$this->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); + $carrierTax = $carrier->getTaxesRate(new Address((int)$this->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); $configuration = Configuration::getMultiple(array('PS_SHIPPING_FREE_PRICE', 'PS_SHIPPING_HANDLING', 'PS_SHIPPING_METHOD', 'PS_SHIPPING_FREE_WEIGHT')); // Free fees @@ -1238,7 +1240,7 @@ class CartCore extends ObjectModel { $moduleName = $carrier->external_module_name; $module = Module::getInstanceByName($moduleName); - + if (Validate::isLoadedObject($module)) { if (array_key_exists('id_carrier', $module)) @@ -1295,13 +1297,13 @@ class CartCore extends ObjectModel * * @return mixed Return a string if an error occurred and false otherwise */ - function checkDiscountValidity($discountObj, $discounts, $order_total, $products, $checkCartDiscount = false, + function checkDiscountValidity($discountObj, $discounts, $order_total, $products, $checkCartDiscount = false, Customer $customer = null, Shop $shop = null) { if (!$shop) $shop = Context::getContext()->shop; if (!$customer) - $customer = Context::getContext()->customer; + $customer = Context::getContext()->customer; if (!$order_total) return Tools::displayError('Cannot add voucher if order is free.'); if (!$discountObj->active) @@ -1331,12 +1333,12 @@ class CartCore extends ObjectModel 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))) @@ -1391,7 +1393,7 @@ class CartCore extends ObjectModel // New layout system with personalization fields $formattedAddresses['invoice'] = AddressFormat::getFormattedLayoutData($invoice); $formattedAddresses['delivery'] = AddressFormat::getFormattedLayoutData($delivery); - + $total_tax = $this->getOrderTotal() - $this->getOrderTotal(false); if ($total_tax < 0) @@ -1513,7 +1515,7 @@ class CartCore extends ObjectModel $textValue = str_replace(array("\n", "\r"), '', nl2br($textValue)); $textValue = str_replace('\\', '\\\\', $textValue); $textValue = str_replace('\'', '\\\'', $textValue); - return $this->_addCustomization($id_product, 0, $index, $type, $textValue, 0); + return $this->_addCustomization($id_product, 0, $index, $type, $textValue, 0); } /* @@ -1523,9 +1525,9 @@ class CartCore extends ObjectModel */ public function addPictureToProduct($id_product, $index, $type, $file) { - return $this->_addCustomization($id_product, 0, $index, $type, $file, 0); + return $this->_addCustomization($id_product, 0, $index, $type, $file, 0); } - + /* * Remove a customer's customization * @@ -1534,7 +1536,7 @@ class CartCore extends ObjectModel public function deleteCustomizationToProduct($id_product, $index) { $result = true; - + $custData = Db::getInstance()->getRow(' SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu LEFT JOIN `'._DB_PREFIX_.'customized_data` cd @@ -1544,7 +1546,7 @@ class CartCore extends ObjectModel AND `index` = '.(int)$index.' AND `in_cart` = 0' ); - + // Delete customization picture if necessary if ($custData['type'] == 0) $result &= (@unlink(_PS_UPLOAD_DIR_.$custData['value']) && @unlink(_PS_UPLOAD_DIR_.$custData['value'].'_small')); @@ -1556,7 +1558,7 @@ class CartCore extends ObjectModel ); return $result; } - + /** * Return custom pictures in this cart for a specified product * @@ -1603,7 +1605,7 @@ class CartCore extends ObjectModel $cart->id = NULL; $cart->id_shop = $this->id_shop; $cart->id_group_shop = $this->id_group_shop; - + $cart->add(); if (!Validate::isLoadedObject($cart)) @@ -1620,7 +1622,7 @@ class CartCore extends ObjectModel FROM '._DB_PREFIX_.'customization c LEFT JOIN '._DB_PREFIX_.'customized_data cd ON cd.id_customization = c.id_customization WHERE c.id_cart = '.(int)$this->id); - + // Group line by id_customization $customsById = array(); foreach ($customs AS $custom) @@ -1629,7 +1631,7 @@ class CartCore extends ObjectModel $customsById[$custom['id_customization']] = array(); $customsById[$custom['id_customization']][] = $custom; } - + // Insert new customizations $custom_ids = array(); foreach($customsById as $customizationId => $val) @@ -1639,7 +1641,7 @@ class CartCore extends ObjectModel VALUES(\'\', '.(int)$cart->id.', '.(int)$custom['id_product_attribute'].', '.(int)$custom['id_product'].', '.(int)$custom['quantity'].')'); $custom_ids[$custom['id_customization']] = Db::getInstance(_PS_USE_SQL_SLAVE_)->Insert_ID(); } - + // Insert customized_data if (sizeof($customs)) { @@ -1655,7 +1657,7 @@ class CartCore extends ObjectModel } Db::getInstance(_PS_USE_SQL_SLAVE_)->Execute($sql_custom_data); } - + return array('cart' => $cart, 'success' => $success); } diff --git a/classes/Category.php b/classes/Category.php index db50e67ae..91de31564 100644 --- a/classes/Category.php +++ b/classes/Category.php @@ -548,7 +548,8 @@ class CategoryCore extends ObjectModel LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.') LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group` AND tr.`id_country` = '.(int)$context->country->id.' - AND tr.`id_state` = 0) + AND tr.`id_state` = 0 + AND tr.`zipcode_from` = 0) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)$id_lang.') LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` diff --git a/classes/County.php b/classes/County.php index 1f0045b66..d4449ba8a 100644 --- a/classes/County.php +++ b/classes/County.php @@ -25,6 +25,10 @@ * International Registered Trademark & Property of PrestaShop SA */ + +/** +* @deprecated since 1.5 +*/ class CountyCore extends ObjectModel { public $id; @@ -63,181 +67,97 @@ class CountyCore extends ObjectModel public function delete() { - $id = $this->id; - parent::delete(); - - // remove associated zip codes & tax rule - return (County::deleteZipCodeByIdCounty($id) AND TaxRule::deleteTaxRuleByIdCounty($id)); + return true; } + /** + * @deprecated since 1.5 + */ public static function getCounties($id_state) { - if (!isset(self::$_cache_get_counties[$id_state])) - { - self::$_cache_get_counties[$id_state] = Db::getInstance()->ExecuteS(' - SELECT * FROM `'._DB_PREFIX_.'county` - WHERE `id_state` = '.(int)$id_state - ); - } - - return self::$_cache_get_counties[$id_state]; + Tools::displayAsDeprecated(); + return false; } - // return the list of associated zipcode + /** + * @deprecated since 1.5 + */ public function getZipCodes() { - return Db::getInstance()->ExecuteS(' - SELECT * FROM `'._DB_PREFIX_.'county_zip_code` - WHERE `id_county` = '.(int)$this->id.' - ORDER BY `from_zip_code` ASC' - ); + Tools::displayAsDeprecated(); + return false; } + /** + * @deprecated since 1.5 + */ public function addZipCodes($zip_codes) { - list($from, $to) = $this->breakDownZipCode($zip_codes); - - if ($from == 0) - return false; - - return Db::getInstance()->Execute( - 'INSERT INTO `'._DB_PREFIX_.'county_zip_code` (`id_county`, `from_zip_code`, `to_zip_code`) - VALUES ('.(int)$this->id.','.(int)$from.','.(int)$to.')' - ); + Tools::displayAsDeprecated(); + return true; } - + /** + * @deprecated since 1.5 + */ public function removeZipCodes($zip_codes) { - list($from, $to) = $this->breakDownZipCode($zip_codes); - - if ($from == 0) - return false; - - return Db::getInstance()->Execute(' - DELETE FROM `'._DB_PREFIX_.'county_zip_code` - WHERE `id_county` = '.(int)$this->id.' - AND `from_zip_code` = '.(int)$from.' - AND `to_zip_code` = '.(int)$to - ); + Tools::displayAsDeprecated(); + return true; } - + /** + * @deprecated since 1.5 + */ public function breakDownZipCode($zip_codes) { - $zip_codes = preg_split('/-/', $zip_codes); - - if (sizeof($zip_codes) == 2) - { - $from = $zip_codes[0]; - $to = $zip_codes[1]; - if ($zip_codes[0] > $zip_codes[1]) - { - $from = $zip_codes[1]; - $to = $zip_codes[0]; - } - else if ($zip_codes[0] == $zip_codes[1]) - { - $from = $zip_codes[0]; - $to = 0; - } - } - else if (sizeof($zip_codes) == 1) - { - $from = $zip_codes[0]; - $to = 0; - } - - if (!Validate::isInt($from) OR !Validate::isInt($to)) - { - $from = 0; - $to = 0; - } - - return array($from, $to); + Tools::displayAsDeprecated(); + return array(0,0); } + /** + * @deprecated since 1.5 + */ public static function getIdCountyByZipCode($id_state, $zip_code) { - if (!isset(self::$_cache_county_zipcode[$id_state.'-'.$zip_code])) - { - self::$_cache_county_zipcode[$id_state.'-'.$zip_code] = Db::getInstance()->getValue(' - SELECT DISTINCT c.`id_county` FROM `'._DB_PREFIX_.'county` c - LEFT JOIN `'._DB_PREFIX_.'county_zip_code` cz ON (c.`id_county` = cz.`id_county`) - WHERE `id_state` = '.(int)$id_state.' - AND cz.`from_zip_code` >= '.(int)$zip_code.' - AND cz.`to_zip_code` <= '.(int)$zip_code - ); - } - - return self::$_cache_county_zipcode[$id_state.'-'.$zip_code]; + Tools::displayAsDeprecated(); + return false; } + /** + * @deprecated since 1.5 + */ public function isZipCodeRangePresent($zip_codes) { - $res = false; - list($from, $to) = $this->breakDownZipCode($zip_codes); - - if ($from == 0) - return false; - - if ($to != 0) - { - $res = Db::getInstance()->getValue(' - SELECT COUNT(*) FROM `'._DB_PREFIX_.'county_zip_code` cz - LEFT JOIN `'._DB_PREFIX_.'county` c ON (c.`id_county` = cz.`id_county`) - LEFT JOIN `'._DB_PREFIX_.'state` s ON (s.`id_state` = c.`id_state`) - WHERE `from_zip_code` >= '.(int)$from.' - AND `to_zip_code` <= '.(int)$to.' - AND s.`id_country` = (SELECT `id_country` - FROM `'._DB_PREFIX_.'state` s - LEFT JOIN `'._DB_PREFIX_.'county` c ON (c.`id_state` = s.`id_state`) - WHERE `id_county` = '.(int)$this->id.' - )' - ); - } - - return ($res OR County::isZipCodePresent($from) OR County::isZipCodePresent($to)); + Tools::displayAsDeprecated(); + return false; } + /** + * @deprecated since 1.5 + */ public function isZipCodePresent($zip_code) { - - if ($zip_code == 0) - return false; - - return (bool) Db::getInstance()->getValue(' - SELECT COUNT(*) FROM `'._DB_PREFIX_.'county_zip_code` cz - LEFT JOIN `'._DB_PREFIX_.'county` c ON (c.`id_county` = cz.`id_county`) - LEFT JOIN `'._DB_PREFIX_.'state` s ON (s.`id_state` = c.`id_state`) - WHERE - (`from_zip_code` <= '.(int)$zip_code.' AND `to_zip_code` >= '.(int)$zip_code.') - OR - (`from_zip_code` = '.(int)$zip_code.') - AND s.`id_country` = (SELECT `id_country` - FROM `'._DB_PREFIX_.'state` s - LEFT JOIN `'._DB_PREFIX_.'county` c ON (c.`id_state` = s.`id_state`) - WHERE `id_county` = '.(int)$this->id.' - )' - ); + Tools::displayAsDeprecated(); + return false; } + /** + * @deprecated since 1.5 + */ public static function deleteZipCodeByIdCounty($id_county) { - return Db::getInstance()->Execute( - 'DELETE FROM `'._DB_PREFIX_.'county_zip_code` - WHERE `id_county` = '.(int)$id_county - ); + Tools::displayAsDeprecated(); + return true; } - + /** + * @deprecated since 1.5 + */ public static function getIdCountyByNameAndIdState($name, $id_state) { - return Db::getInstance()->getValue(' - SELECT `id_county` FROM `'._DB_PREFIX_.'county` - WHERE `name` = \''.pSQL($name).'\' - AND `id_state` = '.(int)$id_state - ); + Tools::displayAsDeprecated(); + return false; } } diff --git a/classes/Helper.php b/classes/Helper.php index 3c52ab550..bc9ad0bdd 100755 --- a/classes/Helper.php +++ b/classes/Helper.php @@ -34,7 +34,7 @@ class HelperCore public static $translationsKeysForAdminCategorieTree = array( 'Home', 'selected', 'selecteds', 'Collapse All', 'Expand All', 'Check All', 'Uncheck All' ); - + /** * * @param type $trads values of translations keys @@ -42,22 +42,22 @@ class HelperCore * @param type $selected_cat array of selected categories * Format * Array - ( - [0] => 1 - [1] => 2 - ) - * OR - Array - ( - [1] => Array - ( - [id_category] => 1 - [name] => Home page - [link_rewrite] => home - ) - ) + * ( + * [0] => 1 + * [1] => 2 + * ) + * OR + * Array + * ( + * [1] => Array + * ( + * [id_category] => 1 + * [name] => Home page + * [link_rewrite] => home + * ) + * ) * @param type $input_name name of input - * @return string + * @return string */ public static function renderAdminCategorieTree($trads, $selected_cat = array(), $input_name = 'categoryBox', $use_radio = false) { @@ -86,7 +86,7 @@ class HelperCore '; - + $html .= '
'.$trads['Collapse All'].' @@ -97,7 +97,7 @@ class HelperCore ' : '').'
'; - + $home_is_selected = false; foreach($selected_cat AS $cat) { @@ -121,10 +121,77 @@ class HelperCore
  • '.$trads['Home'].'
      -
    •  
    • +
    •  
  • '; return $html; } + + /** + * Create a select input field + * + * @param array $values + * @param array $html_options any key => value options + * @param array $select_options + * - key: the array value that will be used as a key in my select (optional) + * - value: the array value that will be used as a label in my select (optional) + * - empty: the label displayed as an empty value (optional) + * - selected: the key corresponding to the selected value (optional) + * + * @return string html content + */ + public static function selectInput(array $values, array $html_options = array(), array $select_options = array()) + { + // options management + $options = self::buildHtmlOptions($html_options); + $select_html = ''; + + return $select_html; + } + + /** + * Create html a string containing html options + * eg: buildHtmlOptions(array('name' => 'myInputName', 'id' => 'myInputId')); + * return => 'name="myInputName" id="myInputId"' + * + * @param array $html_options + * + * @return string + */ + protected static function buildHtmlOptions(array $html_options) + { + $html = ''; + + foreach ($html_options as $html_option => $value) + $html .= Tools::htmlentitiesUTF8($html_option).'="'.Tools::htmlentitiesUTF8($value).'" '; + + return rtrim($html, ' '); + } } + diff --git a/classes/LocalizationPack.php b/classes/LocalizationPack.php index a7c78d92f..899a4d44b 100644 --- a/classes/LocalizationPack.php +++ b/classes/LocalizationPack.php @@ -117,60 +117,8 @@ class LocalizationPackCore return false; } } - - // Add counties - foreach ($data->county AS $xml_county) - { - $county_attributes = $xml_county->attributes(); - if (!$id_county = County::getIdCountyByNameAndIdState($county_attributes['name'], $state->id)) - { - $county = new County(); - $county->name = $county_attributes['name']; - $county->id_state = (int)$state->id; - $county->active = 1; - - if (!$county->validateFields()) - { - $this->_errors[] = Tools::displayError('Invalid County properties'); - return false; - } - - if (!$county->save()) - { - $this->_errors[] = Tools::displayError('An error has occurred while adding the county'); - return false; - } - } else { - $county = new County((int)$id_county); - if (!Validate::isLoadedObject($county)) - { - $this->_errors[] = Tools::displayError('An error occurred while fetching the county.'); - return false; - } - } - - // add zip codes - foreach ($xml_county->zipcode AS $xml_zipcode) - { - $zipcode_attributes = $xml_zipcode->attributes(); - - $zipcodes = $zipcode_attributes['from']; - if (isset($zipcode_attributes['to'])) - $zipcodes .= '-'.$zipcode_attributes['to']; - - if ($county->isZipCodeRangePresent($zipcodes)) - continue; - - if (!$county->addZipCodes($zipcodes)) - { - $this->_errors[] = Tools::displayError('An error has occurred while adding zipcodes'); - return false; - } - } - } } - return true; } diff --git a/classes/Manufacturer.php b/classes/Manufacturer.php index f0c4b1677..871314390 100644 --- a/classes/Manufacturer.php +++ b/classes/Manufacturer.php @@ -286,8 +286,9 @@ class ManufacturerCore extends ObjectModel LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product` AND i.`cover` = 1) LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.') LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group` - AND tr.`id_country` = '.(int)$context->country->id.' - AND tr.`id_state` = 0) + AND tr.`id_country` = '.(int)$context->country->id.' + AND tr.`id_state` = 0 + AND tr.`zipcode_from` = 0) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)$id_lang.') LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` diff --git a/classes/PaymentModule.php b/classes/PaymentModule.php index 7f0d58a73..6168aaa0d 100644 --- a/classes/PaymentModule.php +++ b/classes/PaymentModule.php @@ -99,9 +99,12 @@ abstract class PaymentModuleCore extends Module if ($secure_key !== false AND $secure_key != $cart->secure_key) die(Tools::displayError()); + + $carrier = new Carrier((int)$cart->id_carrier, (int)$cart->id_lang); + // Copying data from cart $order = new Order(); - $order->id_carrier = (int)($cart->id_carrier); + $order->id_carrier = $carrier->id; $order->id_customer = (int)($cart->id_customer); $order->id_address_invoice = (int)($cart->id_address_invoice); $order->id_address_delivery = (int)($cart->id_address_delivery); @@ -109,10 +112,10 @@ abstract class PaymentModuleCore extends Module $order->id_currency = ($currency_special ? (int)($currency_special) : (int)($cart->id_currency)); $order->id_lang = (int)($cart->id_lang); $order->id_cart = (int)($cart->id); - + $order->id_shop = (int)($shop->getID() ? $shop->getID() : $cart->id_shop); $order->id_group_shop = (int)($shop->getID() ? $shop->getGroupID() : $cart->id_group_shop); - + $customer = new Customer((int)($order->id_customer)); $order->secure_key = ($secure_key ? pSQL($secure_key) : pSQL($customer->secure_key)); $order->payment = $paymentMethod; @@ -128,8 +131,8 @@ abstract class PaymentModuleCore extends Module $order->total_products = (float)($cart->getOrderTotal(false, Cart::ONLY_PRODUCTS)); $order->total_products_wt = (float)($cart->getOrderTotal(true, Cart::ONLY_PRODUCTS)); $order->total_discounts = (float)(abs($cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS))); - $order->total_shipping = (float)($cart->getOrderShippingCost()); - $order->carrier_tax_rate = (float)Tax::getCarrierTaxRate($cart->id_carrier, (int)$cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); + $order->total_shipping = (float)$cart->getOrderShippingCost(); + $order->carrier_tax_rate = (float)$carrier->getTaxesRate(new Address((int)$cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); $order->total_wrapping = (float)(abs($cart->getOrderTotal(true, Cart::ONLY_WRAPPING))); $order->total_paid = (float)(Tools::ps_round((float)($cart->getOrderTotal(true, Cart::BOTH)), 2)); $order->invoice_date = '0000-00-00 00:00:00'; @@ -256,15 +259,15 @@ abstract class PaymentModuleCore extends Module if (isset($customization['datas'][_CUSTOMIZE_TEXTFIELD_])) foreach ($customization['datas'][_CUSTOMIZE_TEXTFIELD_] AS $text) $customizationText .= $text['name'].':'.' '.$text['value'].'
    '; - + if (isset($customization['datas'][_CUSTOMIZE_FILE_])) $customizationText .= sizeof($customization['datas'][_CUSTOMIZE_FILE_]) .' '. Tools::displayError('image(s)').'
    '; - - $customizationText .= '---
    '; + + $customizationText .= '---
    '; } - + $customizationText = rtrim($customizationText, '---
    '); - + $customizationQuantity = (int)($product['customizationQuantityTotal']); $productsList .= ' @@ -377,7 +380,6 @@ abstract class PaymentModuleCore extends Module { $invoice = new Address((int)($order->id_address_invoice)); $delivery = new Address((int)($order->id_address_delivery)); - $carrier = new Carrier((int)($order->id_carrier), $order->id_lang); $delivery_state = $delivery->id_state ? new State((int)($delivery->id_state)) : false; $invoice_state = $invoice->id_state ? new State((int)($invoice->id_state)) : false; @@ -387,11 +389,11 @@ abstract class PaymentModuleCore extends Module '{email}' => $customer->email, '{delivery_block_txt}' => $this->_getFormatedAddress($delivery, "\n"), '{invoice_block_txt}' => $this->_getFormatedAddress($invoice, "\n"), - '{delivery_block_html}' => $this->_getFormatedAddress($delivery, "
    ", + '{delivery_block_html}' => $this->_getFormatedAddress($delivery, "
    ", array( - 'firstname' => '%s', + 'firstname' => '%s', 'lastname' => '%s')), - '{invoice_block_html}' => $this->_getFormatedAddress($invoice, "
    ", + '{invoice_block_html}' => $this->_getFormatedAddress($invoice, "
    ", array( 'firstname' => '%s', 'lastname' => '%s')), diff --git a/classes/Product.php b/classes/Product.php index 70b9b8f49..d08705035 100644 --- a/classes/Product.php +++ b/classes/Product.php @@ -186,7 +186,7 @@ class ProductCore extends ObjectModel public $tags; public $isFullyLoaded = false; - + protected $langMultiShop = true; public $cache_is_pack; @@ -202,10 +202,10 @@ class ProductCore extends ObjectModel protected static $_cacheFeatures = array(); protected static $_frontFeaturesCache = array(); protected static $producPropertiesCache = array(); - + /** @var array cache stock data in getStock() method */ protected static $cacheStock = array(); - + /** @var array tables */ protected $tables = array ('product', 'product_lang'); @@ -303,7 +303,7 @@ class ProductCore extends ObjectModel parent::__construct($id_product, $id_lang, $id_shop); if (!$context) $context = Context::getContext(); - + if ($full AND $this->id) { $this->isFullyLoaded = $full; @@ -311,10 +311,13 @@ class ProductCore extends ObjectModel $this->manufacturer_name = Manufacturer::getNameById((int)$this->id_manufacturer); $this->supplier_name = Supplier::getNameById((int)$this->id_supplier); self::$_tax_rules_group[$this->id] = $this->id_tax_rules_group; + + $address = NULL; if (is_object($context->cart) AND $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')} != NULL) - $this->tax_rate = Tax::getProductTaxRate($this->id, $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); - else - $this->tax_rate = Tax::getProductTaxRate($this->id, NULL); + $address = $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}; + + $this->tax_rate = $this->getTaxesRate(new Address($address)); + $this->new = $this->isNew(); $this->price = Product::getPriceStatic((int)($this->id), false, NULL, 6, NULL, false, true, 1, false, NULL, NULL, NULL, $this->specificPrice); $this->unit_price = ($this->unit_price_ratio != 0 ? $this->price / $this->unit_price_ratio : 0); @@ -375,7 +378,7 @@ class ProductCore extends ObjectModel return $fields; } - + public function add($autodate = true, $nullValues = false) { if (!parent::add($autodate, $nullValues)) @@ -405,18 +408,25 @@ class ProductCore extends ObjectModel )); } - public static function initPricesComputation($customer = NULL) + public static function initPricesComputation($id_customer = NULL) { - if ($customer) + if ($id_customer) + { + $customer = new Customer((int)($id_customer)); + if (!Validate::isLoadedObject($customer)) + die(Tools::displayError()); self::$_taxCalculationMethod = Group::getPriceDisplayMethod((int)($customer->id_default_group)); + } + else if (Validate::isLoadedObject(Context::getContext()->customer)) + self::$_taxCalculationMethod = Group::getPriceDisplayMethod(Context::getContext()->customer->id_default_group); else self::$_taxCalculationMethod = Group::getDefaultPriceDisplayMethod(); } - public static function getTaxCalculationMethod($customer = NULL) + public static function getTaxCalculationMethod($id_customer = NULL) { - if ($customer) - self::initPricesComputation((int)($customer)); + if ($id_customer) + self::initPricesComputation((int)($id_customer)); return (int)(self::$_taxCalculationMethod); } @@ -556,7 +566,7 @@ class ProductCore extends ObjectModel { if (!GroupReduction::deleteProductReduction($this->id)) return false; - + Hook::deleteProduct($this); if (!parent::delete() OR !$this->deleteCategories(true) OR @@ -669,7 +679,7 @@ class ProductCore extends ObjectModel if (!$this->setGroupReduction()) return false; - + return true; } @@ -740,7 +750,7 @@ class ProductCore extends ObjectModel SELECT `id_image` FROM `'._DB_PREFIX_.'image` WHERE `id_product` = '.(int)($this->id)); - + $status = true; if ($result) foreach($result as $row) @@ -1369,7 +1379,7 @@ class ProductCore extends ObjectModel AND tr.`id_state` = 0) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`) - WHERE p.`active` = 1 + WHERE p.`active` = 1 AND DATEDIFF(p.`date_add`, DATE_SUB(NOW(), INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY)) > 0 AND p.`id_product` IN ( SELECT cp.`id_product` @@ -1556,7 +1566,7 @@ class ProductCore extends ObjectModel $ret[] = $val['id_category']; return $ret; } - + public static function getProductCategoriesFull($id_product = '', $id_lang) { $ret = array(); @@ -1642,7 +1652,7 @@ class ProductCore extends ObjectModel { if (!$context) $context = Context::getContext(); - + $cur_cart = $context->cart; if (isset($divisor)) @@ -1680,7 +1690,7 @@ class ProductCore extends ObjectModel // retrieve address informations $id_country = (int)$context->country->id; $id_state = 0; - $id_county = 0; + $zipcode = 0; if (!$id_address) $id_address = $cur_cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}; @@ -1692,18 +1702,14 @@ class ProductCore extends ObjectModel { $id_country = (int)($address_infos['id_country']); $id_state = (int)($address_infos['id_state']); - $postcode = (int)$address_infos['postcode']; - - $id_county = (int)County::getIdCountyByZipCode($id_state, $postcode); + $zipcode = $address_infos['postcode']; } } elseif (isset($context->customer->geoloc_id_country)) { $id_country = (int)$context->customer->geoloc_id_country; $id_state = (int)$context->customer->id_state; - $postcode = (int)$context->customer->postcode; - - $id_county = (int)County::getIdCountyByZipCode($id_state, $postcode); + $zipcode = (int)$context->customer->postcode; } if (Tax::excludeTaxeOption()) @@ -1712,7 +1718,7 @@ class ProductCore extends ObjectModel if ($usetax != false AND !empty($address_infos['vat_number']) AND $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY') AND Configuration::get('VATNUMBER_MANAGEMENT')) $usetax = false; - return Product::priceCalculation($context->shop->getID(), $id_product, $id_product_attribute, $id_country, $id_state, $id_county, $id_currency, $id_group, $quantity, $usetax, $decimals, $only_reduc, + return Product::priceCalculation($context->shop->getID(), $id_product, $id_product_attribute, $id_country, $id_state, $zipcode, $id_currency, $id_group, $quantity, $usetax, $decimals, $only_reduc, $usereduc, $with_ecotax, $specificPriceOutput, $use_groupReduction); } @@ -1733,16 +1739,17 @@ class ProductCore extends ObjectModel * @param boolean $use_reduc Set if the returned amount will include reduction * @param boolean $with_ecotax insert ecotax in price output. * @param variable_reference $specific_price_output If a specific price applies regarding the previous parameters, this variable is filled with the corresponding SpecificPrice object + * * @return float Product price **/ - public static function priceCalculation($id_shop, $id_product, $id_product_attribute, $id_country, $id_state, $id_county, $id_currency, $id_group, $quantity, $use_tax, $decimals, $only_reduc, $use_reduc, $with_ecotax, &$specific_price, $use_groupReduction) + public static function priceCalculation($id_shop, $id_product, $id_product_attribute, $id_country, $id_state, $zipcode, $id_currency, $id_group, $quantity, $use_tax, $decimals, $only_reduc, $use_reduc, $with_ecotax, &$specific_price, $use_groupReduction) { // Caching if ($id_product_attribute === NULL) $product_attribute_label = 'NULL'; else $product_attribute_label = ($id_product_attribute === false ? 'false' : $id_product_attribute); - $cacheId = $id_product.'-'.$id_shop.'-'.$id_currency.'-'.$id_country.'-'.$id_state.'-'.$id_county.'-'.$id_group.'-'.$quantity.'-'.$product_attribute_label.'-'.($use_tax?'1':'0').'-'.$decimals.'-'.($only_reduc?'1':'0').'-'.($use_reduc?'1':'0').'-'.$with_ecotax; + $cacheId = $id_product.'-'.$id_shop.'-'.$id_currency.'-'.$id_country.'-'.$id_state.'-'.$zipcode.'-'.$id_group.'-'.$quantity.'-'.$product_attribute_label.'-'.($use_tax?'1':'0').'-'.$decimals.'-'.($only_reduc?'1':'0').'-'.($use_reduc?'1':'0').'-'.$with_ecotax; // reference parameter is filled before any returns $specific_price = SpecificPrice::getSpecificPrice((int)($id_product), $id_shop, $id_currency, $id_country, $id_group, $quantity); @@ -1773,14 +1780,18 @@ class ProductCore extends ObjectModel if ($id_product_attribute !== false) // If you want the default combination, please use NULL value instead $price += $attribute_price; - // TaxRate calculation - $tax_rate = Tax::getProductTaxRateViaRules((int)$id_product, (int)$id_country, (int)$id_state, (int)$id_county); - if ($tax_rate === false) - $tax_rate = 0; + // Tax + $address = new Address(); + $address->id_country = $id_country; + $address->id_state = $id_state; + $address->postcode = $zipcode; + + $tax_manager = TaxManagerFactory::getManager($address, Product::getIdTaxRulesGroupByIdProduct((int)$id_product)); + $product_tax_calculator = $tax_manager->getTaxCalculator(); // Add Tax if ($use_tax) - $price = $price * (1 + ($tax_rate / 100)); + $price = $product_tax_calculator->addTaxes($price); $price = Tools::ps_round($price, $decimals); // Reduction @@ -1793,7 +1804,7 @@ class ProductCore extends ObjectModel if (!$specific_price['id_currency']) $reduction_amount = Tools::convertPrice($reduction_amount, $id_currency); - $reduc = Tools::ps_round(!$use_tax ? $reduction_amount / (1 + $tax_rate / 100) : $reduction_amount, $decimals); + $reduc = Tools::ps_round(!$use_tax ? $product_tax_calculator->removeTax($reduction_amount) : $reduction_amount, $decimals); } else $reduc = Tools::ps_round($price * $specific_price['reduction'], $decimals); @@ -1825,8 +1836,13 @@ class ProductCore extends ObjectModel $ecotax = Tools::convertPrice($ecotax, $id_currency); if ($use_tax) { - $taxRate = TaxRulesGroup::getTaxesRate((int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID'), (int)$id_country, (int)$id_state, (int)$id_county); - $price += $ecotax * (1 + ($taxRate / 100)); + // reinit the tax manager for ecotax handling + $tax_manager = TaxManagerFactory::getManager( + $address, + (int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID') + ); + $ecotax_tax_calculator = $tax_manager->getTaxCalculator(); + $price += $ecotax_tax_calculator->addTaxes($ecotax); } else $price += $ecotax; @@ -1915,7 +1931,7 @@ class ProductCore extends ObjectModel $ret .= Tools::displayPrice($params['p']['price'], $smarty->ps_currency); return $ret; } - + /** * Display price with right format and currency * @@ -1975,10 +1991,10 @@ class ProductCore extends ObjectModel $product = new Product($id_product); return $product->getStock($id_product_attribute); } - + /** * Create JOIN query with 'stock' table - * + * * @param string $productAlias Alias of product table * @param string|int $productAttribute If string : alias of PA table ; if int : value of PA ; if null : nothing about PA * @param bool $innerJoin LEFT JOIN or INNER JOIN @@ -2002,10 +2018,10 @@ class ProductCore extends ObjectModel return $sql; } - + /** * Set the stock quantity of current product - * + * * @since 1.5.0 * @param int $quantity * @param int $id_product_attribute @@ -2060,7 +2076,7 @@ class ProductCore extends ObjectModel 'quantity' => $quantity, ), 'INSERT'); } - + // Change stock quantity on product if ($id_stock = Stock::getStockId($this->id, $id_product_attribute, $shop->getID(true))) { @@ -2082,7 +2098,7 @@ class ProductCore extends ObjectModel /** * Get the stock quantity of current product - * + * * @since 1.5.0 * @param int $id_product_attribute * @param Context $context @@ -2103,6 +2119,7 @@ class ProductCore extends ObjectModel WHERE id_product = '.$this->id.' AND id_product_attribute = '.(int)$id_product_attribute .$context->shop->sqlRestriction(Shop::SHARE_STOCK); + self::$cacheStock[$this->id][$id_product_attribute] = (int)Db::getInstance()->getValue($sql); } return self::$cacheStock[$this->id][$id_product_attribute]; @@ -2692,7 +2709,7 @@ class ProductCore extends ObjectModel { if (!$this->isFullyLoaded && is_null($this->tags)) $this->tags = Tag::getProductTags($this->id); - + if (!($this->tags AND key_exists($id_lang, $this->tags))) return ''; @@ -2714,7 +2731,7 @@ class ProductCore extends ObjectModel if (!$row['id_product']) return false; $context = Context::getContext(); - + // Product::getDefaultAttribute is only called if id_product_attribute is missing from the SQL query at the origin of it: consider adding it in order to avoid unnecessary queries $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']); if ((!isset($row['id_product_attribute']) OR !$row['id_product_attribute']) @@ -3030,10 +3047,10 @@ class ProductCore extends ObjectModel $fields_present[] = array('id_customization_field' => $field['index'], 'type' => $field['type']); foreach ($requiredFields AS $required_field) if (!in_array($required_field, $fields_present)) - return false; + return false; return true; } - + public static function idIsOnCategoryId($id_product, $categories) { $sql = 'SELECT id_product FROM `'._DB_PREFIX_.'category_product` WHERE `id_product`='.(int)($id_product).' AND `id_category` IN('; @@ -3063,7 +3080,7 @@ class ProductCore extends ObjectModel FROM `'._DB_PREFIX_.'category_product` cp INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`) WHERE cp.`id_product` = '.(int)$this->id.' AND ctg.`id_group` = 1'); - else + else return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT cg.`id_group` FROM `'._DB_PREFIX_.'category_product` cp @@ -3075,7 +3092,7 @@ class ProductCore extends ObjectModel /** * Add a stock movement for current product - * + * * @param int $quantity * @param int $id_reason * @param int $id_product_attribute @@ -3155,6 +3172,20 @@ class ProductCore extends ObjectModel return self::$_tax_rules_group[$id_product]; } + /** + * @return the total taxes rate applied to the product + */ + public function getTaxesRate(Address $address = NULL) + { + if (!$address OR !$address->id_country) + $address = Tax::initializeAddress(); + + $tax_manager = TaxManagerFactory::getManager($address, $this->id_tax_rules_group); + $tax_calculator = $tax_manager->getTaxCalculator(); + + return $tax_calculator->getTaxesRate(); + } + /** * Webservice getter : get product features association * @@ -3368,7 +3399,7 @@ class ProductCore extends ObjectModel SET `cover` = 1 WHERE `id_product` = '.(int)($this->id).' AND `id_image` = '.(int)$id_image); return true; } - + /** * Webservice getter : get image ids of current product for association * @@ -3390,8 +3421,8 @@ class ProductCore extends ObjectModel FROM `'._DB_PREFIX_.'product_tag` WHERE `id_product` = '.(int)($this->id)); } - - + + public function getWsManufacturerName() { return Manufacturer::getNameById((int)$this->id_manufacturer); @@ -3404,7 +3435,7 @@ class ProductCore extends ObjectModel SET ecotax = 0 '); } - + /** * Set Group reduction if needed */ diff --git a/classes/Supplier.php b/classes/Supplier.php index 04cc60991..7fbc535ef 100644 --- a/classes/Supplier.php +++ b/classes/Supplier.php @@ -223,7 +223,8 @@ class SupplierCore extends ObjectModel LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)($id_lang).') LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group` AND tr.`id_country` = '.(int)Context::getContext()->country->id.' - AND tr.`id_state` = 0) + AND tr.`id_state` = 0 + AND tr.`zipcode_from` = 0) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)($id_lang).') LEFT JOIN `'._DB_PREFIX_.'supplier` s ON s.`id_supplier` = p.`id_supplier` diff --git a/classes/Tax.php b/classes/tax/Tax.php similarity index 60% rename from classes/Tax.php rename to classes/tax/Tax.php index 3ef78b811..d10c32638 100644 --- a/classes/Tax.php +++ b/classes/tax/Tax.php @@ -74,138 +74,6 @@ class TaxCore extends ObjectModel return parent::delete(); } - /** - * Get all available taxes - * - * @return array Taxes - */ - public static function getTaxes($id_lang = false, $active = 1) - { - return Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' - SELECT t.id_tax, t.rate'.((int)($id_lang) ? ', tl.name, tl.id_lang ' : '').' - FROM `'._DB_PREFIX_.'tax` t - '.((int)($id_lang) ? 'LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)($id_lang).')' - .($active == 1 ? 'WHERE t.`active` = 1' : '').' - ORDER BY `name` ASC' : '')); - } - - public static function excludeTaxeOption() - { - return !Configuration::get('PS_TAX'); - } - - public static function getTaxIdByName($tax_name, $active = 1) - { - $tax = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' - SELECT t.`id_tax` - FROM `'._DB_PREFIX_.'tax` t - LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (tl.id_tax = t.id_tax) - WHERE tl.`name` = \''.pSQL($tax_name).'\' '. - ($active == 1 ? ' AND t.`active` = 1' : '')); - - return $tax ? (int)($tax['id_tax']) : false; - } - - /** - * Return the product tax - * - * @param integer $id_product - * @param integer $id_country - * @return Tax - */ - public static function getProductTaxRate($id_product, $id_address = NULL) - { - $id_country = (int)Context::getContext()->country->id; - $id_state = 0; - $id_county = 0; - $rate = 0; - if (!empty($id_address)) - { - $address_infos = Address::getCountryAndState($id_address); - if ($address_infos['id_country']) - { - $id_country = (int)($address_infos['id_country']); - $id_state = (int)$address_infos['id_state']; - $id_county = (int)County::getIdCountyByZipCode($address_infos['id_state'], $address_infos['postcode']); - } - - if (!empty($address_infos['vat_number']) AND $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY') AND Configuration::get('VATNUMBER_MANAGEMENT')) - return 0; - } - - if ($rate = Tax::getProductTaxRateViaRules((int)$id_product, (int)$id_country, (int)$id_state, (int)$id_county)) - return $rate; - - return $rate; - } - - public static function getProductEcotaxRate($id_address = NULL) - { - $id_country = (int)Context::getContext()->country->id; - $id_state = 0; - $id_county = 0; - $rate = 0; - if (!empty($id_address)) - { - $address_infos = Address::getCountryAndState($id_address); - if ($address_infos['id_country']) - { - $id_country = (int)($address_infos['id_country']); - $id_state = (int)$address_infos['id_state']; - $id_county = (int)County::getIdCountyByZipCode($address_infos['id_state'], $address_infos['postcode']); - } - - if (!empty($address_infos['vat_number']) AND $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY') AND Configuration::get('VATNUMBER_MANAGEMENT')) - return 0; - } - - if ($rate = TaxRulesGroup::getTaxesRate((int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID'), (int)$id_country, (int)$id_state, (int)$id_county)) - return $rate; - - return $rate; - } - - /** - * Return the product tax rate using the tax rules system - * - * @param integer $id_product - * @param integer $id_country - * @return Tax - */ - public static function getProductTaxRateViaRules($id_product, $id_country, $id_state, $id_county) - { - if (!isset(self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$id_state.'-'.$id_county])) - { - $tax_rate = TaxRulesGroup::getTaxesRate((int)Product::getIdTaxRulesGroupByIdProduct((int)$id_product), (int)$id_country, (int)$id_state, (int)$id_county); - self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$id_county] = $tax_rate; - } - - return self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$id_county]; - } - - - public static function getCarrierTaxRate($id_carrier, $id_address = NULL) - { - $id_country = (int)Context::getContext()->country->id; - $id_state = 0; - $id_county = 0; - if (!empty($id_address)) - { - $address_infos = Address::getCountryAndState($id_address); - if ($address_infos['id_country']) - { - $id_country = (int)($address_infos['id_country']); - $id_state = (int)$address_infos['id_state']; - $id_county = (int)County::getIdCountyByZipCode($address_infos['id_state'], $address_infos['postcode']); - } - - if (!empty($address_infos['vat_number']) AND $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY') AND Configuration::get('VATNUMBER_MANAGEMENT')) - return 0; - } - - return TaxRulesGroup::getTaxesRate((int)Carrier::getIdTaxRulesGroupByIdCarrier((int)$id_carrier), (int)$id_country, (int)$id_state, (int)$id_county); - } - public function toggleStatus() { if (parent::toggleStatus()) @@ -229,5 +97,145 @@ class TaxCore extends ObjectModel return true; } + + /** + * Get all available taxes + * + * @return array Taxes + */ + public static function getTaxes($id_lang = false, $active = 1) + { + return Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' + SELECT t.id_tax, t.rate'.((int)($id_lang) ? ', tl.name, tl.id_lang ' : '').' + FROM `'._DB_PREFIX_.'tax` t + '.((int)($id_lang) ? 'LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)($id_lang).')' + .($active == 1 ? 'WHERE t.`active` = 1' : '').' + ORDER BY `name` ASC' : '')); + } + + public static function excludeTaxeOption() + { + return !Configuration::get('PS_TAX'); + } + + /** + * Return the tax id associated to the specified name + * + * @param string $tax_name + * @param boolean $active (true by default) + */ + public static function getTaxIdByName($tax_name, $active = 1) + { + $tax = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT t.`id_tax` + FROM `'._DB_PREFIX_.'tax` t + LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (tl.id_tax = t.id_tax) + WHERE tl.`name` = \''.pSQL($tax_name).'\' '. + ($active == 1 ? ' AND t.`active` = 1' : '')); + + return $tax ? (int)($tax['id_tax']) : false; + } + + /** + * Returns the product tax + * + * @param integer $id_product + * @param integer $id_country + * @return Tax + * + * @deprecated use $product->getTaxesRate() instead + */ + public static function getProductTaxRate($id_product, $id_address = NULL) + { + $address = Tax::initializeAddress($id_address); + $id_tax_rules = (int)Product::getIdTaxRulesGroupByIdProduct($id_product); + + $tax_manager = TaxManagerFactory::getManager($address, $id_tax_rules); + $tax_calculator = $tax_manager->getTaxCalculator(); + + return $tax_calculator->getTaxesRate(); + } + + /** + * Returns the ecotax tax rate + * + * @param id_address + * @return float $tax_rate + */ + public static function getProductEcotaxRate($id_address = NULL) + { + $address = Tax::initializeAddress($id_address); + + $tax_manager = TaxManagerFactory::getManager($address, (int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID')); + $tax_calculator = $tax_manager->getTaxCalculator(); + + return $tax_calculator->getTaxesRate(); + } + + /** + * Returns the carrier tax rate + * + * @param id_address + * @return float $tax_rate + */ + public static function getCarrierTaxRate($id_carrier, $id_address = NULL) + { + $address = Tax::initializeAddress($id_address); + $id_tax_rules = (int)Carrier::getIdTaxRulesGroupByIdCarrier((int)$id_carrier); + + $tax_manager = TaxManagerFactory::getManager($address, $id_tax_rules); + $tax_calculator = $tax_manager->getTaxCalculator(); + + return $tax_calculator->getTaxesRate(); + } + + /** + * Initiliaze an address corresponding to the id address if any or to the + * default shop configuration + * + * @param int $id_address + * @return Address address + */ + public static function initializeAddress($id_address = NULL) + { + // set the default address + $address = new Address(); + $address->id_country = (int)Context::getContext()->country->id; + $address->id_state = 0; + $address->postcode = 0; + + // if an id_address has been specified retrieve the address + if ($id_address) + { + $address = new Address((int)$id_address); + + if (!Validate::isLoadedObject()) + throw new Exception('Invalid address'); + } + + return $address; + } + + /** + * Return the product tax rate using the tax rules system + * + * @param integer $id_product + * @param integer $id_country + * @return Tax + * + * @deprecated since 1.5 + */ + public static function getProductTaxRateViaRules($id_product, $id_country, $id_state, $zipcode) + { + Tools::displayAsDeprecated(); + + if (!isset(self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$id_state.'-'.$zipcode])) + { + $tax_rate = TaxRulesGroup::getTaxesRate((int)Product::getIdTaxRulesGroupByIdProduct((int)$id_product), (int)$id_country, (int)$id_state, $zipcode); + self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$zipcode] = $tax_rate; + } + + return self::$_product_tax_via_rules[$id_product.'-'.$id_country.'-'.$zipcode]; + } } diff --git a/classes/tax/TaxCalculator.php b/classes/tax/TaxCalculator.php new file mode 100644 index 000000000..547c3446e --- /dev/null +++ b/classes/tax/TaxCalculator.php @@ -0,0 +1,144 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +/** + * @since 1.5.0 + * + * TaxCaculator is responsible of the tax computation + */ +class TaxCalculatorCore +{ + /** + * COMBINE_METHOD sum taxes + * eg: 100€ * (10% + 15%) + */ + const COMBINE_METHOD = 1; + + /** + * ONE_AFTER_ANOTHER_METHOD apply taxes one after another + * eg: (100€ * 10%) * 15% + */ + const ONE_AFTER_ANOTHER_METHOD = 2; + + /** + * @var array $taxes_rate + */ + public $taxes_rate; + + /** + * @var int $computation_method (COMBINE_METHOD | ONE_AFTER_ANOTHER_METHOD) + */ + public $computation_method; + + + /** + * @param array $taxes_rate + * @param int $computation_method (COMBINE_METHOD | ONE_AFTER_ANOTHER_METHOD) + */ + public function __construct(array $taxes_rate, $computation_method = TaxCalculator::COMBINE_METHOD) + { + $this->taxes_rate = $taxes_rate; + $this->computation_method = (int)$computation_method; + } + + /** + * Compute and add the taxes to the specified price + * + * @param price + * @return price with taxes + */ + public function addTaxes($price) + { + $total_price = $price; + if ($this->computation_method == TaxCalculator::ONE_AFTER_ANOTHER_METHOD) + { + foreach ($this->taxes_rate as $tax_rate) + $total_price = $total_price * (1 + abs($tax_rate) / 100); + } + else + { + foreach ($this->taxes_rate as $tax_rate) + { + if ($tax_rate != 0) + $total_price = $total_price + ($price * (abs($tax_rate) / 100)); + } + } + return $total_price; + } + + + /** + * Compute and remove the taxes to the specified price + * + * @param price + * @return price without taxes + */ + public function removeTaxes($price) + { + $total_price = $price; + if ($this->computation_method == TaxCalculator::ONE_AFTER_ANOTHER_METHOD) + { + foreach ($this->taxes_rate as $tax_rate) + $total_price = $total_price / (1 + abs($tax_rate) / 100); + } + else + { + $taxes_rate = 0; + foreach ($this->taxes_rate as $tax_rate) + $taxes_rate += abs($tax_rate); + + $total_price = $total_price / (1 + (abs($taxes_rate) / 100)); + } + + return $total_price; + } + + /** + * @return total taxes rate + */ + public function getTaxesRate() + { + $taxes_rate = 0; + if ($this->computation_method == TaxCalculator::ONE_AFTER_ANOTHER_METHOD) + { + $taxes_rate = 1; + foreach ($this->taxes_rate as $rate) + $taxes_rate *= (1 + (abs($rate) / 100)); + + $taxes_rate = $taxes_rate - 1; + $taxes_rate = $taxes_rate * 100; + } + else + { + foreach ($this->taxes_rate as $rate) + $taxes_rate += abs($rate); + } + + return $taxes_rate; + } +} + diff --git a/classes/tax/TaxManagerFactory.php b/classes/tax/TaxManagerFactory.php new file mode 100644 index 000000000..4ced3f9b7 --- /dev/null +++ b/classes/tax/TaxManagerFactory.php @@ -0,0 +1,103 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +/** +* @since 1.5 +*/ +class TaxManagerFactoryCore +{ + protected static $cache_tax_manager; + + /** + * Returns a tax manager able to handle this address + * + * @param Address $address + * @param string $type + * + * @return TaxManager + */ + public static function getManager(Address $address, $type) + { + $cache_id = TaxManagerFactory::getCacheKey($address).'-'.$type; + if (!isset(TaxManagerFactory::$cache_tax_manager[$cache_id])) + { + $tax_manager = TaxManagerFactory::execHookTaxManagerFactory($address, $type); + if (!($tax_manager instanceof TaxManagerInterface)) + $tax_manager = new TaxRulesTaxManager($address, $type); + + TaxManagerFactory::$cache_tax_manager[$cache_id] = $tax_manager; + } + + return TaxManagerFactory::$cache_tax_manager[$cache_id]; + } + + /** + * Check for a tax manager able to handle this type of address in the module list + * + * @param Address $address + * @param string $type + * + * @return TaxManager + */ + public static function execHookTaxManagerFactory(Address $address, $type) + { + $modules_infos = Hook::getModulesFromHook(Hook::getIdByName('taxManager')); + $tax_manager = false; + + foreach ($modules_infos as $module_infos) + { + $module_instance = Module::getInstanceByName($module_infos['name']); + if (is_callable(array($module_instance, 'hookTaxManager'))) + { + $tax_manager = $module_instance->hookTaxManager(array( + 'address' => $address, + 'params' => $type + )); + } + + if ($tax_manager) + break; + } + + return $tax_manager; + } + + + /** + * Create a unique identifier for the address + * @param Address + */ + protected static function getCacheKey(Address $address) + { + return $address->id_country.'-' + .(int)$address->id_state.'-' + .$address->postcode.'-' + .$address->vat_number.'-' + .$address->dni; + } +} + diff --git a/classes/tax/TaxManagerInterface.php b/classes/tax/TaxManagerInterface.php new file mode 100644 index 000000000..c8b388e34 --- /dev/null +++ b/classes/tax/TaxManagerInterface.php @@ -0,0 +1,51 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + + +/** +* A TaxManager define a way to retrieve tax. +*/ +interface TaxManagerInterface +{ + /** + * This method determine if the tax manager is available for the specified address. + * + * @param Address $address + * @param string $type + * + * @return TaxManager + */ + public static function isAvailableForThisAddress(Address $address); + + /** + * Return the tax calculator associated to this address + * + * @return TaxCalculator + */ + public function getTaxCalculator(); +} + diff --git a/classes/tax/TaxManagerModule.php b/classes/tax/TaxManagerModule.php new file mode 100644 index 000000000..ecab6c6b9 --- /dev/null +++ b/classes/tax/TaxManagerModule.php @@ -0,0 +1,57 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +abstract class TaxManagerModuleCore extends Module +{ + public $tax_manager_class; + + public function install() + { + return (parent::install() && $this->registerHook('taxManager') ); + } + + public function hookTaxManager($args) + { + $class_file =_PS_MODULE_DIR_.'/'.$this->name.'/'.$this->tax_manager_class.'.php'; + + if (!isset($this->tax_manager_class) || !file_exists($class_file)) + die(Tools::displayError('Incorrect Tax Manager class ['.$this->tax_manager_class.']')); + + require_once($class_file); + + if (!class_exists($this->tax_manager_class)) + die(Tools::displayError('Tax Manager class not found ['.$this->tax_manager_class.']')); + + $class = $this->tax_manager_class; + + if ($class::isAvailableForThisAddress($args['address'])) + return new $class(); + + return false; + } +} + diff --git a/classes/TaxRule.php b/classes/tax/TaxRule.php similarity index 52% rename from classes/TaxRule.php rename to classes/tax/TaxRule.php index c527f0db6..3b697f880 100644 --- a/classes/TaxRule.php +++ b/classes/tax/TaxRule.php @@ -30,13 +30,21 @@ class TaxRuleCore extends ObjectModel public $id_tax_rules_group; public $id_country; public $id_state; - public $id_county; + public $zipcode_from; + public $zipcode_to; public $id_tax; - public $state_behavior; - public $county_behavior; + public $behavior; + public $description; protected $fieldsRequired = array('id_tax_rules_group', 'id_country', 'id_tax'); - protected $fieldsValidate = array('id_tax_rules_group' => 'isUnsignedId', 'id_country' => 'isUnsignedId', 'id_state' => 'isUnsignedId', 'id_county' => 'isUnsignedId', 'id_tax' => 'isUnsignedId', 'state_behavior' => 'isUnsignedInt', 'county_behavior' => 'isUnsignedInt'); + protected $fieldsValidate = array('id_tax_rules_group' => 'isUnsignedId', + 'id_country' => 'isUnsignedId', + 'id_state' => 'isUnsignedId', + 'zipcode_from' => 'isUnsignedId', // TODO: char + 'zipcode_to' => 'isUnsignedId', // TODO: char + 'id_tax' => 'isUnsignedId', + 'behavior' => 'isUnsignedInt', + 'description' => 'isUnsignedInt'); // TODO:char protected $table = 'tax_rule'; protected $identifier = 'id_tax_rule'; @@ -47,10 +55,11 @@ class TaxRuleCore extends ObjectModel $fields['id_tax_rules_group'] = (int)($this->id_tax_rules_group); $fields['id_country'] = (int)$this->id_country; $fields['id_state'] = (int)$this->id_state; - $fields['id_county'] = (int)$this->id_county; - $fields['state_behavior'] = (int)$this->state_behavior; - $fields['county_behavior'] = (int)$this->county_behavior; - $fields['id_tax'] = (int)($this->id_tax); + $fields['zipcode_from'] = (int)$this->zipcode_from; + $fields['zipcode_to'] = (int)$this->zipcode_to; + $fields['behavior'] = (int)$this->behavior; + $fields['id_tax'] = (int)($this->id_tax); + $fields['description'] = $this->description; return $fields; } @@ -66,23 +75,44 @@ class TaxRuleCore extends ObjectModel ); } + public static function retrieveById($id_tax_rule) + { + return Db::getInstance()->getRow(' + SELECT * FROM `'._DB_PREFIX_.'tax_rule` + WHERE `id_tax_rule` = '.(int)$id_tax_rule); + } +/* public static function getTaxRulesByGroupId($id_group) { if (empty($id_group)) die(Tools::displayError()); - $results = Db::getInstance()->ExecuteS(' + return Db::getInstance()->ExecuteS(' SELECT * FROM `'._DB_PREFIX_.'tax_rule` WHERE `id_tax_rules_group` = '.(int)$id_group ); + }*/ - $res = array(); - foreach ($results AS $row) - $res[$row['id_country']][$row['id_state']][$row['id_county']] = array('id_tax' => $row['id_tax'], 'state_behavior' => $row['state_behavior'], 'county_behavior' => $row['county_behavior']); + public static function getTaxRulesByGroupId($id_lang, $id_group) + { - return $res; - } + + return Db::getInstance()->ExecuteS(' + SELECT g.`id_tax_rule`, + c.`name` AS country_name, + s.`name` AS state_name, + t.rate, + g.`zipcode_from`, g.`zipcode_to`, + g.`description`, + g.`behavior` + FROM `'._DB_PREFIX_.'tax_rule` g + LEFT JOIN `'._DB_PREFIX_.'country_lang` c ON (g.`id_country` = c.`id_country` AND id_lang = '.(int)$id_lang.') + LEFT JOIN `'._DB_PREFIX_.'state` s ON (g.`id_state` = s.`id_state`) + LEFT JOIN `'._DB_PREFIX_.'tax` t ON (g.`id_tax` = t.`id_tax`) + WHERE `id_tax_rules_group` = '.(int)$id_group + ); + } public static function deleteTaxRuleByIdTax($id_tax) { @@ -93,19 +123,57 @@ class TaxRuleCore extends ObjectModel } + /** + * @deprecated since 1.5 + */ public static function deleteTaxRuleByIdCounty($id_county) { - return Db::getInstance()->Execute(' - DELETE FROM `'._DB_PREFIX_.'tax_rule` - WHERE `id_county` = '.(int)$id_county - ); + Tools::displayAsDeprecated(); + return true; } + /** + * @param int $id_tax + * @return boolean + */ public static function isTaxInUse($id_tax) { return Db::getInstance()->getValue(' SELECT COUNT(*) FROM `'._DB_PREFIX_.'tax_rule` WHERE `id_tax` = '.(int)$id_tax ); } + + + /** + * @param string $zipcode a range of zipcode (eg: 75000 / 75000-75015) + * @return array an array containing two zipcode ordered by zipcode + */ + public function breakDownZipCode($zip_codes) + { + $zip_codes = preg_split('/-/', $zip_codes); + + if (sizeof($zip_codes) == 2) + { + $from = $zip_codes[0]; + $to = $zip_codes[1]; + if ($zip_codes[0] > $zip_codes[1]) + { + $from = $zip_codes[1]; + $to = $zip_codes[0]; + } + elseif ($zip_codes[0] == $zip_codes[1]) + { + $from = $zip_codes[0]; + $to = 0; + } + } + elseif (sizeof($zip_codes) == 1) + { + $from = $zip_codes[0]; + $to = 0; + } + + return array($from, $to); + } } diff --git a/classes/TaxRulesGroup.php b/classes/tax/TaxRulesGroup.php similarity index 61% rename from classes/TaxRulesGroup.php rename to classes/tax/TaxRulesGroup.php index b8c4e1d6a..793f831a5 100644 --- a/classes/TaxRulesGroup.php +++ b/classes/tax/TaxRulesGroup.php @@ -59,74 +59,19 @@ class TaxRulesGroupCore extends ObjectModel ); } + /** + * @return array an array of tax rules group formatted as $id => $name + */ public static function getTaxRulesGroupsForOptions() { $tax_rules[] = array('id_tax_rules_group' => 0, 'name' => Tools::displayError('No tax')); return array_merge($tax_rules, TaxRulesGroup::getTaxRulesGroups()); } - public static function getTaxes($id_tax_rules_group, $id_country, $id_state, $id_county) - { - if (empty($id_tax_rules_group) OR empty($id_country)) - return array(new Tax()); // No Tax - - if (isset(self::$_taxes[$id_tax_rules_group.'-'.$id_country.'-'.$id_state.'-'.$id_county])) - return self::$_taxes[$id_tax_rules_group.'-'.$id_country.'-'.$id_state.'-'.$id_county]; - - $rows = Db::getInstance()->ExecuteS(' - SELECT * - FROM `'._DB_PREFIX_.'tax_rule` - WHERE `id_country` = '.(int)$id_country.' - AND `id_tax_rules_group` = '.(int)$id_tax_rules_group.' - AND `id_state` IN (0, '.(int)$id_state.') - AND `id_county` IN (0, '.(int)$id_county.') - ORDER BY `id_county` DESC, `id_state` DESC' - ); - - - $taxes = array(); - foreach ($rows AS $row) - { - if ($row['id_county'] != 0) - { - switch($row['county_behavior']) - { - case County::USE_BOTH_TAX: - $taxes[] = new Tax($row['id_tax']); - break; - - case County::USE_COUNTY_TAX: - $taxes = array(new Tax($row['id_tax'])); - break 2; - - case County::USE_STATE_TAX: // do nothing - break; - } - } - else if ($row['id_state'] != 0) - { - switch($row['state_behavior']) - { - case PS_STATE_TAX: // use only product tax - $taxes[] = new Tax($row['id_tax']); - break 2; // switch + foreach - - case PS_BOTH_TAX: - $taxes[] = new Tax($row['id_tax']); - break; - - case PS_PRODUCT_TAX: // do nothing use country tax - break; - } - } - else - $taxes[] = new Tax((int)$row['id_tax']); - } - - self::$_taxes[$id_tax_rules_group.'-'.$id_country.'-'.$id_state.'-'.$id_county] = $taxes; - return $taxes; - } + /** + * @return array + */ public static function getAssociatedTaxRatesByIdCountry($id_country) { $rows = Db::getInstance()->ExecuteS(' @@ -136,7 +81,7 @@ class TaxRulesGroupCore extends ObjectModel LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`) WHERE tr.`id_country` = '.(int)$id_country.' AND tr.`id_state` = 0 - AND tr.`id_county` = 0' + AND 0 between `zipcode_from` AND `zipcode_to`' ); $res = array(); @@ -146,15 +91,12 @@ class TaxRulesGroupCore extends ObjectModel return $res; } - public static function getTaxesRate($id_tax_rules_group, $id_country, $id_state, $id_county) - { - $rate = 0; - foreach (TaxRulesGroup::getTaxes($id_tax_rules_group, $id_country, $id_state, $id_county) AS $tax) - $rate += (float)$tax->rate; - - return $rate; - } - + /** + * Returns the tax rules group id corresponding to the name + * + * @param string name + * @return int id of the tax rules + */ public static function getIdByName($name) { return Db::getInstance()->getValue( @@ -163,4 +105,29 @@ class TaxRulesGroupCore extends ObjectModel WHERE `name` = \''.pSQL($name).'\'' ); } -} \ No newline at end of file + + /** + * @deprecated since 1.5 + */ + public static function getTaxesRate($id_tax_rules_group, $id_country, $id_state, $zipcode) + { + Tools::displayAsDeprecated(); + $rate = 0; + foreach (TaxRulesGroup::getTaxes($id_tax_rules_group, $id_country, $id_state, $zipcode) AS $tax) + $rate += (float)$tax->rate; + + return $rate; + } + + /** + * Return taxes associated to this para + * @deprecated since 1.5 + */ + public static function getTaxes($id_tax_rules_group, $id_country, $id_state, $id_county) + { + Tools::displayAsDeprecated(); + return array(); + } + +} + diff --git a/classes/tax/TaxRulesTaxManager.php b/classes/tax/TaxRulesTaxManager.php new file mode 100644 index 000000000..9840a64d3 --- /dev/null +++ b/classes/tax/TaxRulesTaxManager.php @@ -0,0 +1,101 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class TaxRulesTaxManagerCore implements TaxManagerInterface +{ + public $address; + public $type; + public $tax_calculator; + + + public function __construct(Address $address, $type) + { + $this->address = $address; + $this->type = $type; + } + + /** + * Returns true if this tax manager is available for this address + * + * @return boolean + */ + public static function isAvailableForThisAddress(Address $address) + { + return true; // default manager, available for all addresses + } + + /** + * Return the tax calculator associated to this address + * + * @return TaxCalculator + */ + public function getTaxCalculator() + { + if (isset($this->tax_calculator)) + return $this->tax_calculator; + + $postcode = 0; + if (!empty($this->address->postcode)) + $postcode = $this->address->postcode; + + $rows = Db::getInstance()->ExecuteS(' + SELECT * + FROM `'._DB_PREFIX_.'tax_rule` + WHERE `id_country` = '.(int)$this->address->id_country.' + AND `id_tax_rules_group` = '.(int)$this->type.' + AND `id_state` IN (0, '.(int)$this->address->id_state.') + AND ('.pSQL($postcode).' BETWEEN `zipcode_from` AND `zipcode_to` OR `zipcode_from` = 0 OR `zipcode_from` = '.pSQL($postcode).') + ORDER BY `zipcode_from` DESC, `zipcode_to` DESC, `id_state` DESC, `id_country` DESC'); + + $behavior = 0; + $first_row = true; + $taxes_rates = array(); + + foreach ($rows as $row) + { + $tax = new Tax((int)$row['id_tax']); + + $taxes_rates[] = $tax->rate; + + // the applied behavior correspond to the most specific rules + if ($first_row) + { + $behavior = $row['behavior']; + $first_row = false; + } + + if ($row['behavior'] == 0) + break; + } + + $this->tax_calculator = new TaxCalculator($taxes_rates, $behavior); + + return $this->tax_calculator; + } +} + diff --git a/controllers/ProductController.php b/controllers/ProductController.php index 8423629a7..754329c37 100644 --- a/controllers/ProductController.php +++ b/controllers/ProductController.php @@ -49,7 +49,7 @@ class ProductControllerCore extends FrontController $this->addJS(_PS_JS_DIR_.'jquery/jquery.jqzoom.js'); } } - + public function canonicalRedirection() { // Automatically redirect to the canonical URL if the current in is the right one @@ -189,9 +189,9 @@ class ProductControllerCore extends FrontController $id_country = (int)($id_customer ? Customer::getCurrentCountry($id_customer) : Configuration::get('PS_COUNTRY_DEFAULT')); // Tax - $tax = (float)(Tax::getProductTaxRate((int)$this->product->id, $this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); + $tax = (float)$this->product->getTaxesRate(new Address((int)$this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); $this->context->smarty->assign('tax_rate', $tax); - + $productPriceWithTax = Product::getPriceStatic($this->product->id, true, NULL, 6); if (Product::$_taxCalculationMethod == PS_TAX_INC) $productPriceWithTax = Tools::ps_round($productPriceWithTax, 2); diff --git a/install-dev/php/remove_tab.php b/install-dev/php/remove_tab.php new file mode 100644 index 000000000..69666e70e --- /dev/null +++ b/install-dev/php/remove_tab.php @@ -0,0 +1,11 @@ +Execute(' + DELETE t, l + FROM `ps_tab` t LEFT JOIN `PREFIX_tab_lang` l ON (t.id_tab = l.id_tab) + WHERE t.`class_name` = '.pSQL($tabname)); +} + diff --git a/install-dev/php/update_tax_rules.php b/install-dev/php/update_tax_rules.php new file mode 100644 index 000000000..252c4dd60 --- /dev/null +++ b/install-dev/php/update_tax_rules.php @@ -0,0 +1,48 @@ +Execute(' + ALTER TABLE `'._DB_PREFIX_.'tax_rule` + ADD `zipcode_from` INT NOT NULL AFTER `id_state` , + ADD `zipcode_to` INT NOT NULL AFTER `zipcode_from` , + ADD `behavior` INT NOT NULL AFTER `zipcode_to`, + ADD `description` VARCHAR( 100 ) NOT NULL AFTER `id_tax`; + '); + + // Drop integrity constraint + Db::getInstance()->Execute(' + ALTER TABLE `'._DB_PREFIX_.'tax_rule` DROP INDEX tax_rule + '); + + // Create new format rules + Db::getInstance()->Execute(' + INSERT INTO `'._DB_PREFIX_.'tax_rule` (`id_tax_rules_group`, `id_country`, `id_state`, `id_tax`, `behavior`, `zipcode_from`, `zipcode_to`) + SELECT r.`id_tax_rules_group`, r.`id_country`, r.`id_state`, r.`id_tax`, 0, z.`from_zip_code`, z.`to_zip_code` + FROM `'._DB_PREFIX_.'tax_rule` r INNER JOIN `'._DB_PREFIX_.'county_zip_code` z ON (z.`id_county` = r.`id_county`) + '); + + // update behavior + Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'tax_rule` SET `behavior` = GREATEST(`state_behavior`, `county_behavior`); + '); + + + // Clean old entries + Db::getInstance()->Execute(' + DELETE FROM `'._DB_PREFIX_.'tax_rule` + WHERE `id_county` != 0 + AND `zipcode_from` = 0 + '); + + // Remove old columns + Db::getInstance()->Execute(' + ALTER TABLE `'._DB_PREFIX_.'tax_rule` + DROP `id_county`, + DROP `state_behavior`, + DROP `county_behavior` + '); +} + diff --git a/install-dev/sql/db.sql b/install-dev/sql/db.sql index 9d5bba38c..fa8171800 100644 --- a/install-dev/sql/db.sql +++ b/install-dev/sql/db.sql @@ -178,7 +178,7 @@ CREATE TABLE `PREFIX_cart` ( KEY `id_lang` (`id_lang`), KEY `id_currency` (`id_currency`), KEY `id_guest` (`id_guest`), - KEY `id_group_shop` (`id_group_shop`), + KEY `id_group_shop` (`id_group_shop`), KEY `id_shop` (`id_shop`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; @@ -324,7 +324,7 @@ CREATE TABLE `PREFIX_configuration_lang` ( CREATE TABLE `PREFIX_connections` ( `id_connections` int(10) unsigned NOT NULL auto_increment, - `id_group_shop` INT(11) UNSIGNED NOT NULL DEFAULT '1', + `id_group_shop` INT(11) UNSIGNED NOT NULL DEFAULT '1', `id_shop` INT(11) UNSIGNED NOT NULL DEFAULT '1', `id_guest` int(10) unsigned NOT NULL, `id_page` int(10) unsigned NOT NULL, @@ -442,7 +442,7 @@ CREATE TABLE `PREFIX_customer` ( KEY `customer_login` (`email`,`passwd`), KEY `id_customer_passwd` (`id_customer`,`passwd`), KEY `id_gender` (`id_gender`), - KEY `id_group_shop` (`id_group_shop`), + KEY `id_group_shop` (`id_group_shop`), KEY `id_shop` (`id_shop`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; @@ -487,7 +487,7 @@ CREATE TABLE `PREFIX_customer_thread` ( KEY `id_contact` (`id_contact`), KEY `id_customer` (`id_customer`), KEY `id_order` (`id_order`), - KEY `id_product` (`id_product`) + KEY `id_product` (`id_product`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; @@ -942,7 +942,7 @@ CREATE TABLE `PREFIX_orders` ( KEY `id_currency` (`id_currency`), KEY `id_address_delivery` (`id_address_delivery`), KEY `id_address_invoice` (`id_address_invoice`), - KEY `id_group_shop` (`id_group_shop`), + KEY `id_group_shop` (`id_group_shop`), KEY `id_shop` (`id_shop`), INDEX `date_add`(`date_add`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; @@ -1119,7 +1119,7 @@ CREATE TABLE `PREFIX_page_type` ( CREATE TABLE `PREFIX_page_viewed` ( `id_page` int(10) unsigned NOT NULL, - `id_group_shop` INT UNSIGNED NOT NULL DEFAULT '1', + `id_group_shop` INT UNSIGNED NOT NULL DEFAULT '1', `id_shop` INT UNSIGNED NOT NULL DEFAULT '1', `id_date_range` int(10) unsigned NOT NULL, `counter` int(10) unsigned NOT NULL, @@ -1637,14 +1637,14 @@ CREATE TABLE `PREFIX_tax_rule` ( `id_tax_rules_group` int(11) NOT NULL, `id_country` int(11) NOT NULL, `id_state` int(11) NOT NULL, - `id_county` int(11) NOT NULL, + `zipcode_from` INT NOT NULL, + `zipcode_to` INT NOT NULL, `id_tax` int(11) NOT NULL, - `state_behavior` int(11) NOT NULL, - `county_behavior` int(11) NOT NULL, + `behavior` int(11) NOT NULL, + `description` VARCHAR( 100 ) NOT NULL, PRIMARY KEY (`id_tax_rule`), KEY `id_tax_rules_group` (`id_tax_rules_group`), - KEY `id_tax` (`id_tax`), - UNIQUE KEY `tax_rule` (`id_tax_rules_group`, `id_country`, `id_state`, `id_county`) + KEY `id_tax` (`id_tax`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; CREATE TABLE `PREFIX_tax_rules_group` ( @@ -1688,23 +1688,6 @@ CREATE TABLE `PREFIX_import_match` ( PRIMARY KEY (`id_import_match`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; - -CREATE TABLE `PREFIX_county` ( - `id_county` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(64) NOT NULL, - `id_state` int(11) NOT NULL, - `active` tinyint(1) NOT NULL, - PRIMARY KEY (`id_county`) -) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8 ; - - -CREATE TABLE `PREFIX_county_zip_code` ( - `id_county` INT NOT NULL , - `from_zip_code` INT NOT NULL , - `to_zip_code` INT NOT NULL , - PRIMARY KEY ( `id_county` , `from_zip_code` , `to_zip_code` ) -) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; - CREATE TABLE `PREFIX_address_format` ( `id_country` int(10) unsigned NOT NULL, `format` varchar(255) NOT NULL DEFAULT '', diff --git a/install-dev/sql/db_settings_lite.sql b/install-dev/sql/db_settings_lite.sql index 00ca26aca..82cef4119 100644 --- a/install-dev/sql/db_settings_lite.sql +++ b/install-dev/sql/db_settings_lite.sql @@ -66,7 +66,8 @@ INSERT INTO `PREFIX_hook` (`id_hook`, `name`, `title`, `description`, `position` (63, 'beforeAuthentication', 'Before Authentication', 'Before authentication', 0, 0), (64, 'paymentTop', 'Top of payment page', 'Top of payment page', 0, 0), (65, 'afterCreateHtaccess', 'After htaccess creation', 'After htaccess creation', 0, 0), -(66, 'afterSaveAdminMeta', 'After save configuration in AdminMeta', 'After save configuration in AdminMeta', 0, 0); +(66, 'afterSaveAdminMeta', 'After save configuration in AdminMeta', 'After save configuration in AdminMeta', 0, 0), +(67, 'taxManager', 'Tax Manager Factory', '' , 0, 0); INSERT INTO `PREFIX_configuration` (`id_configuration`, `name`, `value`, `date_add`, `date_upd`) VALUES (1, 'PS_LANG_DEFAULT', '1', NOW(), NOW()), @@ -1180,7 +1181,7 @@ INSERT INTO `PREFIX_address_format` (`id_country`, `format`) UPDATE `PREFIX_address_format` set `format`='firstname lastname company address1 address2 -city, State:name postcode +city, State:name postcode Country:name phone' where `id_country`=21; @@ -1199,3 +1200,4 @@ postcode city State:name Country:name phone' where `id_country`=10; + diff --git a/install-dev/sql/upgrade/1.5.0.1.sql b/install-dev/sql/upgrade/1.5.0.1.sql index 24603b766..65a786574 100644 --- a/install-dev/sql/upgrade/1.5.0.1.sql +++ b/install-dev/sql/upgrade/1.5.0.1.sql @@ -22,4 +22,20 @@ INSERT INTO `PREFIX_module_access` (`id_profile`, `id_module`, `configure`, `vie AND a.`view` = 1 ); -UPDATE `PREFIX_tab` SET `class_name` = 'AdminThemes' WHERE `class_name` = 'AdminAppearance'; \ No newline at end of file +UPDATE `PREFIX_tab` SET `class_name` = 'AdminThemes' WHERE `class_name` = 'AdminAppearance'; + +INSERT INTO `PREFIX_hook` ( +`name` , +`title` , +`description` , +`position` , +`live_edit` +) +VALUES ('taxmanager', 'taxmanager', NULL , '1', '0'); + +/* PHP:update_tax_rules(); */; + +/* PHP:remove_tab(AdminCounty); */; +DROP TABLE `PREFIX_county_zip_code`; +DROP TABLE `PREFIX_county`; + diff --git a/install-dev/xml/doUpgrade.php b/install-dev/xml/doUpgrade.php index dbf90cf7d..812501710 100644 --- a/install-dev/xml/doUpgrade.php +++ b/install-dev/xml/doUpgrade.php @@ -30,7 +30,7 @@ $engineType = 'ENGINE_TYPE'; if (function_exists('date_default_timezone_set')) date_default_timezone_set('Europe/Paris'); - + // if _PS_ROOT_DIR_ is defined, use it instead of "guessing" the module dir. if (defined('_PS_ROOT_DIR_') AND !defined('_PS_MODULE_DIR_')) define('_PS_MODULE_DIR_', _PS_ROOT_DIR_.'/modules/'); @@ -133,6 +133,10 @@ require_once(_PS_INSTALLER_PHP_UPGRADE_DIR_.'create_multistore.php'); require_once(_PS_INSTALLER_PHP_UPGRADE_DIR_.'add_order_state.php'); +require_once(_PS_INSTALLER_PHP_UPGRADE_DIR_.'update_tax_rules.php'); + +require_once(_PS_INSTALLER_PHP_UPGRADE_DIR_.'remove_tab.php'); + //old version detection global $oldversion, $logger; $oldversion = false; @@ -313,9 +317,9 @@ if ($confFile->error != false) // Settings updated, compile and cache directories must be emptied $arrayToClean = array( - INSTALL_PATH.'/../tools/smarty/cache/', - INSTALL_PATH.'/../tools/smarty/compile/', - INSTALL_PATH.'/../tools/smarty_v2/cache/', + INSTALL_PATH.'/../tools/smarty/cache/', + INSTALL_PATH.'/../tools/smarty/compile/', + INSTALL_PATH.'/../tools/smarty_v2/cache/', INSTALL_PATH.'/../tools/smarty_v2/compile/'); foreach ($arrayToClean as $dir) if (!file_exists($dir)) diff --git a/modules/vatnumber/VATNumberTaxManager.php b/modules/vatnumber/VATNumberTaxManager.php new file mode 100755 index 000000000..b558b1e16 --- /dev/null +++ b/modules/vatnumber/VATNumberTaxManager.php @@ -0,0 +1,43 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class VATNumberTaxManager implements TaxManagerInterface +{ + public static function isAvailableForThisAddress(Address $address) + { + return (!empty($address->vat_number) + AND $address->id_country != Configuration::get('VATNUMBER_COUNTRY') + AND Configuration::get('VATNUMBER_MANAGEMENT')); + } + + public function getTaxCalculator() + { + // No tax + return new TaxCalculator(array(0)); + } +} + diff --git a/modules/vatnumber/vatnumber.php b/modules/vatnumber/vatnumber.php index 260aa586b..f36ab48fb 100755 --- a/modules/vatnumber/vatnumber.php +++ b/modules/vatnumber/vatnumber.php @@ -1,6 +1,6 @@ version = 1.0; $this->author = 'PrestaShop'; $this->need_instance = 0; - + + $this->tax_manager_class = 'VATNumberTaxManager'; + parent::__construct(); - + $this->displayName = $this->l('European VAT number'); $this->description = $this->l('Enable entering of the VAT intra-community number when creating the address (You must fill in the company field to allow keyboarding VAT number)'); } - + public function install() { return (parent::install() AND Configuration::updateValue('VATNUMBER_MANAGEMENT', 1)); } - + public function uninstall() { return (parent::uninstall() AND Configuration::updateValue('VATNUMBER_MANAGEMENT', 0)); } - + public function enable($forceAll = false) { parent::enable(); Configuration::updateValue('VATNUMBER_MANAGEMENT', 1); } - + public function disable($forceAll = false) { parent::disable(); Configuration::updateValue('VATNUMBER_MANAGEMENT', 0); } - + public static function getPrefixIntracomVAT() { $intracom_array = array( @@ -95,13 +97,13 @@ class VatNumber extends Module 'SK'=>'SK', //Slovakia 'CZ'=>'CZ', //Czech Republic 'SI'=>'SI', //Slovenia - 'RO'=>'RO', //Romania - 'BG'=>'BG' //Bulgaria + 'RO'=>'RO', //Romania + 'BG'=>'BG' //Bulgaria ); return $intracom_array; } - public static function isApplicable($id_country) + public static function isApplicable($id_country) { return (((int)$id_country AND in_array(Country::getIsoById($id_country), self::getPrefixIntracomVAT())) ? 1 : 0); } @@ -144,7 +146,7 @@ class VatNumber extends Module public function getContent() { $echo = ''; - + if (Tools::isSubmit('submitVatNumber')) { if (Configuration::updateValue('VATNUMBER_COUNTRY', (int)(Tools::getValue('vatnumber_country')))) @@ -178,6 +180,4 @@ class VatNumber extends Module
    '; return $echo; } -} - - +} \ No newline at end of file