diff --git a/CHANGE_LOG.txt b/CHANGE_LOG.txt new file mode 100644 index 0000000..b8cbe60 --- /dev/null +++ b/CHANGE_LOG.txt @@ -0,0 +1,6 @@ +2.0.4 - Removed: country translations + - Added: Widget v6 support + - Added: possibility to choose from external pickup points + - Added: Journal 3 one page checkout support + - Added: support for entering decimal values in weight rules + - Added: OCMOD compatibility - installation using installer made possible diff --git a/README.md b/README.md index 76c725a..3ed7662 100644 --- a/README.md +++ b/README.md @@ -3,43 +3,9 @@ # Module for Opencart 3 ## Download link -[Download version 2.0.3](https://github.com/Zasilkovna/opencart3/archive/v2.0.3.zip) +[Download version 2.0.4](https://github.com/Zasilkovna/opencart3/archive/v2.0.4.zip) -### Installation -1. Copy the **admin** and **catalog** directories from the archive to the root directory of your opencart store. -2. Log in to Administration, go to Extensions »Extensions page and select the extension type **Shipping**. - -![screen1](https://raw.githubusercontent.com/Zasilkovna/opencart3/master/doc/img/01-extensions.png) - -3. Install the module by clicking the green (+) button. -4. After installing the module, click the blue **edit** button. - -![screen2](https://raw.githubusercontent.com/Zasilkovna/opencart3/master/doc/img/02-extensions-zasilkovna.png) - -- On the General Module Configuration page, complete all fields - - **API key** - you can find it in the [client section » Client support](https://client.packeta.com/en/support/) - - **Max. weight** - for orders with a higher weight, the shipping method will not be offered in the shopping cart - - **Default shipping cost** - basic shipping cost, you can define shipping prices for specific countries - and weights below - - **E-shop identifier** - identification of the sender which you have set in the client section of your [sender](https://client.packeta.com/en/senders/). - - Enable or disable the module - - - -5. Next, add the transport rules by clicking on the "Edit" link. Enter the shipping cost and free shipping limit here -for each country. - - - -6. Click the weight icon to enter the weighting rules for the selected country. Here you can select the shipping price -for the selected weight range. - - - -7. The list of orders for which customers choose to transport via Packeta can be found in the main menu **Sales » Mail Order -Orders**. Here you can export the marked orders to a csv file, which you then upload to the client section » [Parcels Import](https://client.packeta.com/en/packets-import/upload). - -### Informations about the module +### Information about the module #### Supported languages: - czech @@ -47,52 +13,23 @@ Orders**. Here you can export the marked orders to a csv file, which you then up #### Supported versions: - Opencart 3.0.0 and newer +- php 5.6 - 7.x #### Features provided: -- Widget integration in eshop cart -- Set different prices for different target countries -- Setting prices according to weighting rules -- Traffic tax and GEO zone settings -- Free shipping from the specified price or weight of the order -- Export shipments to a csv file that can be imported in the client section +- widget v6 integration in the eshop cart +- support for pickup points of external carriers +- set different prices for different target countries +- setting prices according to weighting rules +- traffic tax and GEO zone settings +- free shipping from the specified price or weight of the order +- export shipments to a csv file that can be imported in the client section +- Journal 3 one page checkout support # Modul pro Opencart 3 ### Stažení modulu -[Aktuální verze 2.0.3](https://github.com/Zasilkovna/opencart3/archive/v2.0.3.zip) - -## Instalace -1. Adresáře **admin** a **catalog** z archivu nakopírujte do kořenového adresáře vašeho obchodu opencart. -2. Přihlašte se do administrace, přejděte na stránku Extensions » Extensions a vyberte typ rozšíření **Shipping**. - -

- -3. Instalujte modul kliknutím na zelené tlačítko (+). -4. Po nainstalování modulu klikněte na modré tlačítko **edit** - -

- -Na stránce obecná konfigurace modulu vyplňte všechna pole: - -- **API klíč** - naleznete jej v [klientské sekci](https://client.packeta.com/cs/support/) » Klientská podpora -- **Max. hmotnost** - u objednávek s větší hmotnostní nebude v košíku přepravní metoda Zásilkovna nabízena -- **Výchozí cena dopravy** - základní cena dopravy, pro konkrétní země a hmotnosti můžete ceny dopravy definovat níže -- **Identifikátor eshopu** - označení odesílatele které máte nastaveno v klientské sekci u vašeho [odesílatele](https://client.packeta.com/cs/senders/). -- Povolte nebo zakažte modul - -

- -5. Dále přidáme přepravní pravidla kliknutím na odkaz *Edit*. Pro jednotlivé země zde zadáte cenu přepravy a limit pro dopravu zdarma. - -

- -6. Kliknutím na ikonu váhy zadáte váhová pravidla pro vybranou zemi. Můžete zde zvolit cenu přepravy pro zvolené váhové rozmezí. - -

+[Aktuální verze 2.0.4](https://github.com/Zasilkovna/opencart3/archive/v2.0.4.zip) -Seznam objednávek u kterých si zákazníci zvolí dopravu přes Zásilkovnu najdete v hlavním menu **Sales » Zásilkovna Orders**. -Zde můžete označené objednávky exportovat do csv souboru, který poté nahrajete do klientské sekce » [Import zásilek](https://client.packeta.com/cs/packets-import/upload). - ### Informace o modulu #### Podporované jazyky: @@ -103,12 +40,15 @@ Zde můžete označené objednávky exportovat do csv souboru, který poté nahr #### Podporované verze: - Opencart 3.0.0 a novější +- php 5.6 - 7.x #### Poskytované funkce: -- Integrace widgetu v košíku eshopu -- Nastavení různé ceny pro různé cílové země -- Nastavení cen podle váhových pravidel -- Nastavení daně dopravy a GEO zóny -- Doprava zdarma od zadané ceny nebo hmotnosti objednávky -- Export zásilek do csv souboru, který lze importovat v [klientské sekci](https://client.packeta.com/) +- integrace widgetu v6 v košíku eshopu +- podpora výdejních míst externích dopravců +- nastavení různé ceny pro různé cílové země +- nastavení cen podle váhových pravidel +- nastavení daně dopravy a GEO zóny +- doprava zdarma od zadané ceny nebo hmotnosti objednávky +- export zásilek do csv souboru, který lze importovat v [klientské sekci](https://client.packeta.com/) +- podpora jednokrokového košíku Journal 3 diff --git a/catalog/controller/extension/module/zasilkovna.php b/catalog/controller/extension/module/zasilkovna.php deleted file mode 100644 index 34e70ed..0000000 --- a/catalog/controller/extension/module/zasilkovna.php +++ /dev/null @@ -1,62 +0,0 @@ -load->model('extension/shipping/zasilkovna'); - $defaults = $this->model_extension_shipping_zasilkovna->loadSelectedBranch(); - - $this->response->addHeader('Content-Type: application/json'); - $this->response->setOutput(json_encode($defaults)); - } - - /** - * Loads properties of selected branch from session. - * - * @throws Exception - */ - public function saveSelectedBranch() { - $this->load->model('extension/shipping/zasilkovna'); - $this->model_extension_shipping_zasilkovna->saveSelectedBranch(); - } - - /** - * Save additional order data to DB during "order confirm". - * All required records with order data are created in DB during this step. - * This method is called by "after" event on catalog/controller/checkout/confirm. - * - * @param string $route - * @param array $args - * @param int $output - * @throws Exception - */ - public function saveOrderData(&$route, &$args, &$output) { - $this->load->model('extension/shipping/zasilkovna'); - $this->model_extension_shipping_zasilkovna->saveOrderData(); - } - - /** - * Clean-up of additional order data in session when order is finished. - * This method is called as "before" event on catalog/controller/checkout/success. - * - * @param string $route - * @param array $args - * @return void - * @throws Exception - */ - public function sessionCleanup(&$route, &$args) { - $this->load->model('extension/shipping/zasilkovna'); - $this->model_extension_shipping_zasilkovna->sessionCleanup(); - } -} \ No newline at end of file diff --git a/catalog/language/en-gb/extension/shipping/zasilkovna.php b/catalog/language/en-gb/extension/shipping/zasilkovna.php deleted file mode 100644 index 4a305dc..0000000 --- a/catalog/language/en-gb/extension/shipping/zasilkovna.php +++ /dev/null @@ -1,13 +0,0 @@ -load->model(self::ROUTING_BASE_PATH); - $this->model_extension_shipping_zasilkovna->createTablesAndEvents(); - - // prefill default configuration items - $defaultConfig = [ - 'shipping_zasilkovna_weight_max' => '5', - 'shipping_zasilkovna_geo_zone_id' => '', - 'shipping_zasilkovna_order_statuses' => [], - 'shipping_zasilkovna_cash_on_delivery_methods' => [] - ]; - $this->load->model('setting/setting'); - $this->model_setting_setting->editSetting('shipping_zasilkovna', $defaultConfig); - - } - - /** - * Entry point (main method) for plugin uninstalling. - * - * @throws Exception - */ - public function uninstall() { - $this->load->model(self::ROUTING_BASE_PATH); - $this->model_extension_shipping_zasilkovna->deleteTablesAndEvents(); - } - - /** - * Handler for main action of extension modul for Zasilkovna (main settings page). - * - * @throws Exception - */ - public function index() { - // save new values from POST request data to module settings - if (($this->request->server['REQUEST_METHOD'] == 'POST') && ($this->checkPermissions())) { - $this->load->language(self::ROUTING_BASE_PATH); - $this->load->model('setting/setting'); - $this->model_setting_setting->editSetting('shipping_zasilkovna', $this->request->post); - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink('marketplace/extension', ['type' => 'shipping'])); - } - - // full initialization of page - $data = $this->initPageData('', self::TEXT_TITLE_MAIN); - - // load data for list of weight rules - $this->load->model(self::ROUTING_WEIGHT_RULES); - $weightRules = $this->model_extension_shipping_zasilkovna_weight_rules->getAllRules(); - $usedCountries = array_keys($weightRules); - - // load data for list of shipping rules - $this->load->model(self::ROUTING_SHIPPING_RULES); - $shippingRules = $this->model_extension_shipping_zasilkovna_shipping_rules->getAllRules(); - - // adding additional data for list of shipping rules - foreach ($shippingRules as $ruleId => $ruleContent) { - // name of country - $shippingRules[$ruleId]['country_name'] = $this->language->get('country_' . $ruleContent['target_country']); - // print message "not set" if default price or free shipping limit is not set - if (empty($ruleContent['default_price'])) { - $shippingRules[$ruleId]['default_price'] = $this->language->get('entry_sr_not_set'); - } - if (empty($ruleContent['free_over_limit'])) { - $shippingRules[$ruleId]['free_over_limit'] = $this->language->get('entry_sr_not_set'); - } - // link to shipping rule editor - $shippingRules[$ruleId][self::TEMPLATE_LINK_EDIT] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, - [self::PARAM_RULE_ID => $ruleContent['rule_id']]); - // link to list of weight rules - $shippingRules[$ruleId]['link_weight_rules'] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, - [self::PARAM_COUNTRY => $ruleContent['target_country']]); - - if (in_array($ruleContent['target_country'], $usedCountries)) { - $shippingRules[$ruleId]['weight_rules_description'] = $this->language->get('text_weight_rules_defined'); - $shippingRules[$ruleId]['weight_rules_tooltip'] = $this->language->get('help_weight_rules_change'); - } - else { - $shippingRules[$ruleId]['weight_rules_description'] = $this->language->get('text_weight_rules_missing'); - $shippingRules[$ruleId]['weight_rules_tooltip'] = $this->language->get('help_weight_rules_creation'); - } - } - $data['shipping_rules'] = $shippingRules; - $data['link_shipping_rules'] = $this->createAdminLink(self::ACTION_SHIPPING_RULES); - - // adding additional data for displaying to user in list of weight rules - foreach ($usedCountries as $countryCode) { - $weightRules[$countryCode]['country_name'] = $this->language->get('country_' . $countryCode); - $weightRules[$countryCode][self::TEMPLATE_LINK_EDIT] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); - } - $data['weight_rules'] = $weightRules; - - $this->setGlobalConfigurationForm($data); - - $this->response->setOutput($this->load->view(self::ROUTING_BASE_PATH, $data)); - } - - /** - * Set items of global configuration to template data. - * - * @throws Exception - * @var array $data page template data - */ - private function setGlobalConfigurationForm(&$data) { - $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(''); - $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink('marketplace/extension', ['type' => 'shipping']); - - // loads list of secondary stores - $this->load->model('setting/store'); - $secondaryStores = $this->model_setting_store->getStores(); - - // loads values for global settings from POST request data or from module configuration - $configurationItems = [ - 'shipping_zasilkovna_api_key', - 'shipping_zasilkovna_tax_class_id', - 'shipping_zasilkovna_weight_max', - 'shipping_zasilkovna_default_free_shipping_limit', - 'shipping_zasilkovna_default_shipping_price', - 'shipping_zasilkovna_status', - 'shipping_zasilkovna_sort_order', - 'shipping_zasilkovna_geo_zone_id', - 'shipping_zasilkovna_order_statuses', - 'shipping_zasilkovna_cash_on_delivery_methods', - 'shipping_zasilkovna_eshop_identifier_0' // default store always exists - ]; - - // adds form items for e-shop identifiers for secondary stores - foreach ($secondaryStores as $storeProperties) { - $configurationItems[] = 'shipping_zasilkovna_eshop_identifier_' . $storeProperties['store_id']; - } - - foreach ($configurationItems as $itemName) { - if (isset($this->request->post[$itemName])) { - $data[$itemName] = $this->request->post[$itemName]; - } - else { - $data[$itemName] = $this->config->get($itemName); - } - } - - // loads list of tax classes and geo zones defined in administration - $this->load->model('localisation/tax_class'); - $data['tax_classes'] = $this->model_localisation_tax_class->getTaxClasses(); - $this->load->model('localisation/geo_zone'); - $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones(); - - // loads list of defined order statuses - $this->load->model('localisation/order_status'); - $data['eshop_order_statuses'] = $this->model_localisation_order_status->getOrderStatuses(); - - // loads list of installed payment methods - $this->load->model(self::ROUTING_BASE_PATH); - $data['payment_methods'] = $this->model_extension_shipping_zasilkovna->getInstalledPaymentMethods(); - - // creates list of store names for e-shop identifier items - $data['store_list'] = []; - $data['store_list'][] = [ - 'id' => 0, - 'name' => $this->config->get('config_name'), - 'identifier' => $data['shipping_zasilkovna_eshop_identifier_0'] - ]; - foreach ($secondaryStores as $storeProperies) { - $data['store_list'][] = [ - 'id' => $storeProperties['store_id'], - 'name' => $storeProperties['name'], - 'identifier' => $data['shipping_zasilkovna_eshop_identifier_' . $storeProperties['store_id']] - ]; - } - } - - /** - * Handler for show weight rules for given country. - * @throws Exception - */ - public function weight_rules() { // method name with underscore is required for correct routing - $this->checkCountryCode(); - $countryCode = $this->request->get[self::PARAM_COUNTRY]; - - $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); - - $this->load->model(self::ROUTING_WEIGHT_RULES); - $data[self::TEMPLATE_LINK_ADD] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_ADD, [self::PARAM_COUNTRY => $countryCode]); - $data[self::TEMPLATE_LINK_DELETE] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_DELETE, [self::PARAM_COUNTRY => $countryCode]); - $data[self::TEMPLATE_LINK_BACK] = $this->createAdminLink(''); - $data['text_country_name'] = $this->language->get('country_' . $countryCode); - - $weightRules = $this->model_extension_shipping_zasilkovna_weight_rules->getRulesForCountry($countryCode); - foreach ($weightRules as $rule) { - $data['weight_rules'][] = [ - 'rule_id' => $rule['rule_id'], - 'min_weight' => $rule['min_weight'], - 'max_weight' => $rule['max_weight'], - 'price' => $rule['price'], - self::TEMPLATE_LINK_EDIT => $this->createAdminLink(self::ACTION_WEIGHT_RULES_EDIT, - [self::PARAM_COUNTRY => $countryCode, self::PARAM_RULE_ID => $rule['rule_id']]) - ]; - } - - $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_weight_rules', $data)); - } - - /** - * Handler for creation of new weight rule. - * @throws Exception - */ - public function weight_rules_add() { // method name with underscore is required for correct routing - $this->checkCountryCode(); - $countryCode = $this->request->get[self::PARAM_COUNTRY]; - - $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); - - // check if http method is POST (save of data from form) - if ($this->request->server['REQUEST_METHOD'] === 'POST') { - $this->load->model(self::ROUTING_WEIGHT_RULES); - $errorMessage = $this->model_extension_shipping_zasilkovna_weight_rules->addRule($this->request->post, $countryCode); - if (empty($errorMessage)) { - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); - } - else { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); - } - } - - $this->setWeightRuleFormContent($data, $countryCode); - } - - /** - * Handler for edit of existing weight rule. - * @throws Exception - */ - public function weight_rules_edit() { // method name with underscore is required for correct routing - $this->checkCountryCode(); - - if (!isset($this->request->get['rule_id'])) { - $this->load->language(self::ROUTING_BASE_PATH); - $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); - $this->response->redirect($this->createAdminLink('')); - } - - $countryCode = $this->request->get[self::PARAM_COUNTRY]; - $ruleId = $this->request->get[self::PARAM_RULE_ID]; - - $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); - - // check if http method is POST (save of data from form) - if ($this->request->server['REQUEST_METHOD'] === 'POST') { - $this->load->model(self::ROUTING_WEIGHT_RULES); - $errorMessage = $this->model_extension_shipping_zasilkovna_weight_rules->editRule($ruleId, $this->request->post, $countryCode); - if (empty($errorMessage)) { - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); - } - else { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); - } - } - - $this->setWeightRuleFormContent($data, $countryCode, $ruleId); - } - - /** - * Handler for delete of selected weight rules. - * - * @throws Exception - */ - public function weight_rules_delete() { // method name with underscore is required for correct routing - $this->checkCountryCode(); - $this->load->language(self::ROUTING_BASE_PATH); - $countryCode = $this->request->get[self::PARAM_COUNTRY]; - - if (!empty($this->request->post['selected'])) { - $this->load->model(self::ROUTING_WEIGHT_RULES); - $this->model_extension_shipping_zasilkovna_weight_rules->deleteRules($this->request->post['selected']); - } - - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); - } - - /** - * Set of form content for weight rule editor. Common part for "add" and "edit" action. - * - * @throws Exception - * - * @var array $data data for page template - * @var string $countryCode iso country code of target country - * @var int $ruleId internal ID of processed rule (0 for adding a new rule) - */ - private function setWeightRuleFormContent(array $data, $countryCode, $ruleId = 0) { - $isEdit = ($ruleId !== 0); - - if ($this->request->server['REQUEST_METHOD'] === 'POST') { // load data from POST request - $postData = $this->request->post; - $data['min_weight'] = $postData['min_weight']; - $data['max_weight'] = $postData['max_weight']; - $data['price'] = $postData['price']; - } - else if ($isEdit) { // load data from DB - $this->load->model(self::ROUTING_WEIGHT_RULES); - $rowData = $this->model_extension_shipping_zasilkovna_weight_rules->getRule($ruleId); - if (!empty($rowData)) { - $data['min_weight'] = $rowData['min_weight']; - $data['max_weight'] = $rowData['max_weight']; - $data['price'] = $rowData['price']; - } - } - - $data['text_form_title'] = $this->language->get($isEdit ? 'text_edit_weight_rule' : 'text_new_weight_rule'); - if ($isEdit) { - $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_EDIT, - [self::PARAM_COUNTRY => $countryCode, self::PARAM_RULE_ID => $ruleId]); - } - else { - $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_ADD, [self::PARAM_COUNTRY => $countryCode]); - } - $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); - - $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_weight_rules_form', $data)); - } - - /** - * Handler for list of shipping rules for given country. - * @throws Exception - */ - public function shipping_rules() { // method name with underscore is required for correct routing - $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_SHIPPING_RULES); - - $this->load->model(self::ROUTING_SHIPPING_RULES); - $data[self::TEMPLATE_LINK_ADD] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_ADD); - $data[self::TEMPLATE_LINK_DELETE] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_DELETE); - $data[self::TEMPLATE_LINK_BACK] = $this->createAdminLink(''); - - $shippingRules = $this->model_extension_shipping_zasilkovna_shipping_rules->getAllRules(); - foreach ($shippingRules as $rule) { - $data['shipping_rules'][] = [ - 'rule_id' => $rule['rule_id'], - 'target_country_name' => $this->language->get('country_' . $rule['target_country']), - 'default_price' => (empty($rule['default_price']) ? $this->language->get('entry_sr_not_set') : $rule['default_price']) , - 'free_over_limit' => (empty($rule['free_over_limit']) ? $this->language->get('entry_sr_not_set') : $rule['free_over_limit']), - 'is_enabled' => $rule['is_enabled'], - self::TEMPLATE_LINK_EDIT => $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, - [self::PARAM_RULE_ID => $rule['rule_id']]) - ]; - } - - $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_shipping_rules_list', $data)); - } - - /** - * Handler for creation of new shipping rule. - * @throws Exception - */ - public function shipping_rules_add() { // method name with underscore is required for correct routing - $data = $this->initPageData(self::ACTION_SHIPPING_RULES, self::TEXT_TITLE_SHIPPING_RULES); - - // check if http method is POST (save of data from form) - if ($this->request->server['REQUEST_METHOD'] === 'POST') { - $this->load->model(self::ROUTING_SHIPPING_RULES); - $errorMessage = $this->model_extension_shipping_zasilkovna_shipping_rules->checkRuleData($this->request->post); - if (empty($errorMessage)) { - $this->model_extension_shipping_zasilkovna_shipping_rules->addRule($this->request->post); - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); - } - else { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); - } - } - - $this->setShippingRuleFormContent($data); - } - - /** - * Handler for edit of existing shipping rule. - * @throws Exception - */ - public function shipping_rules_edit() { // method name with underscore is required for correct routing - if (!isset($this->request->get[self::PARAM_RULE_ID])) { - $this->load->language(self::ROUTING_BASE_PATH); - $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); - $this->response->redirect($this->createAdminLink('')); - } - - $ruleId = $this->request->get[self::PARAM_RULE_ID]; - $data = $this->initPageData(self::ACTION_SHIPPING_RULES, self::TEXT_TITLE_SHIPPING_RULES); - - // check if http method is POST (save of data from form) - if ($this->request->server['REQUEST_METHOD'] === 'POST') { - $this->load->model(self::ROUTING_SHIPPING_RULES); - $errorMessage = $this->model_extension_shipping_zasilkovna_shipping_rules->editRule($ruleId, $this->request->post); - if (empty($errorMessage)) { - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); - } - else { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); - } - } - - $this->setShippingRuleFormContent($data, $ruleId); - } - - /** - * Handler for delete of selected shipping rules. - * - * @throws Exception - */ - public function shipping_rules_delete() { // method name with underscore is required for correct routing - $this->load->language(self::ROUTING_BASE_PATH); - - if (!empty($this->request->post['selected'])) { - $this->load->model(self::ROUTING_SHIPPING_RULES); - $this->model_extension_shipping_zasilkovna_shipping_rules->deleteRules($this->request->post['selected']); - } - - $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); - $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); - } - - /** - * Set of form content for shipping rule editor. Common part for "add" and "edit" action. - * - * @throws Exception - * - * @var array $data data for page template - * @var int $ruleId internal ID of processed rule (0 for adding a new rule) - */ - private function setShippingRuleFormContent(array $data, $ruleId = 0) { - $isEdit = ($ruleId !== 0); - - if ($this->request->server['REQUEST_METHOD'] === 'POST') { // load data from POST request - $postData = $this->request->post; - $data['target_country'] = $postData['target_country']; - $data['default_price'] = $postData['default_price']; - $data['free_over_limit'] = $postData['free_over_limit']; - $data['is_enabled'] = $postData['is_enabled']; - } - else if ($isEdit) { // load data from DB - $this->load->model(self::ROUTING_SHIPPING_RULES); - $rowData = $this->model_extension_shipping_zasilkovna_shipping_rules->getRule($ruleId); - if (!empty($rowData)) { - $data['target_country'] = $rowData['target_country']; - $data['default_price'] = $rowData['default_price']; - $data['free_over_limit'] = $rowData['free_over_limit']; - $data['is_enabled'] = $rowData['is_enabled']; - } - } - - // creation of localized list of allowed countries - $countryList = []; - foreach (self::ALLOWED_COUNTRIES as $countryCode) { - $countryList[] = [ - 'code' => $countryCode, - 'name' => $this->language->get('country_' . $countryCode) - ]; - } - $data['countries'] = $countryList; - - // set description text and links for form - $data['text_form_title'] = $this->language->get($isEdit ? 'text_edit_shipping_rule' : 'text_new_shipping_rule'); - if ($isEdit) { - $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, - [self::PARAM_RULE_ID => $ruleId]); - } - else { - $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_ADD); - } - $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink(self::ACTION_SHIPPING_RULES); - - $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_shipping_rules_form', $data)); - } - - /** - * Extension of menu in administration. Adds new item with list of Zasilkovna orders to menu "Sales". - * This method is called by "before" event on admin/view/common/column_left/before. - * - * @param string $route routing path of page - * @param array $data template parameters - * @param StdClass $template instance of page template - * @throws Exception - */ - public function adminMenuExtension(&$route, &$data, &$template) { - if (!$this->user->hasPermission('access', self::ROUTING_BASE_PATH)) { - return; - } - - foreach ($data['menus'] as &$menu) { - if ($menu['id'] != 'menu-sale') { - continue; - } - - // load translations for Zasilkovna to separate language context - $this->load->language(self::ROUTING_BASE_PATH, 'zasilkovna'); - - // creation of new menu item for Zasilkovna - $newMenuItem = [ - 'name' => $this->language->get('zasilkovna')->get('text_menu_item'), - 'href' => $this->createAdminLink(self::ACTION_ORDERS) - ]; - array_push($menu['children'], $newMenuItem); - } - } - - /** - * Handler for list of "Zasilkovna" orders. - * - * @throws Exception - */ - public function orders() { - $this->load->language(self::ROUTING_BASE_PATH); - $this->load->model(self::ROUTING_ORDERS); - - // initialization of page data including setup of list parameters (filters, sorting, paging) - $data = $this->initPageData(self::ACTION_ORDERS, self::TEXT_TTILE_ORDERS); - $paramData = $this->model_extension_shipping_zasilkovna_orders->getUrlParameters(); - - // load list of order statuses and creation of array for translate ID to status description - $this->load->model('localisation/order_status'); - $orderStatusList = $this->model_localisation_order_status->getOrderStatuses(); - $data['order_statuses'] = $orderStatusList; - $orderStatusDescriptions = []; - foreach ($orderStatusList as $orderStatusItem) { - $orderStatusDescriptions[$orderStatusItem['order_status_id']] = $orderStatusItem['name']; - } - - // load list of payment methods considered as "cash on delivery" - $codPaymentMethods = (array)$this->config->get('shipping_zasilkovna_cash_on_delivery_methods'); - - // load count of orders and list of orders for current page - $orderCount = $this->model_extension_shipping_zasilkovna_orders->getOrdersCount($paramData['filterData']); - $dbOrderList = $this->model_extension_shipping_zasilkovna_orders->getOrders($paramData); - - // format list of orders for template - foreach ($dbOrderList as $order) { - $data['orders'][] = [ - 'order_id' => $order['order_id'], - 'customer' => $order['customer'], - 'order_status' => $orderStatusDescriptions[$order['order_status_id']], - 'total' => $this->currency->format($order['total'], $order['currency_code'], $order['currency_value']), - 'is_cod' => in_array($order['payment_code'], $codPaymentMethods), - 'date_added' => date($this->language->get('date_format_short'), strtotime($order['date_added'])), - 'branch_id' => $order['branch_id'], - 'branch_name' => $order['branch_name'], - 'exported' => !empty($order['exported']) ? date($this->language->get('date_format_short'), strtotime($order['exported'])) : '' - ]; - } - - // to keep selected rows as selected - if (isset($this->request->post['selected'])) { - $data['selected'] = (array)$this->request->post['selected']; - } else { - $data['selected'] = array(); - } - - // creation set of links for CSV export actions - $csvExportUrlParams = $paramData['filterData']; - $csvExportUrlParams['sort'] = $paramData['sort']; - $csvExportUrlParams['order'] = $paramData['order']; - $data[self::TEMPLATE_LINK_EXPORT_SELECTED] = $this->createAdminLink(self::ACTION_ORDERS_EXPORT, - array_merge($csvExportUrlParams, ['scope' => 'selected'])); - $data[self::TEMPLATE_LINK_EXPORT_ALL] = $this->createAdminLink(self::ACTION_ORDERS_EXPORT, - array_merge($csvExportUrlParams, ['scope' => 'all'])); - - // creation set of links to change grid sorting - $sortingUrlParams = []; - foreach ($paramData['filterData'] as $paramName => $paramValue) { - if (!empty($paramValue)) { - $sortingUrlParams[$paramName] = $paramValue; - } - } - $sortingUrlParams['page'] = $paramData['page']; - $sortingUrlParams['order'] = ($paramData['order'] == 'ASC') ? 'DESC' : 'ASC'; - - $data['link_sorting_order_id'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'o.order_id'])); - $data['link_sorting_customer'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'customer'])); - $data['link_sorting_order_status_id'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'order_status_id'])); - $data['link_sorting_order_total'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'o.total'])); - $data['link_sorting_order_date'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'date_added'])); - $data['link_sorting_branch_name'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'oz.branch_name'])); - $data['link_sorting_exported'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'exported'])); - - // current sorting properties - $data['sort'] = $paramData['sort']; - $data['order'] = $paramData['order']; - - // preparation of paging (switch between pages) - $pagingUrlParameters = []; - foreach ($paramData['filterData'] as $paramName => $paramValue) { - if (!empty($paramValue)) { - $pagingUrlParameters[$paramName] = $paramValue; - } - } - $pagingUrlParameters['sort'] = $paramData['sort']; - $pagingUrlParameters['order'] = $paramData['order']; - $pagingUrlParameters['page'] = '{page}'; - - $pageNumber = $paramData['page']; - $pagination = new Pagination(); - $pagination->total = $orderCount; - $pagination->page = $pageNumber; - $pagination->limit = $this->config->get('config_limit_admin'); - $pagination->url = $this->createAdminLink(self::ACTION_ORDERS, $pagingUrlParameters); - $data['pagination'] = $pagination->render(); - - // preparation of paging (current page info) - // string template: Showing %d to %d of %d (%d Pages) - $data['results'] = sprintf($this->language->get('text_pagination'), - ($orderCount) ? (($pageNumber - 1) * $this->config->get('config_limit_admin')) + 1 : 0, - ((($pageNumber - 1) * $this->config->get('config_limit_admin')) > ($orderCount - $this->config->get('config_limit_admin'))) ? $orderCount : ((($pageNumber - 1) * $this->config->get('config_limit_admin')) + $this->config->get('config_limit_admin')), - $orderCount, - ceil($orderCount / $this->config->get('config_limit_admin'))); - - // creation set of variables for default value of filters - foreach ($paramData['filterData'] as $paramName => $paramValue) { - $data[$paramName] = $paramValue; - } - - // items of selectbox for type of export - $data['export_types'] = [ - [ 'value' => 'not_exported', 'name' => $this->language->get('entry_ol_not_exported')], - [ 'value' => 'exported', 'name' => $this->language->get('entry_ol_exported')], - [ 'value' => 'all', 'name' => $this->language->get('entry_ol_all_records')] - ]; - - // add user token parameter for functionality of JS request (e.g. customer name autocomplete) - $data['user_token'] = $this->session->data['user_token']; - - $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_orders', $data)); - } - - /** - * Handler for export orders to CSV (all or selected orders). - * - * @throws Exception - */ - public function orders_export() { // method name with underscore is required for correct routing - $this->load->language(self::ROUTING_BASE_PATH); - $this->load->model(self::ROUTING_ORDERS); - - $paramData = $this->model_extension_shipping_zasilkovna_orders->getUrlParameters(); - $exportScope = $this->request->get['scope']; - // all parameters except list of "row" checkboxes are part of form action (get parameters) - $orderIdList = (isset($this->request->post['selected'])) ? $this->request->post['selected']: []; - - $this->load->model('setting/store'); - $csvRawData = $this->model_extension_shipping_zasilkovna_orders->getCsvExportData($paramData, $exportScope, $orderIdList); - - // open stdout as file and put content to it using native function for writing data in CSV format - $fileHandle = fopen('php://output', 'wb'); - ob_start(); - // first two lines are fixed header of file - fputcsv($fileHandle, ['version 5']); - fputcsv($fileHandle, []); - - foreach ($csvRawData as $rawRecord) { - fputcsv($fileHandle, $rawRecord); - } - $csvFileContent = ob_get_contents(); - ob_end_clean(); - - // set http headers for "force download" and file type - $this->response->addHeader('Content-Type: text/csv'); - $fileName = 'orders-' . date('Y-m-d-H-i-s') . '.csv'; - $this->response->addHeader('Content-Disposition: attachment; filename="' . $fileName . '"'); - - // send content of csv file as output - $this->response->setOutput($csvFileContent); - } - - /** - * Check if user has permission to change module settings. - * - * @return bool TRUE = success, FALSE = error - */ - private function checkPermissions() { - if (!$this->user->hasPermission('modify', self::ROUTING_BASE_PATH)) { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_permission'); - return false; - } - - return true; - } - - /** - * Check presence and content of url parameter for country. - * If parameter is missing or invalid, redirect to main setting page is performed. - * - * @return void - */ - private function checkCountryCode() { - // check if code of target country is part of url - if (empty($this->request->get[self::PARAM_COUNTRY])) { - $this->load->language(self::ROUTING_BASE_PATH); - $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); - $this->response->redirect($this->createAdminLink('')); - } - - // check if specified country is allowed - if (!in_array($this->request->get[self::PARAM_COUNTRY], self::ALLOWED_COUNTRIES)) { - $this->load->language(self::ROUTING_BASE_PATH); - $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_invalid_country'); - $this->response->redirect($this->createAdminLink('')); - } - } - - /** - * Method for page initialization. Returns customized base content of template data. - * - * @param string $actionName internal name of module action - * @param string $titleId language independent identifier of page title - * @param array $urlParameters additional parameters to url - * @return array initial version of template data - */ - private function initPageData($actionName, $titleId, $urlParameters = []) { - // load language file for module - $this->load->language(self::ROUTING_BASE_PATH); - // set page (document) title - $this->document->setTitle($this->language->get($titleId)); - - // creation of customized common part of template data - $data = [ - // common parts of page (header, left column with system menu, footer) - 'header' => $this->load->controller('common/header'), - 'column_left' => $this->load->controller('common/column_left'), - 'footer' => $this->load->controller('common/footer'), - 'breadcrumbs' => [ - [ - 'text' => $this->language->get('text_home'), - 'href' => $this->createAdminLink('common/dashboard') - ], - [ - 'text' => $this->language->get('text_shipping'), - 'href' => $this->createAdminLink('marketplace/extension', ['type' => 'shipping']) - ], - [ - 'text' => $this->language->get(self::TEXT_TITLE_MAIN), - 'href' => $this->createAdminLink('') - ] - ] - ]; - - // last part of "breadcrumbs" is added only for nonempty action name (pages of module) - if (!empty($actionName)) { - $data['breadcrumbs'][] = [ - 'text' => $this->language->get($titleId), - 'href' => $this->createAdminLink($actionName, $urlParameters) - ]; - } - - // check if some error/success message is stored in session and set it as template parameter - if (isset($this->session->data[self::TEMPLATE_MESSAGE_SUCCESS])) { - $data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS]; - - unset($this->session->data[self::TEMPLATE_MESSAGE_SUCCESS]); - } - if (isset($this->session->data[self::TEMPLATE_MESSAGE_ERROR])) { - $data[self::TEMPLATE_MESSAGE_ERROR] = $this->session->data[self::TEMPLATE_MESSAGE_ERROR]; - - unset($this->session->data[self::TEMPLATE_MESSAGE_ERROR]); - } - - return $data; - } - - /** - * Creates link to given action in administration including user token. - * - * @param string $actionName internal name of module action - * @param array $urlParameters additional parameters to url - * @return string - */ - private function createAdminLink($actionName, $urlParameters = []) - { - // empty action name => main page of module - if ('' == $actionName) { - $actionName = self::ROUTING_BASE_PATH; - } - - // action name without slash (/) => action of module - if (strpos($actionName, '/') === false) { - $actionName = self::ROUTING_BASE_PATH . '/' . $actionName; - } - - // otherwise action name is absolute routing path => no change in action name - // user token must be part of any administration link - $urlParameters['user_token'] = $this->session->data['user_token']; - - return $this->url->link($actionName, $urlParameters, true); - } - -} +load->model(self::ROUTING_BASE_PATH); + $this->model_extension_shipping_zasilkovna->createTablesAndEvents(); + + // prefill default configuration items + $defaultConfig = [ + 'shipping_zasilkovna_version' => self::VERSION, + 'shipping_zasilkovna_weight_max' => '5', + 'shipping_zasilkovna_geo_zone_id' => '', + 'shipping_zasilkovna_order_statuses' => [], + 'shipping_zasilkovna_cash_on_delivery_methods' => [] + ]; + + $this->load->model('setting/setting'); + $this->model_setting_setting->editSetting('shipping_zasilkovna', $defaultConfig); + } + + /** + * @return array + */ + private function getSettings() + { + $this->load->model('setting/setting'); + return $this->model_setting_setting->getSetting('shipping_zasilkovna'); + } + + /** + * @return string|null + */ + private function getSchemaVersion() + { + $this->load->model('setting/setting'); + $existingSettings = $this->getSettings(); + if ($existingSettings && $this->isInstalled()) { + if (!empty($existingSettings['shipping_zasilkovna_version'])) { + return $existingSettings['shipping_zasilkovna_version']; + } + + return '2.0.3'; + } + + return null; + } + + /** Does database version differ from code version? Downgrades not supported. + * @return bool + */ + private function isVersionMismatch() + { + $version = $this->getSchemaVersion(); + if ($version && version_compare($version, self::VERSION) < 0) { + return true; + } + + return false; + } + + /** Returns name of extension as its known to OpenCart + * @return string + */ + private function getExtensionName() + { + return basename(__FILE__, '.php'); + } + + /** + * @return bool + */ + private function isInstalled() + { + $this->load->model('setting/extension'); + $installed = $this->model_setting_extension->getInstalled('shipping'); + + $extensionName = $this->getExtensionName(); + foreach ($installed as $installedExtensionName) { + if ($installedExtensionName === $extensionName) { + return true; + } + } + + return false; + } + + /** + * Entry point (main method) for plugin uninstalling. + * + * @throws Exception + */ + public function uninstall() { + // framework deletes shipping_zasilkovna settings before calling extension uninstall method + $this->load->model(self::ROUTING_BASE_PATH); + $this->model_extension_shipping_zasilkovna->deleteTablesAndEvents(); + } + + /** + * Plugin version upgrade. The need for an upgrade is checked each time the settings page is displayed. + */ + public function upgrade() + { + $this->load->model(self::ROUTING_BASE_PATH); + $this->load->language('extension/shipping/zasilkovna'); + + try { + $this->model_extension_shipping_zasilkovna->upgradeSchema($this->getSchemaVersion()); + } catch (ZasilkovnaUpgradeException $exception) { + $this->session->data['error_warning_multirow'] = [ + $this->language->get('extension_upgrade_failed'), + $exception->getMessage(), + $this->language->get('please_see_log'), + $this->language->get('extension_may_not_work'), + $this->language->get('error_needs_to_be_resolved'), + ]; + return; + } + + $this->model_extension_shipping_zasilkovna->installEvents(); + + $this->load->model('setting/setting'); + $settings = $this->model_setting_setting->getSetting('shipping_zasilkovna'); + $settings['shipping_zasilkovna_version'] = self::VERSION; + $this->model_setting_setting->editSetting('shipping_zasilkovna', $settings); + + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = + sprintf($this->language->get('extension_upgraded'), self::VERSION); + } + + /** + * @return bool + */ + private function isUpgradedNeeded() + { + return $this->isInstalled() && $this->isVersionMismatch(); + } + + /** + * Handler for main action of extension modul for Zasilkovna (main settings page). + * + * @throws Exception + */ + public function index() { + if ($this->isUpgradedNeeded()) { + $this->upgrade(); + } + + // save new values from POST request data to module settings + if (($this->request->server['REQUEST_METHOD'] == 'POST') && ($this->checkPermissions())) { + $this->load->language(self::ROUTING_BASE_PATH); + $existingSettings = $this->getSettings(); + $this->model_setting_setting->editSetting('shipping_zasilkovna', $this->request->post + $existingSettings); + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink('marketplace/extension', ['type' => 'shipping'])); + } + + // full initialization of page + $data = $this->initPageData('', self::TEXT_TITLE_MAIN); + + // load data for list of weight rules + $this->load->model(self::ROUTING_WEIGHT_RULES); + $weightRules = $this->model_extension_shipping_zasilkovna_weight_rules->getAllRules(); + $usedCountries = array_keys($weightRules); + + // load data for list of shipping rules + $this->load->model(self::ROUTING_SHIPPING_RULES); + $shippingRules = $this->model_extension_shipping_zasilkovna_shipping_rules->getAllRules(); + + $this->load->model(self::ROUTING_COUNTRIES); + // adding additional data for list of shipping rules + foreach ($shippingRules as $ruleId => $ruleContent) { + // name of country + $shippingRules[$ruleId]['country_name'] = $this->model_extension_shipping_zasilkovna_countries->getCountryNameByIsoCode2($ruleContent['target_country']); + + // print message "not set" if default price or free shipping limit is not set + if (empty($ruleContent['default_price'])) { + $shippingRules[$ruleId]['default_price'] = $this->language->get('entry_sr_not_set'); + } + if (empty($ruleContent['free_over_limit'])) { + $shippingRules[$ruleId]['free_over_limit'] = $this->language->get('entry_sr_not_set'); + } + // link to shipping rule editor + $shippingRules[$ruleId][self::TEMPLATE_LINK_EDIT] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, + [self::PARAM_RULE_ID => $ruleContent['rule_id']]); + // link to list of weight rules + $shippingRules[$ruleId]['link_weight_rules'] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, + [self::PARAM_COUNTRY => $ruleContent['target_country']]); + + if (in_array($ruleContent['target_country'], $usedCountries)) { + $shippingRules[$ruleId]['weight_rules_description'] = $this->language->get('text_weight_rules_defined'); + $shippingRules[$ruleId]['weight_rules_tooltip'] = $this->language->get('help_weight_rules_change'); + } + else { + $shippingRules[$ruleId]['weight_rules_description'] = $this->language->get('text_weight_rules_missing'); + $shippingRules[$ruleId]['weight_rules_tooltip'] = $this->language->get('help_weight_rules_creation'); + } + } + $data['shipping_rules'] = $shippingRules; + $data['link_shipping_rules'] = $this->createAdminLink(self::ACTION_SHIPPING_RULES); + + // adding additional data for displaying to user in list of weight rules + foreach ($usedCountries as $countryCode) { + $weightRules[$countryCode]['country_name'] = $this->model_extension_shipping_zasilkovna_countries->getCountryNameByIsoCode2($countryCode); + $weightRules[$countryCode][self::TEMPLATE_LINK_EDIT] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); + } + $data['weight_rules'] = $weightRules; + + $this->setGlobalConfigurationForm($data); + + $this->response->setOutput($this->load->view(self::ROUTING_BASE_PATH, $data)); + } + + /** + * Set items of global configuration to template data. + * + * @throws Exception + * @var array $data page template data + */ + private function setGlobalConfigurationForm(&$data) { + $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(''); + $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink('marketplace/extension', ['type' => 'shipping']); + + // loads list of secondary stores + $this->load->model('setting/store'); + $secondaryStores = $this->model_setting_store->getStores(); + + // loads values for global settings from POST request data or from module configuration + $configurationItems = [ + 'shipping_zasilkovna_api_key', + 'shipping_zasilkovna_tax_class_id', + 'shipping_zasilkovna_weight_max', + 'shipping_zasilkovna_default_free_shipping_limit', + 'shipping_zasilkovna_default_shipping_price', + 'shipping_zasilkovna_status', + 'shipping_zasilkovna_sort_order', + 'shipping_zasilkovna_geo_zone_id', + 'shipping_zasilkovna_order_statuses', + 'shipping_zasilkovna_cash_on_delivery_methods', + 'shipping_zasilkovna_eshop_identifier_0' // default store always exists + ]; + + // adds form items for e-shop identifiers for secondary stores + foreach ($secondaryStores as $storeProperties) { + $configurationItems[] = 'shipping_zasilkovna_eshop_identifier_' . $storeProperties['store_id']; + } + + foreach ($configurationItems as $itemName) { + if (isset($this->request->post[$itemName])) { + $data[$itemName] = $this->request->post[$itemName]; + } + else { + $data[$itemName] = $this->config->get($itemName); + } + } + + // loads list of tax classes and geo zones defined in administration + $this->load->model('localisation/tax_class'); + $data['tax_classes'] = $this->model_localisation_tax_class->getTaxClasses(); + $this->load->model('localisation/geo_zone'); + $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones(); + + // loads list of defined order statuses + $this->load->model('localisation/order_status'); + $data['eshop_order_statuses'] = $this->model_localisation_order_status->getOrderStatuses(); + + // loads list of installed payment methods + $this->load->model(self::ROUTING_BASE_PATH); + $data['payment_methods'] = $this->model_extension_shipping_zasilkovna->getInstalledPaymentMethods(); + $data['extension_version'] = self::VERSION; + + // creates list of store names for e-shop identifier items + $data['store_list'] = []; + $data['store_list'][] = [ + 'id' => 0, + 'name' => $this->config->get('config_name'), + 'identifier' => $data['shipping_zasilkovna_eshop_identifier_0'] + ]; + foreach ($secondaryStores as $storeProperies) { + $data['store_list'][] = [ + 'id' => $storeProperties['store_id'], + 'name' => $storeProperties['name'], + 'identifier' => $data['shipping_zasilkovna_eshop_identifier_' . $storeProperties['store_id']] + ]; + } + } + + /** + * Handler for show weight rules for given country. + * @throws Exception + */ + public function weight_rules() { // method name with underscore is required for correct routing + $this->checkCountryCode(); + $countryCode = $this->request->get[self::PARAM_COUNTRY]; + + $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); + + $this->load->model(self::ROUTING_COUNTRIES); + $this->load->model(self::ROUTING_WEIGHT_RULES); + $data[self::TEMPLATE_LINK_ADD] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_ADD, [self::PARAM_COUNTRY => $countryCode]); + $data[self::TEMPLATE_LINK_DELETE] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_DELETE, [self::PARAM_COUNTRY => $countryCode]); + $data[self::TEMPLATE_LINK_BACK] = $this->createAdminLink(''); + $data['text_country_name'] = $this->model_extension_shipping_zasilkovna_countries->getCountryNameByIsoCode2($countryCode); + + $weightRules = $this->model_extension_shipping_zasilkovna_weight_rules->getRulesForCountry($countryCode); + foreach ($weightRules as $rule) { + $data['weight_rules'][] = [ + 'rule_id' => $rule['rule_id'], + 'min_weight' => $rule['min_weight'], + 'max_weight' => $rule['max_weight'], + 'price' => $rule['price'], + self::TEMPLATE_LINK_EDIT => $this->createAdminLink(self::ACTION_WEIGHT_RULES_EDIT, + [self::PARAM_COUNTRY => $countryCode, self::PARAM_RULE_ID => $rule['rule_id']]) + ]; + } + + $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_weight_rules', $data)); + } + + /** + * Handler for creation of new weight rule. + * @throws Exception + */ + public function weight_rules_add() { // method name with underscore is required for correct routing + $this->checkCountryCode(); + $countryCode = $this->request->get[self::PARAM_COUNTRY]; + + $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); + + // check if http method is POST (save of data from form) + if ($this->request->server['REQUEST_METHOD'] === 'POST') { + $this->load->model(self::ROUTING_WEIGHT_RULES); + $errorMessage = $this->model_extension_shipping_zasilkovna_weight_rules->addRule($this->request->post, $countryCode); + if (empty($errorMessage)) { + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); + } + else { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); + } + } + + $this->setWeightRuleFormContent($data, $countryCode); + } + + /** + * Handler for edit of existing weight rule. + * @throws Exception + */ + public function weight_rules_edit() { // method name with underscore is required for correct routing + $this->checkCountryCode(); + + if (!isset($this->request->get['rule_id'])) { + $this->load->language(self::ROUTING_BASE_PATH); + $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); + $this->response->redirect($this->createAdminLink('')); + } + + $countryCode = $this->request->get[self::PARAM_COUNTRY]; + $ruleId = $this->request->get[self::PARAM_RULE_ID]; + + $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); + + // check if http method is POST (save of data from form) + if ($this->request->server['REQUEST_METHOD'] === 'POST') { + $this->load->model(self::ROUTING_WEIGHT_RULES); + $errorMessage = $this->model_extension_shipping_zasilkovna_weight_rules->editRule($ruleId, $this->request->post, $countryCode); + if (empty($errorMessage)) { + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); + } + else { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); + } + } + + $this->setWeightRuleFormContent($data, $countryCode, $ruleId); + } + + /** + * Handler for delete of selected weight rules. + * + * @throws Exception + */ + public function weight_rules_delete() { // method name with underscore is required for correct routing + $this->checkCountryCode(); + $this->load->language(self::ROUTING_BASE_PATH); + $countryCode = $this->request->get[self::PARAM_COUNTRY]; + + if (!empty($this->request->post['selected'])) { + $this->load->model(self::ROUTING_WEIGHT_RULES); + $this->model_extension_shipping_zasilkovna_weight_rules->deleteRules($this->request->post['selected']); + } + + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode])); + } + + /** + * Set of form content for weight rule editor. Common part for "add" and "edit" action. + * + * @throws Exception + * + * @var array $data data for page template + * @var string $countryCode iso country code of target country + * @var int $ruleId internal ID of processed rule (0 for adding a new rule) + */ + private function setWeightRuleFormContent(array $data, $countryCode, $ruleId = 0) { + $isEdit = ($ruleId !== 0); + + if ($this->request->server['REQUEST_METHOD'] === 'POST') { // load data from POST request + $postData = $this->request->post; + $data['min_weight'] = $postData['min_weight']; + $data['max_weight'] = $postData['max_weight']; + $data['price'] = $postData['price']; + } + else if ($isEdit) { // load data from DB + $this->load->model(self::ROUTING_WEIGHT_RULES); + $rowData = $this->model_extension_shipping_zasilkovna_weight_rules->getRule($ruleId); + if (!empty($rowData)) { + $data['min_weight'] = $rowData['min_weight']; + $data['max_weight'] = $rowData['max_weight']; + $data['price'] = $rowData['price']; + } + } + + $data['text_form_title'] = $this->language->get($isEdit ? 'text_edit_weight_rule' : 'text_new_weight_rule'); + if ($isEdit) { + $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_EDIT, + [self::PARAM_COUNTRY => $countryCode, self::PARAM_RULE_ID => $ruleId]); + } + else { + $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_WEIGHT_RULES_ADD, [self::PARAM_COUNTRY => $countryCode]); + } + $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink(self::ACTION_WEIGHT_RULES, [self::PARAM_COUNTRY => $countryCode]); + + $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_weight_rules_form', $data)); + } + + /** + * Handler for list of shipping rules for given country. + * @throws Exception + */ + public function shipping_rules() { // method name with underscore is required for correct routing + $data = $this->initPageData(self::ACTION_WEIGHT_RULES, self::TEXT_TITLE_SHIPPING_RULES); + + $this->load->model(self::ROUTING_SHIPPING_RULES); + $data[self::TEMPLATE_LINK_ADD] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_ADD); + $data[self::TEMPLATE_LINK_DELETE] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_DELETE); + $data[self::TEMPLATE_LINK_BACK] = $this->createAdminLink(''); + + $shippingRules = $this->model_extension_shipping_zasilkovna_shipping_rules->getAllRules(); + foreach ($shippingRules as $rule) { + $this->load->model(self::ROUTING_COUNTRIES); + $data['shipping_rules'][] = [ + 'rule_id' => $rule['rule_id'], + 'target_country_name' => $this->model_extension_shipping_zasilkovna_countries->getCountryNameByIsoCode2($rule['target_country']), + 'default_price' => (empty($rule['default_price']) ? $this->language->get('entry_sr_not_set') : $rule['default_price']) , + 'free_over_limit' => (empty($rule['free_over_limit']) ? $this->language->get('entry_sr_not_set') : $rule['free_over_limit']), + 'is_enabled' => $rule['is_enabled'], + self::TEMPLATE_LINK_EDIT => $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, + [self::PARAM_RULE_ID => $rule['rule_id']]) + ]; + } + + $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_shipping_rules_list', $data)); + } + + /** + * Handler for creation of new shipping rule. + * @throws Exception + */ + public function shipping_rules_add() { // method name with underscore is required for correct routing + $data = $this->initPageData(self::ACTION_SHIPPING_RULES, self::TEXT_TITLE_SHIPPING_RULES); + + // check if http method is POST (save of data from form) + if ($this->request->server['REQUEST_METHOD'] === 'POST') { + $this->load->model(self::ROUTING_SHIPPING_RULES); + $errorMessage = $this->model_extension_shipping_zasilkovna_shipping_rules->checkRuleData($this->request->post); + if (empty($errorMessage)) { + $this->model_extension_shipping_zasilkovna_shipping_rules->addRule($this->request->post); + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); + } + else { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); + } + } + + $this->setShippingRuleFormContent($data); + } + + /** + * Handler for edit of existing shipping rule. + * @throws Exception + */ + public function shipping_rules_edit() { // method name with underscore is required for correct routing + if (!isset($this->request->get[self::PARAM_RULE_ID])) { + $this->load->language(self::ROUTING_BASE_PATH); + $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); + $this->response->redirect($this->createAdminLink('')); + } + + $ruleId = $this->request->get[self::PARAM_RULE_ID]; + $data = $this->initPageData(self::ACTION_SHIPPING_RULES, self::TEXT_TITLE_SHIPPING_RULES); + + // check if http method is POST (save of data from form) + if ($this->request->server['REQUEST_METHOD'] === 'POST') { + $this->load->model(self::ROUTING_SHIPPING_RULES); + $errorMessage = $this->model_extension_shipping_zasilkovna_shipping_rules->editRule($ruleId, $this->request->post); + if (empty($errorMessage)) { + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); + } + else { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get($errorMessage); + } + } + + $this->setShippingRuleFormContent($data, $ruleId); + } + + /** + * Handler for delete of selected shipping rules. + * + * @throws Exception + */ + public function shipping_rules_delete() { // method name with underscore is required for correct routing + $this->load->language(self::ROUTING_BASE_PATH); + + if (!empty($this->request->post['selected'])) { + $this->load->model(self::ROUTING_SHIPPING_RULES); + $this->model_extension_shipping_zasilkovna_shipping_rules->deleteRules($this->request->post['selected']); + } + + $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->language->get('text_success'); + $this->response->redirect($this->createAdminLink(self::ACTION_SHIPPING_RULES)); + } + + /** + * Set of form content for shipping rule editor. Common part for "add" and "edit" action. + * + * @throws Exception + * + * @var array $data data for page template + * @var int $ruleId internal ID of processed rule (0 for adding a new rule) + */ + private function setShippingRuleFormContent(array $data, $ruleId = 0) { + $isEdit = ($ruleId !== 0); + + if ($this->request->server['REQUEST_METHOD'] === 'POST') { // load data from POST request + $postData = $this->request->post; + $data['target_country'] = $postData['target_country']; + $data['default_price'] = $postData['default_price']; + $data['free_over_limit'] = $postData['free_over_limit']; + $data['is_enabled'] = $postData['is_enabled']; + } + else if ($isEdit) { // load data from DB + $this->load->model(self::ROUTING_SHIPPING_RULES); + $rowData = $this->model_extension_shipping_zasilkovna_shipping_rules->getRule($ruleId); + if (!empty($rowData)) { + $data['target_country'] = $rowData['target_country']; + $data['default_price'] = $rowData['default_price']; + $data['free_over_limit'] = $rowData['free_over_limit']; + $data['is_enabled'] = $rowData['is_enabled']; + } + } + + // creation of localized list of allowed countries + $countryList = []; + $this->load->model('localisation/country'); + $countries = $this->model_localisation_country->getCountries(); + + foreach ($countries as $country) { + $countryCode = strtolower($country['iso_code_2']); + + $countryList[] = [ + 'code' => $countryCode, + 'name' => $country['name'] + ]; + } + $data['countries'] = $countryList; + + // set description text and links for form + $data['text_form_title'] = $this->language->get($isEdit ? 'text_edit_shipping_rule' : 'text_new_shipping_rule'); + if ($isEdit) { + $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_EDIT, + [self::PARAM_RULE_ID => $ruleId]); + } + else { + $data[self::TEMPLATE_LINK_FORM_ACTION] = $this->createAdminLink(self::ACTION_SHIPPING_RULES_ADD); + } + $data[self::TEMPLATE_LINK_CANCEL] = $this->createAdminLink(self::ACTION_SHIPPING_RULES); + + $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_shipping_rules_form', $data)); + } + + /** + * Extension of menu in administration. Adds new item with list of Zasilkovna orders to menu "Sales". + * This method is called by "before" event on admin/view/common/column_left/before. + * + * @param string $route routing path of page + * @param array $data template parameters + * @param StdClass $template instance of page template + * @throws Exception + */ + public function adminMenuExtension(&$route, &$data, &$template) { + if (!$this->user->hasPermission('access', self::ROUTING_BASE_PATH)) { + return; + } + + foreach ($data['menus'] as &$menu) { + if ($menu['id'] != 'menu-sale') { + continue; + } + + // load translations for Zasilkovna to separate language context + $this->load->language(self::ROUTING_BASE_PATH, 'zasilkovna'); + + // creation of new menu item for Zasilkovna + $newMenuItem = [ + 'name' => $this->language->get('zasilkovna')->get('text_menu_item'), + 'href' => $this->createAdminLink(self::ACTION_ORDERS) + ]; + array_push($menu['children'], $newMenuItem); + break; + } + } + + /** + * Handler for list of "Zasilkovna" orders. + * + * @throws Exception + */ + public function orders() { + $this->load->language(self::ROUTING_BASE_PATH); + $this->load->model(self::ROUTING_ORDERS); + + // initialization of page data including setup of list parameters (filters, sorting, paging) + $data = $this->initPageData(self::ACTION_ORDERS, self::TEXT_TTILE_ORDERS); + $paramData = $this->model_extension_shipping_zasilkovna_orders->getUrlParameters(); + + // load list of order statuses and creation of array for translate ID to status description + $this->load->model('localisation/order_status'); + $orderStatusList = $this->model_localisation_order_status->getOrderStatuses(); + $data['order_statuses'] = $orderStatusList; + $orderStatusDescriptions = []; + foreach ($orderStatusList as $orderStatusItem) { + $orderStatusDescriptions[$orderStatusItem['order_status_id']] = $orderStatusItem['name']; + } + + // load list of payment methods considered as "cash on delivery" + $codPaymentMethods = (array)$this->config->get('shipping_zasilkovna_cash_on_delivery_methods'); + + // load count of orders and list of orders for current page + $orderCount = $this->model_extension_shipping_zasilkovna_orders->getOrdersCount($paramData['filterData']); + $dbOrderList = $this->model_extension_shipping_zasilkovna_orders->getOrders($paramData); + + // format list of orders for template + foreach ($dbOrderList as $order) { + $data['orders'][] = [ + 'order_id' => $order['order_id'], + 'customer' => $order['customer'], + 'order_status' => isset($orderStatusDescriptions[$order['order_status_id']]) ? $orderStatusDescriptions[$order['order_status_id']] : '', + 'total' => $this->currency->format($order['total'], $order['currency_code'], $order['currency_value']), + 'is_cod' => in_array($order['payment_code'], $codPaymentMethods), + 'date_added' => date($this->language->get('date_format_short'), strtotime($order['date_added'])), + 'branch_id' => $order['branch_id'], + 'branch_name' => $order['branch_name'], + 'exported' => !empty($order['exported']) ? date($this->language->get('date_format_short'), strtotime($order['exported'])) : '' + ]; + } + + // to keep selected rows as selected + if (isset($this->request->post['selected'])) { + $data['selected'] = (array)$this->request->post['selected']; + } else { + $data['selected'] = array(); + } + + // creation set of links for CSV export actions + $csvExportUrlParams = $paramData['filterData']; + $csvExportUrlParams['sort'] = $paramData['sort']; + $csvExportUrlParams['order'] = $paramData['order']; + $data[self::TEMPLATE_LINK_EXPORT_SELECTED] = $this->createAdminLink(self::ACTION_ORDERS_EXPORT, + array_merge($csvExportUrlParams, ['scope' => 'selected'])); + $data[self::TEMPLATE_LINK_EXPORT_ALL] = $this->createAdminLink(self::ACTION_ORDERS_EXPORT, + array_merge($csvExportUrlParams, ['scope' => 'all'])); + + // creation set of links to change grid sorting + $sortingUrlParams = []; + foreach ($paramData['filterData'] as $paramName => $paramValue) { + if (!empty($paramValue)) { + $sortingUrlParams[$paramName] = $paramValue; + } + } + $sortingUrlParams['page'] = $paramData['page']; + $sortingUrlParams['order'] = ($paramData['order'] == 'ASC') ? 'DESC' : 'ASC'; + + $data['link_sorting_order_id'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'o.order_id'])); + $data['link_sorting_customer'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'customer'])); + $data['link_sorting_order_status_id'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'order_status_id'])); + $data['link_sorting_order_total'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'o.total'])); + $data['link_sorting_order_date'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'date_added'])); + $data['link_sorting_branch_name'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'oz.branch_name'])); + $data['link_sorting_exported'] = $this->createAdminLink(self::ACTION_ORDERS, array_merge($sortingUrlParams, ['sort' => 'exported'])); + + // current sorting properties + $data['sort'] = $paramData['sort']; + $data['order'] = $paramData['order']; + + // preparation of paging (switch between pages) + $pagingUrlParameters = []; + foreach ($paramData['filterData'] as $paramName => $paramValue) { + if (!empty($paramValue)) { + $pagingUrlParameters[$paramName] = $paramValue; + } + } + $pagingUrlParameters['sort'] = $paramData['sort']; + $pagingUrlParameters['order'] = $paramData['order']; + $pagingUrlParameters['page'] = '{page}'; + + $pageNumber = $paramData['page']; + $pagination = new Pagination(); + $pagination->total = $orderCount; + $pagination->page = $pageNumber; + $pagination->limit = $this->config->get('config_limit_admin'); + $pagination->url = $this->createAdminLink(self::ACTION_ORDERS, $pagingUrlParameters); + $data['pagination'] = $pagination->render(); + + // preparation of paging (current page info) + // string template: Showing %d to %d of %d (%d Pages) + $data['results'] = sprintf($this->language->get('text_pagination'), + ($orderCount) ? (($pageNumber - 1) * $this->config->get('config_limit_admin')) + 1 : 0, + ((($pageNumber - 1) * $this->config->get('config_limit_admin')) > ($orderCount - $this->config->get('config_limit_admin'))) ? $orderCount : ((($pageNumber - 1) * $this->config->get('config_limit_admin')) + $this->config->get('config_limit_admin')), + $orderCount, + ceil($orderCount / $this->config->get('config_limit_admin'))); + + // creation set of variables for default value of filters + foreach ($paramData['filterData'] as $paramName => $paramValue) { + $data[$paramName] = $paramValue; + } + + // items of selectbox for type of export + $data['export_types'] = [ + [ 'value' => 'not_exported', 'name' => $this->language->get('entry_ol_not_exported')], + [ 'value' => 'exported', 'name' => $this->language->get('entry_ol_exported')], + [ 'value' => 'all', 'name' => $this->language->get('entry_ol_all_records')] + ]; + + // add user token parameter for functionality of JS request (e.g. customer name autocomplete) + $data['user_token'] = $this->session->data['user_token']; + + $this->response->setOutput($this->load->view('extension/shipping/zasilkovna_orders', $data)); + } + + /** + * Handler for export orders to CSV (all or selected orders). + * + * @throws Exception + */ + public function orders_export() { // method name with underscore is required for correct routing + $this->load->language(self::ROUTING_BASE_PATH); + $this->load->model(self::ROUTING_ORDERS); + + $paramData = $this->model_extension_shipping_zasilkovna_orders->getUrlParameters(); + $exportScope = $this->request->get['scope']; + // all parameters except list of "row" checkboxes are part of form action (get parameters) + $orderIdList = (isset($this->request->post['selected'])) ? $this->request->post['selected']: []; + + $this->load->model('setting/store'); + $csvRawData = $this->model_extension_shipping_zasilkovna_orders->getCsvExportData($paramData, $exportScope, $orderIdList); + + // open stdout as file and put content to it using native function for writing data in CSV format + $fileHandle = fopen('php://output', 'wb'); + ob_start(); + // first two lines are fixed header of file + fputcsv($fileHandle, ['version 5']); + fputcsv($fileHandle, []); + + foreach ($csvRawData as $rawRecord) { + fputcsv($fileHandle, $rawRecord); + } + $csvFileContent = ob_get_contents(); + ob_end_clean(); + + // set http headers for "force download" and file type + $this->response->addHeader('Content-Type: text/csv'); + $fileName = 'orders-' . date('Y-m-d-H-i-s') . '.csv'; + $this->response->addHeader('Content-Disposition: attachment; filename="' . $fileName . '"'); + + // send content of csv file as output + $this->response->setOutput($csvFileContent); + } + + /** + * Check if user has permission to change module settings. + * + * @return bool TRUE = success, FALSE = error + */ + private function checkPermissions() { + if (!$this->user->hasPermission('modify', self::ROUTING_BASE_PATH)) { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_permission'); + return false; + } + + return true; + } + + /** + * Check presence and content of url parameter for country. + * If parameter is missing or invalid, redirect to main setting page is performed. + * + * @return void + */ + private function checkCountryCode() { + // check if code of target country is part of url + if (empty($this->request->get[self::PARAM_COUNTRY])) { + $this->load->language(self::ROUTING_BASE_PATH); + $this->session->data[self::TEMPLATE_MESSAGE_ERROR] = $this->language->get('error_missing_param'); + $this->response->redirect($this->createAdminLink('')); + } + } + + /** + * Method for page initialization. Returns customized base content of template data. + * + * @param string $actionName internal name of module action + * @param string $titleId language independent identifier of page title + * @param array $urlParameters additional parameters to url + * @return array initial version of template data + */ + private function initPageData($actionName, $titleId, $urlParameters = []) { + // load language file for module + $this->load->language(self::ROUTING_BASE_PATH); + // set page (document) title + $this->document->setTitle($this->language->get($titleId)); + + // creation of customized common part of template data + $data = [ + // common parts of page (header, left column with system menu, footer) + 'header' => $this->load->controller('common/header'), + 'column_left' => $this->load->controller('common/column_left'), + 'footer' => $this->load->controller('common/footer'), + 'breadcrumbs' => [ + [ + 'text' => $this->language->get('text_home'), + 'href' => $this->createAdminLink('common/dashboard') + ], + [ + 'text' => $this->language->get('text_shipping'), + 'href' => $this->createAdminLink('marketplace/extension', ['type' => 'shipping']) + ], + [ + 'text' => $this->language->get(self::TEXT_TITLE_MAIN), + 'href' => $this->createAdminLink('') + ] + ] + ]; + + // last part of "breadcrumbs" is added only for nonempty action name (pages of module) + if (!empty($actionName)) { + $data['breadcrumbs'][] = [ + 'text' => $this->language->get($titleId), + 'href' => $this->createAdminLink($actionName, $urlParameters) + ]; + } + + // check if some error/success message is stored in session and set it as template parameter + if (isset($this->session->data[self::TEMPLATE_MESSAGE_SUCCESS])) { + $data[self::TEMPLATE_MESSAGE_SUCCESS] = $this->session->data[self::TEMPLATE_MESSAGE_SUCCESS]; + + unset($this->session->data[self::TEMPLATE_MESSAGE_SUCCESS]); + } + if (isset($this->session->data[self::TEMPLATE_MESSAGE_ERROR])) { + $data[self::TEMPLATE_MESSAGE_ERROR] = $this->session->data[self::TEMPLATE_MESSAGE_ERROR]; + + unset($this->session->data[self::TEMPLATE_MESSAGE_ERROR]); + } + if (isset($this->session->data['error_warning_multirow'])) { + $data['error_warning_multirow'] = $this->session->data['error_warning_multirow']; + unset($this->session->data['error_warning_multirow']); + } + + return $data; + } + + /** + * Creates link to given action in administration including user token. + * + * @param string $actionName internal name of module action + * @param array $urlParameters additional parameters to url + * @return string + */ + private function createAdminLink($actionName, $urlParameters = []) + { + // empty action name => main page of module + if ('' == $actionName) { + $actionName = self::ROUTING_BASE_PATH; + } + + // action name without slash (/) => action of module + if (strpos($actionName, '/') === false) { + $actionName = self::ROUTING_BASE_PATH . '/' . $actionName; + } + + // otherwise action name is absolute routing path => no change in action name + // user token must be part of any administration link + $urlParameters['user_token'] = $this->session->data['user_token']; + + return $this->url->link($actionName, $urlParameters, true); + } + +} + +class ZasilkovnaUpgradeException extends Exception +{ +} diff --git a/admin/language/cs-cz/extension/shipping/zasilkovna.php b/upload/admin/language/cs-cz/extension/shipping/zasilkovna.php similarity index 90% rename from admin/language/cs-cz/extension/shipping/zasilkovna.php rename to upload/admin/language/cs-cz/extension/shipping/zasilkovna.php index a1727da..a423b28 100644 --- a/admin/language/cs-cz/extension/shipping/zasilkovna.php +++ b/upload/admin/language/cs-cz/extension/shipping/zasilkovna.php @@ -1,128 +1,130 @@ -db->query($sqlOrderTable); - - // new table for weight rules for countries - $sqlWeightRulesTable = 'CREATE TABLE `' . DB_PREFIX . 'zasilkovna_weight_rules` ( - `rule_id` int(11) NOT NULL AUTO_INCREMENT, - `target_country` varchar(5) NOT NULL COMMENT "iso code of target country", - `min_weight` int(11) NOT NULL, - `max_weight` int(11) NOT NULL, - `price` float(12,2) NOT NULL COMMENT "price for given weight and shipping type", - PRIMARY KEY (`rule_id`) - ) ENGINE=MyISAM DEFAULT CHARSET=utf8;'; - $this->db->query($sqlWeightRulesTable); - - // new table for list of shipping types in countries - $sqlShippingRulesTable = 'CREATE TABLE `' . DB_PREFIX . 'zasilkovna_shipping_rules` ( - `rule_id` int(11) NOT NULL AUTO_INCREMENT, - `target_country` varchar(5) NOT NULL COMMENT "iso code of target country", - `default_price` float(12,2) NOT NULL COMMENT "default shipping price for given country", - `free_over_limit` float(12,2) COMMENT "limit for free of charge shipping", - `is_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT "flag if shipping type is enabled", - PRIMARY KEY (`rule_id`) - ) ENGINE=MyISAM DEFAULT CHARSET=utf8;'; - $this->db->query($sqlShippingRulesTable); - - // new events for processing additional data - // source and target must be in the same part of e-shop (catalog or admin) - $this->load->model('setting/event'); - - $events = [ - 'catalog/controller/checkout/confirm/after' => 'extension/module/zasilkovna/saveOrderData', - 'catalog/controller/checkout/success/before' => 'extension/module/zasilkovna/sessionCleanup', - 'admin/view/common/column_left/before' => 'extension/shipping/zasilkovna/adminMenuExtension' - ]; - - foreach ($events as $trigger => $action) { - $this->model_setting_event->addEvent(self::EVENT_CODE, $trigger, $action, 1, 0); - } - } - - /** - * Cleanup during plugin uninstall. Deletes additional DB tables and removes registered events. - * - * @throws Exception - */ - public function deleteTablesAndEvents() { - // drop additional tables for extension module - $tableNames = ['zasilkovna_weight_rules', 'zasilkovna_shipping_rules', 'zasilkovna_orders']; - foreach ($tableNames as $shortTableName) { - $sql = 'DROP TABLE `' . DB_PREFIX . $shortTableName . '`;'; - $this->db->query($sql); - } - // remove events registered for "zasilkovna" plugin - $this->load->model('setting/event'); - $this->model_setting_event->deleteEventByCode(self::EVENT_CODE); - } - - /** - * Load list of payment methods including description name of method. - * If description name is not found, internal method name is returned. - * - * @return array list of payment methods - * @throws Exception - */ - public function getInstalledPaymentMethods() { - // load internal names of installed payment methods - $this->load->model('setting/extension'); - $paymentCodeList = $this->model_setting_extension->getInstalled('payment'); - - // Get description name of payment methods. - // It must implemented inline because there is no model method for it. - // Based on implementation in method getList in class ControllerExtensionExtensionPayment - $paymentMethods = []; - foreach ($paymentCodeList as $paymentCode) { - // check if main file of extension exists - $mainFilePath = DIR_APPLICATION . 'controller/extension/payment/' . $paymentCode . '.php'; - if (!file_exists($mainFilePath)) { - continue; // extension is registered as installed, but file is missing - } - - // load description name of payment method from language file of extension - $this->load->language('extension/payment/' . $paymentCode, 'extension'); - $extensionName = $this->language->get('extension')->get('heading_title'); - - $paymentMethods[] = [ - 'code' => $paymentCode, - 'name' => $extensionName - ]; - } - - return $paymentMethods; - } -} \ No newline at end of file +db->query($sqlOrderTable); + + // new table for weight rules for countries + $sqlWeightRulesTable = 'CREATE TABLE `' . DB_PREFIX . 'zasilkovna_weight_rules` ( + `rule_id` int(11) NOT NULL AUTO_INCREMENT, + `target_country` varchar(5) NOT NULL COMMENT "iso code of target country", + `min_weight` decimal(10,2) NOT NULL DEFAULT 0, + `max_weight` decimal(10,2) NOT NULL DEFAULT 0, + `price` float(12,2) NOT NULL COMMENT "price for given weight and shipping type", + PRIMARY KEY (`rule_id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;'; + $this->db->query($sqlWeightRulesTable); + + // new table for list of shipping types in countries + $sqlShippingRulesTable = 'CREATE TABLE `' . DB_PREFIX . 'zasilkovna_shipping_rules` ( + `rule_id` int(11) NOT NULL AUTO_INCREMENT, + `target_country` varchar(5) NOT NULL COMMENT "iso code of target country", + `default_price` float(12,2) NOT NULL COMMENT "default shipping price for given country", + `free_over_limit` float(12,2) COMMENT "limit for free of charge shipping", + `is_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT "flag if shipping type is enabled", + PRIMARY KEY (`rule_id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;'; + $this->db->query($sqlShippingRulesTable); + + $this->installEvents(); + } + + /** + * Alters database schema + * @param string $oldVersion version before upgrade + * @throws ZasilkovnaUpgradeException + */ + public function upgradeSchema($oldVersion) + { + $queries = []; + + if ($oldVersion && version_compare($oldVersion, '2.0.4') < 0) { + $queries[] = "ALTER TABLE `" . DB_PREFIX . "zasilkovna_orders` + ADD COLUMN `carrier_pickup_point` VARCHAR(40) NULL + COMMENT 'Code of selected carrier pickup point related to branch_id' AFTER `branch_name`;"; + $queries[] = "ALTER TABLE `" . DB_PREFIX . "zasilkovna_orders` + ADD COLUMN `is_carrier` TINYINT(1) NOT NULL DEFAULT 0 + COMMENT 'Tells if branch_id is carrier' AFTER `carrier_pickup_point`;"; + $queries[] = "ALTER TABLE `" . DB_PREFIX . "zasilkovna_weight_rules` + CHANGE `min_weight` `min_weight` decimal(10,2) NOT NULL DEFAULT 0;"; + $queries[] = "ALTER TABLE `" . DB_PREFIX . "zasilkovna_weight_rules` + CHANGE `max_weight` `max_weight` decimal(10,2) NOT NULL DEFAULT 0;"; + } + + foreach ($queries as $query) { + try { + $this->db->query($query); + } catch (Exception $exception) { + $this->log->write('Exception "' . $exception->getMessage() . '" was thrown during execution of SQL query: ' . $query); + throw new ZasilkovnaUpgradeException($exception->getMessage()); + } + } + } + + public function installEvents() + { + // new events for processing additional data + // source and target must be in the same part of e-shop (catalog or admin) + $this->load->model('setting/event'); + + // add new cart here 1/3 + $events = [ + 'catalog/controller/checkout/confirm/after' => 'extension/module/zasilkovna/saveOrderData', + 'catalog/controller/checkout/success/before' => 'extension/module/zasilkovna/sessionCleanup', + 'catalog/controller/checkout/checkout/before' => 'extension/module/zasilkovna/addStyleAndScript', + 'catalog/controller/checkout/shipping_address/save/before' => 'extension/module/zasilkovna/sessionCheckOnShippingChange', + 'catalog/controller/checkout/guest_shipping/save/before' => 'extension/module/zasilkovna/sessionCheckOnShippingChangeGuest', + 'catalog/controller/checkout/guest/save/before' => 'extension/module/zasilkovna/sessionCheckOnShippingChangeGuest', + 'catalog/controller/journal3/checkout/save/before' => 'extension/module/zasilkovna/journal3CheckoutSave', + 'catalog/controller/journal3/checkout/save/after' => 'extension/module/zasilkovna/saveOrderData', + 'admin/view/common/column_left/before' => 'extension/shipping/zasilkovna/adminMenuExtension' + ]; + + $this->model_setting_event->deleteEventByCode(self::EVENT_CODE); + foreach ($events as $trigger => $action) { + $this->model_setting_event->addEvent(self::EVENT_CODE, $trigger, $action, 1, 0); + } + } + + /** + * Cleanup during plugin uninstall. Deletes additional DB tables and removes registered events. + * + * @throws Exception + */ + public function deleteTablesAndEvents() { + // drop additional tables for extension module + $tableNames = ['zasilkovna_weight_rules', 'zasilkovna_shipping_rules', 'zasilkovna_orders']; + foreach ($tableNames as $shortTableName) { + $sql = 'DROP TABLE `' . DB_PREFIX . $shortTableName . '`;'; + $this->db->query($sql); + } + // remove events registered for "zasilkovna" plugin + $this->load->model('setting/event'); + $this->model_setting_event->deleteEventByCode(self::EVENT_CODE); + } + + /** + * Load list of payment methods including description name of method. + * If description name is not found, internal method name is returned. + * + * @return array list of payment methods + * @throws Exception + */ + public function getInstalledPaymentMethods() { + // load internal names of installed payment methods + $this->load->model('setting/extension'); + $paymentCodeList = $this->model_setting_extension->getInstalled('payment'); + + // Get description name of payment methods. + // It must implemented inline because there is no model method for it. + // Based on implementation in method getList in class ControllerExtensionExtensionPayment + $paymentMethods = []; + foreach ($paymentCodeList as $paymentCode) { + // check if main file of extension exists + $mainFilePath = DIR_APPLICATION . 'controller/extension/payment/' . $paymentCode . '.php'; + if (!file_exists($mainFilePath)) { + continue; // extension is registered as installed, but file is missing + } + + // load description name of payment method from language file of extension + $this->load->language('extension/payment/' . $paymentCode, 'extension'); + $extensionName = $this->language->get('extension')->get('heading_title'); + + $paymentMethods[] = [ + 'code' => $paymentCode, + 'name' => $extensionName + ]; + } + + return $paymentMethods; + } +} diff --git a/admin/model/extension/shipping/zasilkovna_common.php b/upload/admin/model/extension/shipping/zasilkovna_common.php similarity index 79% rename from admin/model/extension/shipping/zasilkovna_common.php rename to upload/admin/model/extension/shipping/zasilkovna_common.php index ba390a7..6051aa1 100644 --- a/admin/model/extension/shipping/zasilkovna_common.php +++ b/upload/admin/model/extension/shipping/zasilkovna_common.php @@ -1,27 +1,23 @@ -db->query("SELECT * FROM " . DB_PREFIX . "country WHERE iso_code_2 = '" . $this->db->escape($code) . "'"); + + if (empty($query)) { + return null; + } + + return $query->row; + } + + /** + * @param $code + * @return string|null + */ + public function getCountryNameByIsoCode2($code) + { + if (empty($code)) { + return null; + } + + $country = $this->getCountryByIsoCode2(strtoupper((string) $code)); + if ($country) { + return $country['name']; + } + + return $code; + } +} diff --git a/admin/model/extension/shipping/zasilkovna_orders.php b/upload/admin/model/extension/shipping/zasilkovna_orders.php similarity index 97% rename from admin/model/extension/shipping/zasilkovna_orders.php rename to upload/admin/model/extension/shipping/zasilkovna_orders.php index 2f7abfa..efdd161 100644 --- a/admin/model/extension/shipping/zasilkovna_orders.php +++ b/upload/admin/model/extension/shipping/zasilkovna_orders.php @@ -1,344 +1,345 @@ -request->get[$filterParamName])) { - $filterData[$filterParamName] = $this->request->get[$filterParamName]; - } - else { - $filterData[$filterParamName] = ''; - } - } - // overwrite default value of "exported" parameter to "not exported" - if (empty($filterData[self::FILTER_EXPORTED])) { - $filterData[self::FILTER_EXPORTED] = 'not_exported'; - } - - $allowedSortColumns = ['o.order_id', 'customer', 'order_status_id', 'o.total', 'date_added', 'oz.branch_name', 'exported']; - if (!empty($this->request->get[self::PARAM_SORT_COLUMN]) && in_array($this->request->get[self::PARAM_SORT_COLUMN], $allowedSortColumns)) { - $sortColumn = $this->request->get[self::PARAM_SORT_COLUMN]; - } - else { - $sortColumn = 'o.order_id'; - } - - $allowedSortDirections = ['ASC', 'DESC']; - if (!empty($this->request->get[self::PARAM_SORT_DIRECTION]) && in_array($this->request->get[self::PARAM_SORT_DIRECTION], $allowedSortDirections)) { - $sortDirection = $this->request->get[self::PARAM_SORT_DIRECTION]; - } - else { - $sortDirection = 'DESC'; - } - - if (!empty($this->request->get[self::PARAM_PAGE_NUMBER])) { - $pageNumber = (int) $this->request->get[self::PARAM_PAGE_NUMBER]; - if ($pageNumber <= 0) { - $pageNumber = 1; - } - } - else { - $pageNumber = 1; - } - - $paramData = [ - 'filterData' => $filterData, - self::PARAM_SORT_COLUMN => $sortColumn, - self::PARAM_SORT_DIRECTION => $sortDirection, - self::PARAM_PAGE_NUMBER => $pageNumber - ]; - - return $paramData; - } - - /** - * Creates additional sql conditions for list of orders. - * - * @param array $filterData filter parameters - * @return string - */ - private function createFilterConditions(array $filterData) { - $sqlConditions = ''; - - // filter by selected order statuses selected in global configuration - $orderStatuses = $this->config->get('shipping_zasilkovna_order_statuses'); - if (!empty($orderStatuses)) { - $orderStatusesString = ''; - foreach ($orderStatuses as $status) { - $orderStatusesString .= (int) $status . ', '; - } - $orderStatusesString = substr($orderStatusesString, 0, -2); - $sqlConditions .= ' AND o.order_status_id IN (' . $orderStatusesString . ')'; - } - - if (!empty($filterData[self::FILTER_ORDER_ID])) { - $sqlConditions .= ' AND o.order_id=' . (int) $filterData[self::FILTER_ORDER_ID]; - } - - if (!empty($filterData[self::FILTER_CUSTOMER])) { - $sqlConditions .= ' AND CONCAT(o.firstname, " ", o.lastname) LIKE "%' . $this->db->escape($filterData[self::FILTER_CUSTOMER]) . '%"'; - } - - if (!empty($filterData[self::FILTER_ORDER_DATE_FROM])) { - $sqlConditions .= ' AND o.date_added >= "' . $this->db->escape($filterData[self::FILTER_ORDER_DATE_FROM]) . '"'; - } - if (!empty($filterData[self::FILTER_ORDER_DATE_TO])) { - $sqlConditions .= ' AND o.date_added <= "' . $this->db->escape($filterData[self::FILTER_ORDER_DATE_TO]) . ' 23:59:59"'; - } - - if (!empty($filterData[self::FILTER_BRANCH_NAME])) { - $sqlConditions .= ' AND oz.branch_name like "%' . $filterData[self::FILTER_BRANCH_NAME] . '%"'; - } - - if (!empty($filterData[self::FILTER_EXPORT_DATE_FROM])) { - $sqlConditions .= ' AND oz.exported >= "' . $this->db->escape($filterData[self::FILTER_EXPORT_DATE_FROM]) . '"'; - } - if (!empty($filterData[self::FILTER_EXPORT_DATE_TO])) { - $sqlConditions .= ' AND oz.exported <= "' . $this->db->escape($filterData[self::FILTER_EXPORT_DATE_TO]) . ' 23:59:59"'; - } - - if (!empty($filterData[self::FILTER_EXPORTED])) { - switch ($filterData[self::FILTER_EXPORTED]) { - case 'exported': - $sqlConditions .= ' AND oz.exported IS NOT NULL'; - break; - case 'not_exported': - $sqlConditions .= ' AND oz.exported IS NULL'; - break; - default: // value "all" and other values - break; - } - } - - // remove first "AND" from begin of conditions - $sqlConditions = substr($sqlConditions, 4); - - return $sqlConditions; - } - - /** - * Returns counts of "Zasilkovna" orders according to current filters. - * - * @param array $filterData filter parameters - * @return int count of orders - */ - public function getOrdersCount(array $filterData) { - $sqlConditions = $this->createFilterConditions($filterData); - $sqlQueryTemplate = 'SELECT count(*) AS `total` FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s;'; - $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $sqlConditions); - - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - return (int) $queryResult->row['total']; - } - - /** - * Returns list of "Zasilkovna" orders including additional data according to current filters. - * - * @param array $paramData url parameters - * @return array list of orders - */ - public function getOrders(array $paramData) { - $sqlConditions = $this->createFilterConditions($paramData['filterData']); - $pageSize = $this->config->get('config_limit_admin'); - $queryOffset = ($paramData[self::PARAM_PAGE_NUMBER] - 1) * $pageSize; - - $sqlQueryTemplate = 'SELECT `o`.`order_id`, CONCAT(o.firstname, " ", o.lastname) AS customer, o.order_status_id, ' - . ' `o`.`date_added`, `o`.`payment_code`, `o`.`total`, `o`.`currency_code`, `o`.`currency_value`, `oz`.`branch_id`, `oz`.`branch_name`, `oz`.`exported`' - . ' FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s' - // add sorting and paging parts (variables with column name and direction is already sanitized in getUrlParameters()) - . ' ORDER BY %s %s LIMIT %s, %s'; - $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $sqlConditions, - $paramData[self::PARAM_SORT_COLUMN], $paramData[self::PARAM_SORT_DIRECTION], - (int) $queryOffset, (int) $pageSize); - - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - return $queryResult->rows; - } - - /** - * Returns raw data for CSV export of orders. - * - * @param array $paramData url parameters for order list (filter parameters) - * @param string $scope of export (all or selected record) - * @param array $orderIdList array of selected order IDs - * @return array raw data for CSV export - */ - public function getCsvExportData(array $paramData, $scope, $orderIdList = []) { - // load list of payment method considered as "cash on delivery" - $codPaymentMethod = $this->config->get('shipping_zasilkovna_cash_on_delivery_methods'); - - // load list of e-shop identifiers from module settings - $eshopIdentifierList = $this->getEshopIdentifiers(); - - // load list of orders including additional order data including filters used in order grid - $filterConditions = $this->createFilterConditions($paramData['filterData']); - $scopeCondition = ''; - if ('selected' === $scope && !empty($orderIdList)) { - // function implode cannot be used because of possible sql injection - $orderIdListString = ''; - foreach ($orderIdList as $orderId) { - $orderIdListString .= (int) $orderId . ','; - } - $orderIdListString = substr($orderIdListString, 0 , -1); - - $scopeCondition = ' `o`.`order_id` IN (' . $orderIdListString . ') AND '; - } - - $sqlQueryTemplate = 'SELECT `o`.`order_id`, `o`.`store_id`, `o`.`shipping_firstname`, `o`.`shipping_lastname`, `o`.`shipping_company`,' - . ' `o`.`email`, `o`.`telephone`, `o`.`currency_code`, `o`.`currency_value`, `o`.`total`, `oz`.`total_weight`, `oz`.`branch_id`,' - . ' `o`.`shipping_address_1`, `o`.`shipping_city`, `o`.`shipping_postcode`, `o`.`payment_code` ' - . ' FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s %s' - // add sorting parts (variables with column name and direction is already sanitized in getUrlParameters()) - . ' ORDER BY %s %s'; - - $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $scopeCondition, - $filterConditions, $paramData[self::PARAM_SORT_COLUMN], $paramData[self::PARAM_SORT_DIRECTION]); - - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - - // format data for CSV export - $csvRawData = []; - $exportedOrders = []; - foreach ($queryResult->rows as $dbRow) { - // Parts of order price: - // order.total - total amount of order in main store currency - // order.currency_code - iso code of target currency - // order.currency_value - ratio between main store currency and target currency - $priceInTargetCurrency = $this->currency->format($dbRow['total'], $dbRow['currency_code'], $dbRow['currency_value'], false); - - // set value of "cash on delivery" according to payment method - if (in_array($dbRow['payment_code'], $codPaymentMethod)) { - $cod = $priceInTargetCurrency; - } - else { - $cod = ''; - } - - $eshopIdentifier = (isset($eshopIdentifierList[$dbRow['store_id']])) ? $eshopIdentifierList[$dbRow['store_id']] : ''; - - $csvRawData[] = [ - 'Reserved' => '', - 'OrderNumber' => $dbRow['order_id'], - 'Name' => $dbRow['shipping_firstname'], - 'Surname' => $dbRow['shipping_lastname'], - 'Company' => $dbRow['shipping_company'], - 'E-mail' => $dbRow['email'], - 'Phone' => $dbRow['telephone'], - 'COD' => $cod, - 'Currency' => $dbRow['currency_code'], - 'Value' => (double) $priceInTargetCurrency, - 'Weight' => $dbRow['total_weight'], - 'Pickupoint' => $dbRow['branch_id'], - 'SenderLabel' => $eshopIdentifier, - 'AdultContent' => '', - 'DelayedDelivery' => '', - // street number contains also house number, e-shop doesn't have separate items for street and house number - 'Street' => $dbRow['shipping_address_1'], - 'House Number' => '', - 'City' => $dbRow['shipping_city'], - 'ZIP' => $dbRow['shipping_postcode'], - 'CarrierPickup' => '', - 'Width' => '', - 'Height' => '', - 'Depth' => '', - ]; - - $exportedOrders[] = $dbRow['order_id']; - } - - // mark all exported records as exported (set current date and time) - if (!empty($exportedOrders)) { - $sqlQueryTemplate = 'UPDATE `%s` SET `exported` = NOW() WHERE `order_id` IN (%s);'; - // direct use of implode method is possible because order ID is received from DB record - $sqlQuery = sprintf($sqlQueryTemplate, self::TABLE_NAME, implode(',', $exportedOrders)); - $this->db->query($sqlQuery); - } - - return $csvRawData; - } - - /** - * Returns list of e-shop identifiers for defined stores. - * - * @return array list of e-shop identifiers from settings - */ - private function getEshopIdentifiers() { - $result = [ - 0 => $this->config->get('shipping_zasilkovna_eshop_identifier_0') - ]; - - $storeList = $this->model_setting_store->getStores(); - foreach ($storeList as $storeItem) { - $configItemName = 'shipping_zasilkovna_eshop_identifier_' . $storeItem['store_id']; - $result[$storeItem['store_id']] = $this->config->get($configItemName); - } - - return $result; - } - -} \ No newline at end of file +request->get[$filterParamName])) { + $filterData[$filterParamName] = $this->request->get[$filterParamName]; + } + else { + $filterData[$filterParamName] = ''; + } + } + // overwrite default value of "exported" parameter to "not exported" + if (empty($filterData[self::FILTER_EXPORTED])) { + $filterData[self::FILTER_EXPORTED] = 'not_exported'; + } + + $allowedSortColumns = ['o.order_id', 'customer', 'order_status_id', 'o.total', 'date_added', 'oz.branch_name', 'exported']; + if (!empty($this->request->get[self::PARAM_SORT_COLUMN]) && in_array($this->request->get[self::PARAM_SORT_COLUMN], $allowedSortColumns)) { + $sortColumn = $this->request->get[self::PARAM_SORT_COLUMN]; + } + else { + $sortColumn = 'o.order_id'; + } + + $allowedSortDirections = ['ASC', 'DESC']; + if (!empty($this->request->get[self::PARAM_SORT_DIRECTION]) && in_array($this->request->get[self::PARAM_SORT_DIRECTION], $allowedSortDirections)) { + $sortDirection = $this->request->get[self::PARAM_SORT_DIRECTION]; + } + else { + $sortDirection = 'DESC'; + } + + if (!empty($this->request->get[self::PARAM_PAGE_NUMBER])) { + $pageNumber = (int) $this->request->get[self::PARAM_PAGE_NUMBER]; + if ($pageNumber <= 0) { + $pageNumber = 1; + } + } + else { + $pageNumber = 1; + } + + $paramData = [ + 'filterData' => $filterData, + self::PARAM_SORT_COLUMN => $sortColumn, + self::PARAM_SORT_DIRECTION => $sortDirection, + self::PARAM_PAGE_NUMBER => $pageNumber + ]; + + return $paramData; + } + + /** + * Creates additional sql conditions for list of orders. + * + * @param array $filterData filter parameters + * @return string + */ + private function createFilterConditions(array $filterData) { + $sqlConditions = ''; + + // filter by selected order statuses selected in global configuration + $orderStatuses = $this->config->get('shipping_zasilkovna_order_statuses'); + if (!empty($orderStatuses)) { + $orderStatusesString = ''; + foreach ($orderStatuses as $status) { + $orderStatusesString .= (int) $status . ', '; + } + $orderStatusesString = substr($orderStatusesString, 0, -2); + $sqlConditions .= ' AND o.order_status_id IN (' . $orderStatusesString . ')'; + } + + if (!empty($filterData[self::FILTER_ORDER_ID])) { + $sqlConditions .= ' AND o.order_id=' . (int) $filterData[self::FILTER_ORDER_ID]; + } + + if (!empty($filterData[self::FILTER_CUSTOMER])) { + $sqlConditions .= ' AND CONCAT(o.firstname, " ", o.lastname) LIKE "%' . $this->db->escape($filterData[self::FILTER_CUSTOMER]) . '%"'; + } + + if (!empty($filterData[self::FILTER_ORDER_DATE_FROM])) { + $sqlConditions .= ' AND o.date_added >= "' . $this->db->escape($filterData[self::FILTER_ORDER_DATE_FROM]) . '"'; + } + if (!empty($filterData[self::FILTER_ORDER_DATE_TO])) { + $sqlConditions .= ' AND o.date_added <= "' . $this->db->escape($filterData[self::FILTER_ORDER_DATE_TO]) . ' 23:59:59"'; + } + + if (!empty($filterData[self::FILTER_BRANCH_NAME])) { + $sqlConditions .= ' AND oz.branch_name like "%' . $filterData[self::FILTER_BRANCH_NAME] . '%"'; + } + + if (!empty($filterData[self::FILTER_EXPORT_DATE_FROM])) { + $sqlConditions .= ' AND oz.exported >= "' . $this->db->escape($filterData[self::FILTER_EXPORT_DATE_FROM]) . '"'; + } + if (!empty($filterData[self::FILTER_EXPORT_DATE_TO])) { + $sqlConditions .= ' AND oz.exported <= "' . $this->db->escape($filterData[self::FILTER_EXPORT_DATE_TO]) . ' 23:59:59"'; + } + + if (!empty($filterData[self::FILTER_EXPORTED])) { + switch ($filterData[self::FILTER_EXPORTED]) { + case 'exported': + $sqlConditions .= ' AND oz.exported IS NOT NULL'; + break; + case 'not_exported': + $sqlConditions .= ' AND oz.exported IS NULL'; + break; + default: // value "all" and other values + break; + } + } + + // remove first "AND" from begin of conditions + $sqlConditions = substr($sqlConditions, 4); + + return $sqlConditions; + } + + /** + * Returns counts of "Zasilkovna" orders according to current filters. + * + * @param array $filterData filter parameters + * @return int count of orders + */ + public function getOrdersCount(array $filterData) { + $sqlConditions = $this->createFilterConditions($filterData); + $sqlQueryTemplate = 'SELECT count(*) AS `total` FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s;'; + $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $sqlConditions); + + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + return (int) $queryResult->row['total']; + } + + /** + * Returns list of "Zasilkovna" orders including additional data according to current filters. + * + * @param array $paramData url parameters + * @return array list of orders + */ + public function getOrders(array $paramData) { + $sqlConditions = $this->createFilterConditions($paramData['filterData']); + $pageSize = $this->config->get('config_limit_admin'); + $queryOffset = ($paramData[self::PARAM_PAGE_NUMBER] - 1) * $pageSize; + + $sqlQueryTemplate = 'SELECT `o`.`order_id`, CONCAT(o.firstname, " ", o.lastname) AS customer, o.order_status_id, ' + . ' `o`.`date_added`, `o`.`payment_code`, `o`.`total`, `o`.`currency_code`, `o`.`currency_value`, `oz`.`branch_id`, `oz`.`branch_name`, `oz`.`exported`' + . ' FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s' + // add sorting and paging parts (variables with column name and direction is already sanitized in getUrlParameters()) + . ' ORDER BY %s %s LIMIT %s, %s'; + $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $sqlConditions, + $paramData[self::PARAM_SORT_COLUMN], $paramData[self::PARAM_SORT_DIRECTION], + (int) $queryOffset, (int) $pageSize); + + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + return $queryResult->rows; + } + + /** + * Returns raw data for CSV export of orders. + * + * @param array $paramData url parameters for order list (filter parameters) + * @param string $scope of export (all or selected record) + * @param array $orderIdList array of selected order IDs + * @return array raw data for CSV export + */ + public function getCsvExportData(array $paramData, $scope, $orderIdList = []) { + // load list of payment method considered as "cash on delivery" + $codPaymentMethod = $this->config->get('shipping_zasilkovna_cash_on_delivery_methods'); + + // load list of e-shop identifiers from module settings + $eshopIdentifierList = $this->getEshopIdentifiers(); + + // load list of orders including additional order data including filters used in order grid + $filterConditions = $this->createFilterConditions($paramData['filterData']); + $scopeCondition = ''; + if ('selected' === $scope && !empty($orderIdList)) { + // function implode cannot be used because of possible sql injection + $orderIdListString = ''; + foreach ($orderIdList as $orderId) { + $orderIdListString .= (int) $orderId . ','; + } + $orderIdListString = substr($orderIdListString, 0 , -1); + + $scopeCondition = ' `o`.`order_id` IN (' . $orderIdListString . ') AND '; + } + + $sqlQueryTemplate = 'SELECT `o`.`order_id`, `o`.`store_id`, `o`.`shipping_firstname`, `o`.`shipping_lastname`, `o`.`shipping_company`,' + . ' `o`.`email`, `o`.`telephone`, `o`.`currency_code`, `o`.`currency_value`, `o`.`total`, `oz`.`total_weight`, `oz`.`branch_id`,' + . ' `oz`.`carrier_pickup_point`,' + . ' `o`.`shipping_address_1`, `o`.`shipping_city`, `o`.`shipping_postcode`, `o`.`payment_code` ' + . ' FROM `%s` `o` JOIN `%s` `oz` ON (`oz`.`order_id` = `o`.`order_id`) WHERE %s %s' + // add sorting parts (variables with column name and direction is already sanitized in getUrlParameters()) + . ' ORDER BY %s %s'; + + $sqlQuery = sprintf($sqlQueryTemplate, self::BASE_ORDER_TABLE_NAME, self::TABLE_NAME, $scopeCondition, + $filterConditions, $paramData[self::PARAM_SORT_COLUMN], $paramData[self::PARAM_SORT_DIRECTION]); + + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + + // format data for CSV export + $csvRawData = []; + $exportedOrders = []; + foreach ($queryResult->rows as $dbRow) { + // Parts of order price: + // order.total - total amount of order in main store currency + // order.currency_code - iso code of target currency + // order.currency_value - ratio between main store currency and target currency + $priceInTargetCurrency = $this->currency->format($dbRow['total'], $dbRow['currency_code'], $dbRow['currency_value'], false); + + // set value of "cash on delivery" according to payment method + if (in_array($dbRow['payment_code'], $codPaymentMethod)) { + $cod = $priceInTargetCurrency; + } + else { + $cod = ''; + } + + $eshopIdentifier = (isset($eshopIdentifierList[$dbRow['store_id']])) ? $eshopIdentifierList[$dbRow['store_id']] : ''; + + $csvRawData[] = [ + 'Reserved' => '', + 'OrderNumber' => $dbRow['order_id'], + 'Name' => $dbRow['shipping_firstname'], + 'Surname' => $dbRow['shipping_lastname'], + 'Company' => $dbRow['shipping_company'], + 'E-mail' => $dbRow['email'], + 'Phone' => $dbRow['telephone'], + 'COD' => $cod, + 'Currency' => $dbRow['currency_code'], + 'Value' => (double) $priceInTargetCurrency, + 'Weight' => $dbRow['total_weight'], + 'Pickupoint' => $dbRow['branch_id'], + 'SenderLabel' => $eshopIdentifier, + 'AdultContent' => '', + 'DelayedDelivery' => '', + // street number contains also house number, e-shop doesn't have separate items for street and house number + 'Street' => $dbRow['shipping_address_1'], + 'House Number' => '', + 'City' => $dbRow['shipping_city'], + 'ZIP' => $dbRow['shipping_postcode'], + 'CarrierPickup' => (string) $dbRow['carrier_pickup_point'], + 'Width' => '', + 'Height' => '', + 'Depth' => '', + ]; + + $exportedOrders[] = $dbRow['order_id']; + } + + // mark all exported records as exported (set current date and time) + if (!empty($exportedOrders)) { + $sqlQueryTemplate = 'UPDATE `%s` SET `exported` = NOW() WHERE `order_id` IN (%s);'; + // direct use of implode method is possible because order ID is received from DB record + $sqlQuery = sprintf($sqlQueryTemplate, self::TABLE_NAME, implode(',', $exportedOrders)); + $this->db->query($sqlQuery); + } + + return $csvRawData; + } + + /** + * Returns list of e-shop identifiers for defined stores. + * + * @return array list of e-shop identifiers from settings + */ + private function getEshopIdentifiers() { + $result = [ + 0 => $this->config->get('shipping_zasilkovna_eshop_identifier_0') + ]; + + $storeList = $this->model_setting_store->getStores(); + foreach ($storeList as $storeItem) { + $configItemName = 'shipping_zasilkovna_eshop_identifier_' . $storeItem['store_id']; + $result[$storeItem['store_id']] = $this->config->get($configItemName); + } + + return $result; + } + +} diff --git a/admin/model/extension/shipping/zasilkovna_shipping_rules.php b/upload/admin/model/extension/shipping/zasilkovna_shipping_rules.php similarity index 93% rename from admin/model/extension/shipping/zasilkovna_shipping_rules.php rename to upload/admin/model/extension/shipping/zasilkovna_shipping_rules.php index ffc0d7e..bce79a3 100644 --- a/admin/model/extension/shipping/zasilkovna_shipping_rules.php +++ b/upload/admin/model/extension/shipping/zasilkovna_shipping_rules.php @@ -1,175 +1,171 @@ -db->query($sqlQuery); - - return $queryResult->rows; - } - - /** - * Get content of rule. - * - * @param $ruleId int internal rule ID - * @return array content of rule - */ - public function getRule($ruleId) { - $sqlQuery = sprintf('SELECT * FROM %s WHERE `rule_id` = %s', self::TABLE_NAME, (int) $ruleId); - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - - return $queryResult->row; - } - - /** - * Creates a new weight rule for country. - * - * @param array $ruleData data of new rule from POST parameters - * @return string identifier of error message, empty if no error occurred - */ - public function addRule(array $ruleData) { - $checkErrorMessage = $this->checkRuleData($ruleData); - if (!empty($checkErrorMessage)) { - return $checkErrorMessage; - } - - $sqlQuery = sprintf('INSERT INTO `%s` (`target_country`, `default_price`, `free_over_limit`, `is_enabled`) VALUES ("%s", "%.2f","%.2f", %s);', - self::TABLE_NAME, $this->db->escape($ruleData[self::COLUMN_TARGET_COUNTRY]), (float) $ruleData[self::COLUMN_DEFAULT_PRICE], - (float) $ruleData[self::COLUMN_FREE_OVER_LIMIT], (int) $ruleData[self::COLUMN_IS_ENABLED]); - $this->db->query($sqlQuery); - - return ''; - } - - /** - * Edit an existing rule for country. - * - * @param int $ruleId internal ID of changed rule - * @param array $ruleData data of new rule from POST parameters - * @return string identifier of error message, empty if no error occurred - */ - public function editRule($ruleId, array $ruleData) { - $errorMessage = $this->checkRuleData($ruleData, $ruleId); - if (!empty($errorMessage)) { - return $errorMessage; - } - - $sqlQuery = sprintf('UPDATE `%s` SET `target_country`= "%s", `default_price` = "%.2f", `free_over_limit` = "%.2f", `is_enabled` = %s WHERE `rule_id` = %s', - self::TABLE_NAME, $this->db->escape($ruleData[self::COLUMN_TARGET_COUNTRY]), (float) $ruleData[self::COLUMN_DEFAULT_PRICE], - (float) $ruleData[self::COLUMN_FREE_OVER_LIMIT], (int) $ruleData[self::COLUMN_IS_ENABLED], (int) $ruleId); - $this->db->query($sqlQuery); - - return ''; - } - - /** - * Check content of weight rule data. - * - * @param array $ruleData data of new rule from POST parameters - * @param int $ruleToIgnore ID of ignored rule (for change of rule) - * - * @return string identifier of error message, empty if no error occurred - */ - public function checkRuleData(array $ruleData, $ruleToIgnore = 0) { - // check if user has rights to modify setting - if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { - return self::ERROR_PERMISSION; - } - - // check if defined country is allowed - $country = $ruleData[self::COLUMN_TARGET_COUNTRY]; - if (!in_array($country, self::ALLOWED_COUNTRIES)) { - return self::ERROR_INVALID_COUNTRY; - } - - // check if defined price is valid positive integer number - // both items are optional and can be empty - if (!empty($ruleData[self::COLUMN_DEFAULT_PRICE])) { - $defaultPrice = (int) $ruleData[self::COLUMN_DEFAULT_PRICE]; - if ($defaultPrice <= 0) { - return self::ERROR_INVALID_PRICE; - } - } - - if (!empty($ruleData[self::COLUMN_FREE_OVER_LIMIT])) { - $freeOverLimit = (int) $ruleData[self::COLUMN_FREE_OVER_LIMIT]; - if ($freeOverLimit <= 0) { - return self::ERROR_INVALID_PRICE; - } - } - - // check if rule is rule for this country is already defined - $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `target_country`="%s"', - self::TABLE_NAME, $this->db->escape($country)); - if ($ruleToIgnore > 0) { - $sqlQuery .= ' AND `rule_id` <> ' . $ruleToIgnore; - } - /** @var StdClass $sqlResult */ - $sqlResult = $this->db->query($sqlQuery); - if ($sqlResult->num_rows > 0) { - return self::ERROR_DUPLICATE_COUNTRY_RULE; - } - - return ''; // no error - } - - /** - * Delete an existing rules for country. All selected rules will be deleted. - * - * @param array $ruleIdList list of internal rule IDs - * @return string identifier of error message, empty if no error occurred - */ - public function deleteRules(array $ruleIdList) { - // check if user has rights to modify setting - if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { - return self::ERROR_PERMISSION; - } - - if (empty($ruleIdList)) { // check if list of rules to delete is not empty - return ''; - } - - // convert array of rule IDs to list for sql command (including conversion to int) - $sqlList = ''; - foreach ($ruleIdList as $ruleId) { - $sqlList .= (int) $ruleId . ','; - } - $sqlList = substr($sqlList, 0, -1); - - $sqlQuery = sprintf('DELETE FROM %s WHERE `rule_id` IN (%s);', self::TABLE_NAME, $sqlList); - $this->db->query($sqlQuery); - - return ''; - } - -} \ No newline at end of file +db->query($sqlQuery); + + return $queryResult->rows; + } + + /** + * Get content of rule. + * + * @param $ruleId int internal rule ID + * @return array content of rule + */ + public function getRule($ruleId) { + $sqlQuery = sprintf('SELECT * FROM %s WHERE `rule_id` = %s', self::TABLE_NAME, (int) $ruleId); + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + + return $queryResult->row; + } + + /** + * Creates a new weight rule for country. + * + * @param array $ruleData data of new rule from POST parameters + * @return string identifier of error message, empty if no error occurred + */ + public function addRule(array $ruleData) { + $checkErrorMessage = $this->checkRuleData($ruleData); + if (!empty($checkErrorMessage)) { + return $checkErrorMessage; + } + + $sqlQuery = sprintf('INSERT INTO `%s` (`target_country`, `default_price`, `free_over_limit`, `is_enabled`) VALUES ("%s", "%.2f","%.2f", %s);', + self::TABLE_NAME, $this->db->escape($ruleData[self::COLUMN_TARGET_COUNTRY]), (float) $ruleData[self::COLUMN_DEFAULT_PRICE], + (float) $ruleData[self::COLUMN_FREE_OVER_LIMIT], (int) $ruleData[self::COLUMN_IS_ENABLED]); + $this->db->query($sqlQuery); + + return ''; + } + + /** + * Edit an existing rule for country. + * + * @param int $ruleId internal ID of changed rule + * @param array $ruleData data of new rule from POST parameters + * @return string identifier of error message, empty if no error occurred + */ + public function editRule($ruleId, array $ruleData) { + $errorMessage = $this->checkRuleData($ruleData, $ruleId); + if (!empty($errorMessage)) { + return $errorMessage; + } + + $sqlQuery = sprintf('UPDATE `%s` SET `target_country`= "%s", `default_price` = "%.2f", `free_over_limit` = "%.2f", `is_enabled` = %s WHERE `rule_id` = %s', + self::TABLE_NAME, $this->db->escape($ruleData[self::COLUMN_TARGET_COUNTRY]), (float) $ruleData[self::COLUMN_DEFAULT_PRICE], + (float) $ruleData[self::COLUMN_FREE_OVER_LIMIT], (int) $ruleData[self::COLUMN_IS_ENABLED], (int) $ruleId); + $this->db->query($sqlQuery); + + return ''; + } + + /** + * Check content of weight rule data. + * + * @param array $ruleData data of new rule from POST parameters + * @param int $ruleToIgnore ID of ignored rule (for change of rule) + * + * @return string identifier of error message, empty if no error occurred + */ + public function checkRuleData(array $ruleData, $ruleToIgnore = 0) { + // check if user has rights to modify setting + if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { + return self::ERROR_PERMISSION; + } + + // check if defined price is valid positive integer number + // both items are optional and can be empty + if (!empty($ruleData[self::COLUMN_DEFAULT_PRICE])) { + $defaultPrice = (int) $ruleData[self::COLUMN_DEFAULT_PRICE]; + if ($defaultPrice <= 0) { + return self::ERROR_INVALID_PRICE; + } + } + + if (!empty($ruleData[self::COLUMN_FREE_OVER_LIMIT])) { + $freeOverLimit = (int) $ruleData[self::COLUMN_FREE_OVER_LIMIT]; + if ($freeOverLimit <= 0) { + return self::ERROR_INVALID_PRICE; + } + } + + $country = $ruleData[self::COLUMN_TARGET_COUNTRY]; + + // check if rule is rule for this country is already defined + $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `target_country`="%s"', + self::TABLE_NAME, $this->db->escape($country)); + if ($ruleToIgnore > 0) { + $sqlQuery .= ' AND `rule_id` <> ' . $ruleToIgnore; + } + /** @var StdClass $sqlResult */ + $sqlResult = $this->db->query($sqlQuery); + if ($sqlResult->num_rows > 0) { + return self::ERROR_DUPLICATE_COUNTRY_RULE; + } + + return ''; // no error + } + + /** + * Delete an existing rules for country. All selected rules will be deleted. + * + * @param array $ruleIdList list of internal rule IDs + * @return string identifier of error message, empty if no error occurred + */ + public function deleteRules(array $ruleIdList) { + // check if user has rights to modify setting + if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { + return self::ERROR_PERMISSION; + } + + if (empty($ruleIdList)) { // check if list of rules to delete is not empty + return ''; + } + + // convert array of rule IDs to list for sql command (including conversion to int) + $sqlList = ''; + foreach ($ruleIdList as $ruleId) { + $sqlList .= (int) $ruleId . ','; + } + $sqlList = substr($sqlList, 0, -1); + + $sqlQuery = sprintf('DELETE FROM %s WHERE `rule_id` IN (%s);', self::TABLE_NAME, $sqlList); + $this->db->query($sqlQuery); + + return ''; + } + +} diff --git a/admin/model/extension/shipping/zasilkovna_weight_rules.php b/upload/admin/model/extension/shipping/zasilkovna_weight_rules.php similarity index 87% rename from admin/model/extension/shipping/zasilkovna_weight_rules.php rename to upload/admin/model/extension/shipping/zasilkovna_weight_rules.php index f9eaa15..957c7a5 100644 --- a/admin/model/extension/shipping/zasilkovna_weight_rules.php +++ b/upload/admin/model/extension/shipping/zasilkovna_weight_rules.php @@ -1,208 +1,210 @@ -= min_weight, < max_weight) */ - const COLUMN_PRICE = 'price'; - - /** @var string country code for "other" countries */ - const COUNTRY_OTHER = 'other'; - - /** - * Returns list of rules for all countries. - * First level is country code. Second level is list of rules for country. - * - * @return array list of rules - */ - public function getAllRules() { - $sqlQuery = sprintf('SELECT * FROM `%s` ORDER BY `target_country`, `min_weight`',self::TABLE_NAME); - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - - // conversion of flat list to two-dimensional array with rules for "other" countries as last item - $result = []; - $otherCountries = []; - foreach ($queryResult->rows as $dbRow) { - if (self::COUNTRY_OTHER === $dbRow[self::COLUMN_TARGET_COUNTRY]) { - $otherCountries[] = $dbRow; - } - else { - $result[$dbRow[self::COLUMN_TARGET_COUNTRY]]['items'][] = $dbRow; - } - } - if (!empty($otherCountries)) { - $result[self::COUNTRY_OTHER]['items'] = $otherCountries; - } - return $result; - } - - /** - * Returns list of rules for given country. - * - * @param $countryCode string iso code of target country - * @return array list of rules - */ - public function getRulesForCountry($countryCode) { - $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" ORDER BY `min_weight`;', - self::TABLE_NAME, $this->db->escape($countryCode)); - - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - return $queryResult->rows; - } - - /** - * Get content of rule. - * - * @param $ruleId int internal rule ID - * @return array content of rule - */ - public function getRule($ruleId) { - $sqlQuery = sprintf('SELECT * FROM %s WHERE `rule_id` = %s', self::TABLE_NAME, (int) $ruleId); - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - - return $queryResult->row; - } - - /** - * Creates a new weight rule for country. - * - * @param array $ruleData data of new rule from POST parameters - * @param string $countryCode iso code of target country for new record - * @return string identifier of error message, empty if no error occurred - */ - public function addRule(array $ruleData, $countryCode) { - $checkErrorMessage = $this->checkRuleData($ruleData, $countryCode); - if (!empty($checkErrorMessage)) { - return $checkErrorMessage; - } - - $sqlQuery = sprintf('INSERT INTO `%s` (`target_country`, `min_weight`, `max_weight`, `price`) VALUES ("%s", %s, %s, "%.2f");', - self::TABLE_NAME, $this->db->escape($countryCode), (int) $ruleData[self::COLUMN_MIN_WEIGHT], - (int) $ruleData[self::COLUMN_MAX_WEIGHT], (float) $ruleData[self::COLUMN_PRICE]); - $this->db->query($sqlQuery); - - return ''; - } - - /** - * Edit an existing rule for country. - * - * @param int $ruleId internal ID of changed rule - * @param array $ruleData data of new rule from POST parameters - * @param string $countryCode iso code of target country for changed record - * @return string identifier of error message, empty if no error occurred - */ - public function editRule($ruleId, array $ruleData, $countryCode) { - $ruleData[self::COLUMN_TARGET_COUNTRY] = $countryCode; - $errorMessage = $this->checkRuleData($ruleData, $countryCode, $ruleId); - if (!empty($errorMessage)) { - return $errorMessage; - } - - $sqlQuery = sprintf('UPDATE `%s` SET `min_weight`= %s, `max_weight` = %s, `price` = "%.2f" WHERE `rule_id` = %s', - self::TABLE_NAME, (int) $ruleData[self::COLUMN_MIN_WEIGHT], (int) $ruleData[self::COLUMN_MAX_WEIGHT], (float) $ruleData[self::COLUMN_PRICE], - (int) $ruleId); - $this->db->query($sqlQuery); - - return ''; - } - - /** - * Check content of weight rule data. - * - * @param array $ruleData data of new rule from POST parameters - * @param string $countryCode iso code of target country for new record - * @param int $ruleToIgnore ID of ignored rule (for change of rule) - * - * @return string identifier of error message, empty if no error occurred - */ - public function checkRuleData(array $ruleData, $countryCode, $ruleToIgnore = 0) { - // check if user has rights to modify setting - if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { - return self::ERROR_PERMISSION; - } - - // check if weight and price is positive integer number including weight range - $minWeight = (int) $ruleData[self::COLUMN_MIN_WEIGHT]; - $maxWeight = (int) $ruleData[self::COLUMN_MAX_WEIGHT]; - $price = (float) $ruleData[self::COLUMN_PRICE]; - - if ($minWeight < 0 || $maxWeight <= 0) { // minimal weight can be 0 - return self::ERROR_INVALID_WEIGHT; - } - - if ($minWeight >= $maxWeight) { - return self::ERROR_INVALID_WEIGHT_RANGE; - } - - if ($price <= 0) { - return self::ERROR_INVALID_PRICE; - } - - // check if rule is overlapping with any other rule - $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `min_weight`<%s AND `max_weight` > %s AND `target_country` = "%s"', - self::TABLE_NAME, $maxWeight, $minWeight, $countryCode); - if ($ruleToIgnore > 0) { - $sqlQuery .= ' AND `rule_id` <> ' . $ruleToIgnore; - } - /** @var StdClass $sqlResult */ - $sqlResult = $this->db->query($sqlQuery); - if ($sqlResult->num_rows > 0) { - $errorMesage = sprintf(self::ERROR_RULES_OVERLAPPING, $sqlResult->row[self::COLUMN_MIN_WEIGHT], - $sqlResult->row[self::COLUMN_MAX_WEIGHT]); - return $errorMesage; - } - - return ''; // no error - } - - /** - * Delete an existing rules for country. All selected rules will be deleted. - * - * @param array $ruleIdList list of internal rule IDs - * @return string identifier of error message, empty if no error occurred - */ - public function deleteRules(array $ruleIdList) { - // check if user has rights to modify setting - if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { - return self::ERROR_PERMISSION; - } - - if (empty($ruleIdList)) { // check if list of rules to delete is not empty - return ''; - } - - // convert array of rule IDs to list for sql command (including conversion to int) - $sqlList = ''; - foreach ($ruleIdList as $ruleId) { - $sqlList .= (int) $ruleId . ','; - } - $sqlList = substr($sqlList, 0, -1); - - $sqlQuery = sprintf('DELETE FROM %s WHERE `rule_id` IN (%s);', self::TABLE_NAME, $sqlList); - $this->db->query($sqlQuery); - - return ''; - } += min_weight, < max_weight) */ + const COLUMN_PRICE = 'price'; + + /** @var string country code for "other" countries */ + const COUNTRY_OTHER = 'other'; + + /** + * Returns list of rules for all countries. + * First level is country code. Second level is list of rules for country. + * + * @return array list of rules + */ + public function getAllRules() { + $sqlQuery = sprintf('SELECT * FROM `%s` ORDER BY `target_country`, `min_weight`',self::TABLE_NAME); + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + + // conversion of flat list to two-dimensional array with rules for "other" countries as last item + $result = []; + $otherCountries = []; + foreach ($queryResult->rows as $dbRow) { + if (self::COUNTRY_OTHER === $dbRow[self::COLUMN_TARGET_COUNTRY]) { + $otherCountries[] = $dbRow; + } + else { + $result[$dbRow[self::COLUMN_TARGET_COUNTRY]]['items'][] = $dbRow; + } + } + if (!empty($otherCountries)) { + $result[self::COUNTRY_OTHER]['items'] = $otherCountries; + } + return $result; + } + + /** + * Returns list of rules for given country. + * + * @param $countryCode string iso code of target country + * @return array list of rules + */ + public function getRulesForCountry($countryCode) { + $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" ORDER BY `min_weight`;', + self::TABLE_NAME, $this->db->escape($countryCode)); + + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + return $queryResult->rows; + } + + /** + * Get content of rule. + * + * @param $ruleId int internal rule ID + * @return array content of rule + */ + public function getRule($ruleId) { + $sqlQuery = sprintf('SELECT * FROM %s WHERE `rule_id` = %s', self::TABLE_NAME, (int) $ruleId); + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + + return $queryResult->row; + } + + /** + * Creates a new weight rule for country. + * + * @param array $ruleData data of new rule from POST parameters + * @param string $countryCode iso code of target country for new record + * @return string identifier of error message, empty if no error occurred + */ + public function addRule(array $ruleData, $countryCode) { + $checkErrorMessage = $this->checkRuleData($ruleData, $countryCode); + if (!empty($checkErrorMessage)) { + return $checkErrorMessage; + } + + $sqlQuery = sprintf( + 'INSERT INTO `%s` (`target_country`, `min_weight`, `max_weight`, `price`) VALUES ("%s", %s, %s, "%.2f");', + self::TABLE_NAME, $this->db->escape($countryCode), (float)$ruleData[self::COLUMN_MIN_WEIGHT], + (float)$ruleData[self::COLUMN_MAX_WEIGHT], (float)$ruleData[self::COLUMN_PRICE]); + $this->db->query($sqlQuery); + + return ''; + } + + /** + * Edit an existing rule for country. + * + * @param int $ruleId internal ID of changed rule + * @param array $ruleData data of new rule from POST parameters + * @param string $countryCode iso code of target country for changed record + * @return string identifier of error message, empty if no error occurred + */ + public function editRule($ruleId, array $ruleData, $countryCode) { + $ruleData[self::COLUMN_TARGET_COUNTRY] = $countryCode; + $errorMessage = $this->checkRuleData($ruleData, $countryCode, $ruleId); + if (!empty($errorMessage)) { + return $errorMessage; + } + + $sqlQuery = sprintf( + 'UPDATE `%s` SET `min_weight`= %s, `max_weight` = %s, `price` = "%.2f" WHERE `rule_id` = %s', + self::TABLE_NAME, (float)$ruleData[self::COLUMN_MIN_WEIGHT], + (float)$ruleData[self::COLUMN_MAX_WEIGHT], (float)$ruleData[self::COLUMN_PRICE], (int)$ruleId); + $this->db->query($sqlQuery); + + return ''; + } + + /** + * Check content of weight rule data. + * + * @param array $ruleData data of new rule from POST parameters + * @param string $countryCode iso code of target country for new record + * @param int $ruleToIgnore ID of ignored rule (for change of rule) + * + * @return string identifier of error message, empty if no error occurred + */ + public function checkRuleData(array $ruleData, $countryCode, $ruleToIgnore = 0) { + // check if user has rights to modify setting + if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { + return self::ERROR_PERMISSION; + } + + // check if weight and price is positive integer number including weight range + $minWeight = (float)$ruleData[self::COLUMN_MIN_WEIGHT]; + $maxWeight = (float)$ruleData[self::COLUMN_MAX_WEIGHT]; + $price = (float) $ruleData[self::COLUMN_PRICE]; + + if ($minWeight < 0 || $maxWeight <= 0) { // minimal weight can be 0 + return self::ERROR_INVALID_WEIGHT; + } + + if ($minWeight >= $maxWeight) { + return self::ERROR_INVALID_WEIGHT_RANGE; + } + + if ($price <= 0) { + return self::ERROR_INVALID_PRICE; + } + + // check if rule is overlapping with any other rule + $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `min_weight`<%s AND `max_weight` > %s AND `target_country` = "%s"', + self::TABLE_NAME, $maxWeight, $minWeight, $countryCode); + if ($ruleToIgnore > 0) { + $sqlQuery .= ' AND `rule_id` <> ' . $ruleToIgnore; + } + /** @var StdClass $sqlResult */ + $sqlResult = $this->db->query($sqlQuery); + if ($sqlResult->num_rows > 0) { + $errorMesage = sprintf(self::ERROR_RULES_OVERLAPPING, $sqlResult->row[self::COLUMN_MIN_WEIGHT], + $sqlResult->row[self::COLUMN_MAX_WEIGHT]); + return $errorMesage; + } + + return ''; // no error + } + + /** + * Delete an existing rules for country. All selected rules will be deleted. + * + * @param array $ruleIdList list of internal rule IDs + * @return string identifier of error message, empty if no error occurred + */ + public function deleteRules(array $ruleIdList) { + // check if user has rights to modify setting + if (!$this->user->hasPermission('modify', 'extension/shipping/zasilkovna')) { + return self::ERROR_PERMISSION; + } + + if (empty($ruleIdList)) { // check if list of rules to delete is not empty + return ''; + } + + // convert array of rule IDs to list for sql command (including conversion to int) + $sqlList = ''; + foreach ($ruleIdList as $ruleId) { + $sqlList .= (int) $ruleId . ','; + } + $sqlList = substr($sqlList, 0, -1); + + $sqlQuery = sprintf('DELETE FROM %s WHERE `rule_id` IN (%s);', self::TABLE_NAME, $sqlList); + $this->db->query($sqlQuery); + + return ''; + } } \ No newline at end of file diff --git a/admin/view/template/extension/shipping/zasilkovna.twig b/upload/admin/view/template/extension/shipping/zasilkovna.twig similarity index 94% rename from admin/view/template/extension/shipping/zasilkovna.twig rename to upload/admin/view/template/extension/shipping/zasilkovna.twig index bfbb40b..25ca914 100644 --- a/admin/view/template/extension/shipping/zasilkovna.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna.twig @@ -1,279 +1,297 @@ -{{ header }}{{ column_left }} -
- -
- {% if error_warning %} -
{{ error_warning }} - -
- {% endif %} - {% if success %} -
{{ success }} - -
- {% endif %} -
-
-
- -
-

- {{ text_module_config }} -

-
-
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
- {% for order_status_item in eshop_order_statuses %} -
- -
- {% endfor %} -
-
-
-
- -
-
- {% for payment_method_item in payment_methods %} -
- -
- {% endfor %} -
-
-
-
- -
- {% for store_item in store_list %} -
{{ text_form_item_store_name }}: {{ store_item.name }}
- - {% endfor %} -
-
- -
-
-
-
-
- -
-
-

{{ text_shipping_rules_list }}

- {{ button_edit }} -
-
-
- {% if shipping_rules %} - - - - - - - - - - {% for rule in shipping_rules %} - - - - - - - - - {% endfor %} -
{{ column_shipping_rule_target_country }}{{ column_shipping_rule_default_price }}{{ column_shipping_rule_free_over_limit }}{{ column_shipping_rule_is_enabled }}{{ column_shipping_rule_weight_rules }}{{ column_action }}
{{ rule.country_name }}{{ rule.default_price }}{{ rule.free_over_limit }} - {% if rule.is_enabled %} {{ text_enabled }} {% else %} {{ text_disabled }} {% endif %} - {{ rule.weight_rules_description }} - -
- {% else %} - - - - -
{{ text_no_shipping_rules }}
- {% endif %} -
-
-
- -
-
-

{{ text_weight_rules_list }}

-
-
-
- {% if weight_rules %} - {% for country_rules in weight_rules %} - - - - - - - - - - {% for rule in country_rules.items %} - - - - - - {% endfor %} -
- {{ country_rules.country_name }} - {{ button_edit }} -
{{ column_weight_rule_min_weight }}{{ column_weight_rule_max_weight }}{{ column_weight_rule_price }}
{{ rule.min_weight }}{{ rule.max_weight }}{{ rule.price }}
- {% endfor %} - {% else %} - - - - -
{{ text_no_weight_rules }}
- {% endif %} -
-
-
- -
-
-{{ footer }} \ No newline at end of file +{{ header }}{{ column_left }} +
+ +
+ {% if error_warning_multirow %} +
+ {% for error_warning_row in error_warning_multirow %} +

{{ error_warning_row }}

+ {% endfor %} + +
+ {% endif %} + {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} + {% if success %} +
{{ success }} + +
+ {% endif %} +
+
+
+ +
+

+ {{ text_module_config }} +

+
+
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ {% for order_status_item in eshop_order_statuses %} +
+ +
+ {% endfor %} +
+
+
+
+ +
+
+ {% for payment_method_item in payment_methods %} +
+ +
+ {% endfor %} +
+
+
+
+ +
+ {% for store_item in store_list %} +
{{ text_form_item_store_name }}: {{ store_item.name }}
+ + {% endfor %} +
+
+ +
+
+
+
+
+ +
+
+

{{ text_shipping_rules_list }}

+ {{ button_edit }} +
+
+
+ {% if shipping_rules %} + + + + + + + + + + {% for rule in shipping_rules %} + + + + + + + + + {% endfor %} +
{{ column_shipping_rule_target_country }}{{ column_shipping_rule_default_price }}{{ column_shipping_rule_free_over_limit }}{{ column_shipping_rule_is_enabled }}{{ column_shipping_rule_weight_rules }}{{ column_action }}
{{ rule.country_name }}{{ rule.default_price }}{{ rule.free_over_limit }} + {% if rule.is_enabled %} {{ text_enabled }} {% else %} {{ text_disabled }} {% endif %} + {{ rule.weight_rules_description }} + +
+ {% else %} + + + + +
{{ text_no_shipping_rules }}
+ {% endif %} +
+
+
+ +
+
+

{{ text_weight_rules_list }}

+
+
+
+ {% if weight_rules %} + {% for country_rules in weight_rules %} + + + + + + {# TODO: show weight unit #} + + + + + {% for rule in country_rules.items %} + + + + + + {% endfor %} +
+ {{ country_rules.country_name }} + {{ button_edit }} +
{{ column_weight_rule_min_weight }}{{ column_weight_rule_max_weight }}{{ column_weight_rule_price }}
{{ rule.min_weight }}{{ rule.max_weight }}{{ rule.price }}
+ {% endfor %} + {% else %} + + + + +
{{ text_no_weight_rules }}
+ {% endif %} +
+
+
+ +
+
+

{{ text_about_extension }}

+
+
+ {{ text_extension_version }}: {{ extension_version }} +
+
+ +
+
+{{ footer }} diff --git a/admin/view/template/extension/shipping/zasilkovna_orders.twig b/upload/admin/view/template/extension/shipping/zasilkovna_orders.twig similarity index 98% rename from admin/view/template/extension/shipping/zasilkovna_orders.twig rename to upload/admin/view/template/extension/shipping/zasilkovna_orders.twig index a065ef0..5795845 100644 --- a/admin/view/template/extension/shipping/zasilkovna_orders.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna_orders.twig @@ -1,295 +1,295 @@ -{{ header }}{{ column_left }} -
- -
-
- -
-
-
-

{{ text_order_list }}

-
-
-
-
- - - - - - - - - - - - - - - - - {% if orders %} - {% for order in orders %} - - - - - - - - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
- {% if sort == 'o.order_id' %} - {{ column_order_id }} - {% else %} - {{ column_order_id }} - {% endif %} - - {% if sort == 'customer' %} - {{ column_customer }} - {% else %} - {{ column_customer }} - {% endif %} - - {% if sort == 'order_status_id' %} - {{ column_order_status }} - {% else %} - {{ column_order_status }} - {% endif %} - - {% if sort == 'o.total' %} - {{ column_order_total }} - {% else %} - {{ column_order_total }} - {% endif %} - - {{ column_cod }} - - {% if sort == 'date_added' %} - {{ column_order_date }} - {% else %} - {{ column_order_date }} - {% endif %} - - {% if sort == 'oz.branch_name' %} - {{ column_branch_name }} - {% else %} - {{ column_branch_name }} - {% endif %} - - {% if sort == 'exported' %} - {{ column_exportDate }} - {% else %} - {{ column_exportDate }} - {% endif %} -
{% if order.order_id in selected %} - - {% else %} - - {% endif %} - {{ order.order_id }}{{ order.customer }}{{ order.order_status }}{{ order.total }}{% if order.is_cod %} {{ text_yes }} {% else %} {{ text_no }} {% endif %} {{ order.date_added }}{{ order.branch_name }} (ID: {{ order.branch_id }}){{ order.exported }}
{{ text_no_results }}
-
-
-
-
{{ pagination }}
-
{{ results }}
-
-
-
-
-
- - - - - -
+{{ header }}{{ column_left }} +
+ +
+
+ +
+
+
+

{{ text_order_list }}

+
+
+
+
+ + + + + + + + + + + + + + + + + {% if orders %} + {% for order in orders %} + + + + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
+ {% if sort == 'o.order_id' %} + {{ column_order_id }} + {% else %} + {{ column_order_id }} + {% endif %} + + {% if sort == 'customer' %} + {{ column_customer }} + {% else %} + {{ column_customer }} + {% endif %} + + {% if sort == 'order_status_id' %} + {{ column_order_status }} + {% else %} + {{ column_order_status }} + {% endif %} + + {% if sort == 'o.total' %} + {{ column_order_total }} + {% else %} + {{ column_order_total }} + {% endif %} + + {{ column_cod }} + + {% if sort == 'date_added' %} + {{ column_order_date }} + {% else %} + {{ column_order_date }} + {% endif %} + + {% if sort == 'oz.branch_name' %} + {{ column_branch_name }} + {% else %} + {{ column_branch_name }} + {% endif %} + + {% if sort == 'exported' %} + {{ column_exportDate }} + {% else %} + {{ column_exportDate }} + {% endif %} +
{% if order.order_id in selected %} + + {% else %} + + {% endif %} + {{ order.order_id }}{{ order.customer }}{{ order.order_status }}{{ order.total }}{% if order.is_cod %} {{ text_yes }} {% else %} {{ text_no }} {% endif %} {{ order.date_added }}{{ order.branch_name }} (ID: {{ order.branch_id }}){{ order.exported }}
{{ text_no_results }}
+
+
+
+
{{ pagination }}
+
{{ results }}
+
+
+
+
+
+ + + + + +
{{ footer }} \ No newline at end of file diff --git a/admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig b/upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig similarity index 98% rename from admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig rename to upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig index 4fba38c..380487b 100644 --- a/admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_form.twig @@ -1,66 +1,66 @@ -{{ header }}{{ column_left }} -
- -
- {% if error_warning %} -
{{ error_warning }} - -
- {% endif %} -
-
-

{{ text_form_title }}

-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
+{{ header }}{{ column_left }} +
+ +
+ {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} +
+
+

{{ text_form_title }}

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
{{ footer }} \ No newline at end of file diff --git a/admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig b/upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig similarity index 97% rename from admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig rename to upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig index 6ec8260..ff130c4 100644 --- a/admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna_shipping_rules_list.twig @@ -1,74 +1,74 @@ -{{ header }}{{ column_left }} -
- -
- {% if success %} -
{{ success }} - -
- {% endif %} - {% if error_warning %} -
{{ error_warning }} - -
- {% endif %} -
-
-

{{ text_shipping_rules_list }}

-
-
-
-
- - - - - - - - - - - - - {% if shipping_rules %} - {% for rule in shipping_rules %} - - - - - - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
{{ column_shipping_rule_target_country }}{{ column_shipping_rule_default_price }}{{ column_shipping_rule_free_over_limit }}{{ column_shipping_rule_is_enabled }}{{ column_action }}
{{ rule.target_country_name }}{{ rule.default_price }}{{ rule.free_over_limit }} - {% if rule.is_enabled %} {{ text_enabled }} {% else %} {{ text_disabled }} {% endif %} -
{{ text_no_results }}
-
-
-
-
-
-
+{{ header }}{{ column_left }} +
+ +
+ {% if success %} +
{{ success }} + +
+ {% endif %} + {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} +
+
+

{{ text_shipping_rules_list }}

+
+
+
+
+ + + + + + + + + + + + + {% if shipping_rules %} + {% for rule in shipping_rules %} + + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
{{ column_shipping_rule_target_country }}{{ column_shipping_rule_default_price }}{{ column_shipping_rule_free_over_limit }}{{ column_shipping_rule_is_enabled }}{{ column_action }}
{{ rule.target_country_name }}{{ rule.default_price }}{{ rule.free_over_limit }} + {% if rule.is_enabled %} {{ text_enabled }} {% else %} {{ text_disabled }} {% endif %} +
{{ text_no_results }}
+
+
+
+
+
+
{{ footer }} \ No newline at end of file diff --git a/admin/view/template/extension/shipping/zasilkovna_weight_rules.twig b/upload/admin/view/template/extension/shipping/zasilkovna_weight_rules.twig similarity index 97% rename from admin/view/template/extension/shipping/zasilkovna_weight_rules.twig rename to upload/admin/view/template/extension/shipping/zasilkovna_weight_rules.twig index 8f440b2..94040d2 100644 --- a/admin/view/template/extension/shipping/zasilkovna_weight_rules.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna_weight_rules.twig @@ -1,70 +1,71 @@ -{{ header }}{{ column_left }} -
- -
- {% if success %} -
{{ success }} - -
- {% endif %} - {% if error_warning %} -
{{ error_warning }} - -
- {% endif %} -
-
-

{{ text_weight_rules_list }} - {{ text_country_name }}

-
-
-
-
- - - - - - - - - - - - {% if weight_rules %} - {% for rule in weight_rules %} - - - - - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
{{ column_weight_rule_min_weight }}{{ column_weight_rule_max_weight }}{{ column_weight_rule_price }}{{ column_action }}
{{ rule.min_weight }}{{ rule.max_weight }}{{ rule.price }}
{{ text_no_results }}
-
-
-
-
-
-
+{{ header }}{{ column_left }} +
+ +
+ {% if success %} +
{{ success }} + +
+ {% endif %} + {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} +
+
+

{{ text_weight_rules_list }} - {{ text_country_name }}

+
+
+
+
+ + + + + {# TODO: show weight unit #} + + + + + + + + {% if weight_rules %} + {% for rule in weight_rules %} + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
{{ column_weight_rule_min_weight }}{{ column_weight_rule_max_weight }}{{ column_weight_rule_price }}{{ column_action }}
{{ rule.min_weight }}{{ rule.max_weight }}{{ rule.price }}
{{ text_no_results }}
+
+
+
+
+
+
{{ footer }} \ No newline at end of file diff --git a/admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig b/upload/admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig similarity index 84% rename from admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig rename to upload/admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig index 986f6e6..a0c2fb2 100644 --- a/admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig +++ b/upload/admin/view/template/extension/shipping/zasilkovna_weight_rules_form.twig @@ -1,51 +1,52 @@ -{{ header }}{{ column_left }} -
- -
- {% if error_warning %} -
{{ error_warning }} - -
- {% endif %} -
-
-

{{ text_form_title }}

-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
+{{ header }}{{ column_left }} +
+ +
+ {% if error_warning %} +
{{ error_warning }} + +
+ {% endif %} +
+
+

{{ text_form_title }}

+
+
+
+ {# TODO: show weight unit #} +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
{{ footer }} \ No newline at end of file diff --git a/upload/catalog/controller/extension/module/zasilkovna.php b/upload/catalog/controller/extension/module/zasilkovna.php new file mode 100644 index 0000000..1fb925c --- /dev/null +++ b/upload/catalog/controller/extension/module/zasilkovna.php @@ -0,0 +1,114 @@ +load->model('extension/shipping/zasilkovna'); + $defaults = $this->model_extension_shipping_zasilkovna->loadSelectedBranch(); + + $this->response->addHeader('Content-Type: application/json'); + $this->response->setOutput(json_encode($defaults)); + } + + /** + * Loads properties of selected branch from session. + * + * @throws Exception + */ + public function saveSelectedBranch() { + $this->load->model('extension/shipping/zasilkovna'); + $this->model_extension_shipping_zasilkovna->saveSelectedBranch(); + } + + /** + * Save additional order data to DB during "order confirm". + * All required records with order data are created in DB during this step. + * This method is called by "after" event on catalog/controller/checkout/confirm. + * + * @param string $route + * @param array $args + * @param int $output + * @throws Exception + */ + public function saveOrderData(&$route, &$args, &$output) { + $this->load->model('extension/shipping/zasilkovna'); + $this->model_extension_shipping_zasilkovna->saveOrderData(); + } + + /** + * Clean-up of additional order data in session when order is finished. + * This method is called as "before" event on catalog/controller/checkout/success. + * + * @param string $route + * @param array $args + * @return void + * @throws Exception + */ + public function sessionCleanup(&$route, &$args) + { + // check if order is already completed + // the same check is implemented in original method + if (isset($this->session->data['order_id'])) { + $this->load->model('extension/shipping/zasilkovna'); + $this->model_extension_shipping_zasilkovna->sessionCleanup(); + } + } + + public function sessionCleanupAndSaveSelectedCountry($cartType, $newCountryId, $oldCountryId) + { + $this->load->model('extension/shipping/zasilkovna'); + + if ($oldCountryId != $newCountryId) { + $this->model_extension_shipping_zasilkovna->sessionCleanup(); + } + $this->model_extension_shipping_zasilkovna->saveSelectedCountry($cartType); + } + + public function sessionCheckOnShippingChange(&$route, &$args) + { + $oldAddressId = $this->session->data["shipping_address"]["address_id"]; + $newAddressId = $this->request->post["address_id"]; + if ($oldAddressId != $newAddressId) { + $this->load->model('extension/shipping/zasilkovna'); + $this->model_extension_shipping_zasilkovna->sessionCleanup(); + } + } + + public function sessionCheckOnShippingChangeGuest(&$route, &$args) + { + $newCountryId = $this->request->post['country_id']; + $oldCountryId = $this->session->data['country_id']; + $this->sessionCleanupAndSaveSelectedCountry('standard', $newCountryId, $oldCountryId); + } + + /** + * Event for OPC Journal 3 + * @param $route + * @param $args + */ + public function journal3CheckoutSave(&$route, &$args) + { + $newCountryId = $this->request->post['order_data']['shipping_country_id']; + $oldCountryId = isset($this->session->data['country_id']) ? $this->session->data['country_id'] : NULL; + $this->sessionCleanupAndSaveSelectedCountry('journal3', $newCountryId, $oldCountryId); + } + + public function addStyleAndScript(&$route, &$args) + { + $this->document->addScript('https://widget.packeta.com/v6/www/js/library.js'); + $this->document->addScript('catalog/view/javascript/zasilkovna/shippingExtension.js'); + $this->document->addStyle('catalog/view/theme/zasilkovna/zasilkovna.css'); + } +} diff --git a/catalog/language/cs-cz/extension/shipping/zasilkovna.php b/upload/catalog/language/cs-cz/extension/shipping/zasilkovna.php similarity index 97% rename from catalog/language/cs-cz/extension/shipping/zasilkovna.php rename to upload/catalog/language/cs-cz/extension/shipping/zasilkovna.php index a64d2b9..e654746 100644 --- a/catalog/language/cs-cz/extension/shipping/zasilkovna.php +++ b/upload/catalog/language/cs-cz/extension/shipping/zasilkovna.php @@ -1,13 +1,13 @@ -config->get('shipping_zasilkovna_status')) { - return false; - } - - // check if total weight of order is lower than maximal allowed weight (if limit is defined) - $maxWeight = (int)$this->config->get('shipping_zasilkovna_weight_max'); - if (!empty($maxWeight) && $totalWeight > $maxWeight) { - return false; - } - - // check if target address is from allowed country - $targetCountry = strtolower($targetAddress['iso_code_2']); - if (!in_array($targetCountry, $this->supportedCountries)) { - return false; - } - - // check if target customer address is in allowed geo zone (if zone limitation is defined) - $configGeoZone = (int) $this->config->get('shipping_zasilkovna_geo_zone_id'); - if ($configGeoZone > 0) { - // get country and zone from target address - $cartCountry = $targetAddress['country_id']; - $cartZone = $targetAddress['zone_id']; - // check if given zone or whole country is part of geo zone from configuration - $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `geo_zone_id` = %s AND `country_id` = %s AND (`zone_id` = %s OR `zone_id` = 0)', - self::TABLE_ZONE_TO_GEO_ZONE, $configGeoZone, $cartCountry, $cartZone); - /** @var StdClass $queryResult */ - $queryResult = $this->db->query($sqlQuery); - if (0 == $queryResult->num_rows) { - return false; - } - } - - // all checks passed - return true; - } - - /** - * Calculation of shipping price. Returns price of shipping or -1 if price cannot be calculated. - * - * @param string $countryCode iso code of target country - * @param double $totalWeight total weight of order - * @param double $totalPrice total price of order - * @return array price of shipping and internal shipping service code - */ - private function calculatePrice($countryCode, $totalWeight, $totalPrice) { - // get properties of shipping for target country - $sqlQueryCountry = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `is_enabled` = 1;', self::TABLE_SHIPPING_RULES, - $this->db->escape($countryCode)); - /** @var StdClass $sqlResult */ - $sqlResult = $this->db->query($sqlQueryCountry); - if ($sqlResult->num_rows > 0) { // found record for target country - $countryRow = $sqlResult->row; - $countryExist = true; - } - else { // search for record for "other countries" - $countryExist = false; - $sqlQueryOtherCountries = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `is_enabled` = 1;', self::TABLE_SHIPPING_RULES, - self::OTHER_COUNTRIES_CODE); - /** @var StdClass $sqlResult */ - $sqlResult = $this->db->query($sqlQueryOtherCountries); - if ($sqlResult->num_rows > 0) { // found record for "other countries" - $countryRow = $sqlResult->row; - } - } - - if (isset($countryRow)) { - if ($countryRow['' . self::COLUMN_FREE_OVER_LIMIT . ''] > 0 && $totalPrice > $countryRow[self::COLUMN_FREE_OVER_LIMIT]) { - // price of order is over limit for free shipping - return [ - self::PARAM_PRICE => 0, - self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) - ]; - } - - // search for weight rule for given country - $sqlWeightRule = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `min_weight` <= %s AND `max_weight` > %s;', - self::TABLE_WEIGHT_RULES, $countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE, $totalWeight, $totalWeight); - /** @var StdClass $sqlResult */ - $sqlResult = $this->db->query($sqlWeightRule); - - if ($sqlResult->num_rows > 0) { // found weight rule - return [ - self::PARAM_PRICE => $sqlResult->row[self::COLUMN_PRICE], - self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) - ]; - } - - // check if default price for country is defined - if ($countryRow[self::COLUMN_DEFAULT_PRICE] > 0) { - return [ - self::PARAM_PRICE => $countryRow[self::COLUMN_DEFAULT_PRICE], - self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) - ]; - } - } - - // check if price is over global limit for free shipping - $globalFreeShippingLimit = (float)$this->config->get('shipping_zasilkovna_default_free_shipping_limit'); - if ($globalFreeShippingLimit > 0 && $totalPrice > $globalFreeShippingLimit) { - return [ - self::PARAM_PRICE => 0, - self::PARAM_SERVICE_NAME => 'any' - ]; - } - - // check if global price for shipping is defined - $globalShippingPrice = (float)$this->config->get('shipping_zasilkovna_default_shipping_price'); - if ($globalShippingPrice > 0) { - return [ - self::PARAM_PRICE => $globalShippingPrice, - self::PARAM_SERVICE_NAME => 'any' - ]; - } - - // price cannot be calculated - return [ - self::PARAM_PRICE => self::PRICE_UNKNOWN, - self::PARAM_SERVICE_NAME => '' - ]; - } - - /** - * Returns parameters of available options for shipping. - * It is called from ControllerCheckoutShippingMethod for all registered shipping extensions. - * - * @param array $targetAddress - * @return array - */ - public function getQuote($targetAddress) { - $this->load->language('extension/shipping/zasilkovna'); - $cartTotalWeight = $this->cart->getWeight(); - $cartCountryCode = strtolower($this->cart->session->data["shipping_address"]["iso_code_2"]); - $cartTotalPrice = $this->cart->getTotal(); - - // check base conditions for possibility to use "Zasilkovna" for shipping - $checkResult = $this->checkBasicConditions($cartTotalWeight, $targetAddress); - if (!$checkResult) { - return []; - } - - // preparing inline code for include to html page - // standalone files with static JS code and JS configuration data, must be included inline into description text of shipping item for zasilkovna - // there is no way how to include it directly through controller using $this->document->addXXX() - $inlineCode = $this->prepareCssCode() . $this->prepareJsCoonfigData($targetAddress) . $this->prepareJsCode(); - - // calculate price of shipping (only one item can be displayed) - $calcResult = $this->calculatePrice($cartCountryCode, $cartTotalWeight, $cartTotalPrice); - $shippingPrice = $calcResult[self::PARAM_PRICE]; - $serviceCodeName = $calcResult[self::PARAM_SERVICE_NAME]; - if (self::PRICE_UNKNOWN == $shippingPrice) { - return []; - } - - // preparation of properties for shipping service definition - $titleTextId = 'shipping_' . $serviceCodeName; - $taxClassId = $this->config->get('shipping_zasilkovna_tax_class_id'); - - // preparation of description text including inline Javascript and CSS code - $taxValue = $this->tax->calculate($shippingPrice, $this->config->get('shipping_zasilkovna_tax_class_id'), $this->config->get('config_tax')); - $descriptionText = $this->currency->format($taxValue, $this->session->data['currency']) - . $inlineCode . ''; - - $quote_data[$serviceCodeName] = [ - 'code' => 'zasilkovna.' . $serviceCodeName, - 'title' => $this->language->get($titleTextId), - 'cost' => $shippingPrice, - 'tax_class_id' => $taxClassId, - 'text' => $descriptionText - ]; - - $method_data = [ - 'code' => 'zasilkovna', - 'title' => $this->language->get('text_title'), - 'quote' => $quote_data, - 'sort_order' => $this->config->get('shipping_zasilkovna_sort_order'), - 'error' => false - ]; - - return $method_data; - } - - /** - * Returns content of required CSS file as inline code. - * - * @return string - */ - private function prepareCssCode() { - $cssFileName = dirname(__FILE__, 4) . '/view/stylesheet/zasilkovna/zasilkovna.css'; - $cssPrefix = "\n"; - - return $cssPrefix . file_get_contents($cssFileName) . $cssSuffix; - } - - /** - * Returns content of required JS files as inline code. - * - * @return string - */ - private function prepareJsCode() { - $jsFileDir = dirname(__FILE__, 4) . '/view/javascript/zasilkovna'; - $jsPrefix = "\n\n"; - - // include code for load JS envelope of map widget directly from Zasilkovna - $jsCode = '' . "\n"; - // include code for static js file - $jsCode .= $jsPrefix . file_get_contents($jsFileDir . '/shippingExtension.js') . $jsSuffix; - - // include call of initialization method - $jsCode .= $jsPrefix . 'window.setTimeout("zasilkovnaInitAll();",100);' . $jsSuffix; - - return $jsCode; - } - - /** - * Returns configuration data for JS as inline code. - * - * @param array $address shipping address of customer - * @return string - */ - private function prepareJsCoonfigData($address) { - $jsPrefix = "\n\n"; - - // detect widget language and countries enabled for map widget - $targetCountry = strtolower($address['iso_code_2']); - $userLanguage = $this->language->get('code'); - if (!in_array($userLanguage, $this->supportedLanguages)) { - $userLanguage = 'en'; - } - - $parameters = [ - 'apiKey' => $this->config->get('shipping_zasilkovna_api_key'), - 'language' => $userLanguage, - 'enabledCountries' => $targetCountry, - 'customerAddress' => $address['address_1'] . ' ' . $address['address_2'] . $address['city'], - 'selectBranchText' => $this->language->get('choose_branch'), - 'noBranchSelectedText' => $this->language->get('no_branch_selected'), - 'appIdentity' => self::APP_IDENTITY - ]; - - $jsCode = $jsPrefix; - foreach ($parameters as $paramName => $paramValue) { - $paramValue = str_replace('\\', '\\\\', $paramValue); - $paramValue = str_replace('"', '\\"', $paramValue); - $jsCode .= $paramName . ': "' . str_replace('\\', '\\\\', $paramValue) . '",'; - } - $jsCode .= $jsSuffix; - - return $jsCode; - } - - /** - * Loads properties of selected branch from session. - * - * @return array - */ - public function loadSelectedBranch() { - $defaults = [ - self::KEY_BRANCH_ID => '', - self::KEY_BRANCH_NAME => '', - self::KEY_BRANCH_DESCRIPTION => '' - ]; - - if (isset($this->session->data[self::KEY_BRANCH_ID])) { - $defaults[self::KEY_BRANCH_ID] = $this->session->data[self::KEY_BRANCH_ID]; - $defaults[self::KEY_BRANCH_NAME] = $this->session->data[self::KEY_BRANCH_NAME]; - $defaults[self::KEY_BRANCH_DESCRIPTION] = $this->session->data[self::KEY_BRANCH_DESCRIPTION]; - } - - return $defaults; - } - - /** - * Save properties of selected branch from session. - * - * @return void - */ - public function saveSelectedBranch() { - if ($this->request->post[self::KEY_BRANCH_ID]) { - $this->session->data[self::KEY_BRANCH_ID] = $this->request->post[self::KEY_BRANCH_ID]; - $this->session->data[self::KEY_BRANCH_NAME] = $this->request->post[self::KEY_BRANCH_NAME]; - $this->session->data[self::KEY_BRANCH_DESCRIPTION] = $this->request->post[self::KEY_BRANCH_DESCRIPTION]; - } - } - - /** - * Save additional order data to DB during "order confirm". - * All required records with order data are created in DB during this step. - * This method is called by "after" event on catalog/controller/checkout/confirm. - * - * @return void - */ - public function saveOrderData() { - // check if selected shipping method is stored in session, it should be saved in step 4 of checkout - if (!isset($this->session->data['shipping_method']['code'])) { - return; - } - - // check if shipping name contains word "zasilkovna", format shlould be "zasilkovna." - // title of shipping method for given country is set in settings of plugin - $selectedShipping = $this->session->data['shipping_method']['code']; - if (strpos($selectedShipping, 'zasilkovna') === false) { - return; - } - - // internal ID of order in e-shop - $orderId = (int) $this->session->data['order_id']; - // internal ID of selected target branch for pick-up - $branchId = (int) $this->session->data[self::KEY_BRANCH_ID]; - // name of selected branch (provided by zasilkovna) - $branchName = $this->session->data[self::KEY_BRANCH_NAME]; - // total weight of all products in cart (including product options which can modify product weight) - $totalWeight = (double) $this->cart->getWeight(); - - $sql = sprintf('INSERT INTO `%szasilkovna_orders` (`order_id`, `branch_id`, `branch_name`, `total_weight`) VALUES (%s, %s, "%s", %s);', - DB_PREFIX, $orderId, $branchId, $this->db->escape($branchName), $totalWeight); - $this->db->query($sql); - } - - /** - * Clean-up of additional order data in session when order is finished. - * This method is called as "before" event on catalog/controller/checkout/success. - * - * @return void - */ - public function sessionCleanup() { - // check if order is already completed - // the same check is implemented in original method - if (isset($this->session->data['order_id'])) { - unset($this->session->data[self::KEY_BRANCH_ID]); - unset($this->session->data[self::KEY_BRANCH_NAME]); - unset($this->session->data[self::KEY_BRANCH_DESCRIPTION]); - } - } -} \ No newline at end of file +config->get('shipping_zasilkovna_status')) { + return false; + } + + // check if total weight of order is lower than maximal allowed weight (if limit is defined) + $maxWeight = (int)$this->config->get('shipping_zasilkovna_weight_max'); + if (!empty($maxWeight) && $totalWeight > $maxWeight) { + return false; + } + + // check if target customer address is in allowed geo zone (if zone limitation is defined) + $configGeoZone = (int) $this->config->get('shipping_zasilkovna_geo_zone_id'); + if ($configGeoZone > 0) { + // get country and zone from target address + $cartCountry = $targetAddress['country_id']; + $cartZone = $targetAddress['zone_id']; + // check if given zone or whole country is part of geo zone from configuration + $sqlQuery = sprintf('SELECT * FROM `%s` WHERE `geo_zone_id` = %s AND `country_id` = %s AND (`zone_id` = %s OR `zone_id` = 0)', + self::TABLE_ZONE_TO_GEO_ZONE, $configGeoZone, $cartCountry, $cartZone); + /** @var StdClass $queryResult */ + $queryResult = $this->db->query($sqlQuery); + if (0 == $queryResult->num_rows) { + return false; + } + } + + // all checks passed + return true; + } + + /** + * Calculation of shipping price. Returns price of shipping or -1 if price cannot be calculated. + * + * @param string $countryCode iso code of target country + * @param double $totalWeight total weight of order + * @param double $totalPrice total price of order + * @return array price of shipping and internal shipping service code + */ + private function calculatePrice($countryCode, $totalWeight, $totalPrice) { + // get properties of shipping for target country + $sqlQueryCountry = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `is_enabled` = 1;', self::TABLE_SHIPPING_RULES, + $this->db->escape($countryCode)); + /** @var StdClass $sqlResult */ + $sqlResult = $this->db->query($sqlQueryCountry); + if ($sqlResult->num_rows > 0) { // found record for target country + $countryRow = $sqlResult->row; + $countryExist = true; + } + else { // search for record for "other countries" + $countryExist = false; + $sqlQueryOtherCountries = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `is_enabled` = 1;', self::TABLE_SHIPPING_RULES, + self::OTHER_COUNTRIES_CODE); + /** @var StdClass $sqlResult */ + $sqlResult = $this->db->query($sqlQueryOtherCountries); + if ($sqlResult->num_rows > 0) { // found record for "other countries" + $countryRow = $sqlResult->row; + } + } + + if (isset($countryRow)) { + if ($countryRow['' . self::COLUMN_FREE_OVER_LIMIT . ''] > 0 && $totalPrice > $countryRow[self::COLUMN_FREE_OVER_LIMIT]) { + // price of order is over limit for free shipping + return [ + self::PARAM_PRICE => 0, + self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) + ]; + } + + // search for weight rule for given country + $sqlWeightRule = sprintf('SELECT * FROM `%s` WHERE `target_country` = "%s" AND `min_weight` <= %s AND `max_weight` > %s;', + self::TABLE_WEIGHT_RULES, $countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE, $totalWeight, $totalWeight); + /** @var StdClass $sqlResult */ + $sqlResult = $this->db->query($sqlWeightRule); + + if ($sqlResult->num_rows > 0) { // found weight rule + return [ + self::PARAM_PRICE => $sqlResult->row[self::COLUMN_PRICE], + self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) + ]; + } + + // check if default price for country is defined + if ($countryRow[self::COLUMN_DEFAULT_PRICE] > 0) { + return [ + self::PARAM_PRICE => $countryRow[self::COLUMN_DEFAULT_PRICE], + self::PARAM_SERVICE_NAME => ($countryExist ? $countryCode : self::OTHER_COUNTRIES_CODE) + ]; + } + } + + // check if price is over global limit for free shipping + $globalFreeShippingLimit = (float)$this->config->get('shipping_zasilkovna_default_free_shipping_limit'); + if ($globalFreeShippingLimit > 0 && $totalPrice > $globalFreeShippingLimit) { + return [ + self::PARAM_PRICE => 0, + self::PARAM_SERVICE_NAME => 'any' + ]; + } + + // check if global price for shipping is defined + $globalShippingPrice = (float)$this->config->get('shipping_zasilkovna_default_shipping_price'); + if ($globalShippingPrice > 0) { + return [ + self::PARAM_PRICE => $globalShippingPrice, + self::PARAM_SERVICE_NAME => 'any' + ]; + } + + // price cannot be calculated + return [ + self::PARAM_PRICE => self::PRICE_UNKNOWN, + self::PARAM_SERVICE_NAME => '' + ]; + } + + /** + * Returns parameters of available options for shipping. + * It is called from ControllerCheckoutShippingMethod for all registered shipping extensions. + * + * @param array $targetAddress + * @return array + */ + public function getQuote($targetAddress) { + $this->load->language('extension/shipping/zasilkovna'); + $cartTotalWeight = $this->cart->getWeight(); + $cartCountryCode = strtolower($this->cart->session->data["shipping_address"]["iso_code_2"]); + $cartTotalPrice = $this->cart->getTotal(); + + // check base conditions for possibility to use "Zasilkovna" for shipping + $checkResult = $this->checkBasicConditions($cartTotalWeight, $targetAddress); + if (!$checkResult) { + return []; + } + + $jsConfigData = $this->getJsConfig($targetAddress); + + // calculate price of shipping (only one item can be displayed) + $calcResult = $this->calculatePrice($cartCountryCode, $cartTotalWeight, $cartTotalPrice); + $shippingPrice = $calcResult[self::PARAM_PRICE]; + $serviceCodeName = $calcResult[self::PARAM_SERVICE_NAME]; + if (self::PRICE_UNKNOWN == $shippingPrice) { + return []; + } + + // preparation of properties for shipping service definition + $taxClassId = $this->config->get('shipping_zasilkovna_tax_class_id'); + + // preparation of description text including inline Javascript and CSS code + $taxValue = $this->tax->calculate($shippingPrice, $this->config->get('shipping_zasilkovna_tax_class_id'), $this->config->get('config_tax')); + $descriptionText = $this->currency->format($taxValue, $this->session->data['currency']) + . ''; + + $quote_data[$serviceCodeName] = [ + 'code' => 'zasilkovna.' . $serviceCodeName, + 'title' => $this->language->get('shipping'), + 'cost' => $shippingPrice, + 'tax_class_id' => $taxClassId, + 'text' => $descriptionText + ]; + + $method_data = [ + 'code' => 'zasilkovna', + 'title' => $this->language->get('text_title'), + 'quote' => $quote_data, + 'sort_order' => $this->config->get('shipping_zasilkovna_sort_order'), + 'error' => false + ]; + + return $method_data; + } + + /** + * Returns content of required CSS file as inline code. + * + * @return string + */ + private function prepareCssCode() { + $cssFileName = DIR_APPLICATION . 'view/theme/zasilkovna/zasilkovna.css'; + $cssPrefix = "\n"; + + return $cssPrefix . file_get_contents($cssFileName) . $cssSuffix; + } + + /** identification of e-shop module version + * @return string + */ + private static function getAppIdentity() + { + require_once DIR_APPLICATION . '../admin/controller/extension/shipping/zasilkovna.php'; + return 'opencart-3.0-packeta-' . \ControllerExtensionShippingZasilkovna::VERSION; + } + + private function getJsConfig($address) + { + // detect widget language and countries enabled for map widget + $targetCountry = strtolower($address['iso_code_2']); + $userLanguage = $this->language->get('code'); + if (!in_array($userLanguage, $this->supportedLanguages)) { + $userLanguage = 'en'; + } + + $parameters = [ + 'api_key' => $this->config->get('shipping_zasilkovna_api_key'), + 'language' => $userLanguage, + 'enabled_countries' => $targetCountry, + 'customer_address' => $address['address_1'] . ' ' . $address['address_2'] . $address['city'], + 'select_branch_text' => $this->language->get('choose_branch'), + 'no_branch_selected_text' => $this->language->get('no_branch_selected'), + 'app_identity' => self::getAppIdentity(), + ]; + + $output = ''; + foreach ($parameters as $param => $value) { + $output .= sprintf(' data-%s="%s"', $param, htmlspecialchars($value)); + } + + return $output; + } + + /** + * Loads properties of selected branch from session. + * + * @return array + */ + public function loadSelectedBranch() { + $defaults = [ + self::KEY_BRANCH_ID => '', + self::KEY_BRANCH_NAME => '', + self::KEY_BRANCH_DESCRIPTION => '', + self::KEY_CARRIER_ID => '', + self::KEY_CARRIER_PICKUP_POINT => '', + ]; + + if (isset($this->session->data[self::KEY_BRANCH_ID])) { + $defaults[self::KEY_BRANCH_ID] = $this->session->data[self::KEY_BRANCH_ID]; + $defaults[self::KEY_BRANCH_NAME] = $this->session->data[self::KEY_BRANCH_NAME]; + $defaults[self::KEY_BRANCH_DESCRIPTION] = $this->session->data[self::KEY_BRANCH_DESCRIPTION]; + } + if (isset($this->session->data[self::KEY_CARRIER_ID])) { + $defaults[self::KEY_CARRIER_ID] = $this->session->data[self::KEY_CARRIER_ID]; + } + if (isset($this->session->data[self::KEY_CARRIER_PICKUP_POINT])) { + $defaults[self::KEY_CARRIER_PICKUP_POINT] = $this->session->data[self::KEY_CARRIER_PICKUP_POINT]; + } + + return $defaults; + } + + /** + * Save properties of selected branch from session. + * + * @return void + */ + public function saveSelectedBranch() { + $this->session->data[self::KEY_BRANCH_ID] = $this->request->post[self::KEY_BRANCH_ID]; + $this->session->data[self::KEY_BRANCH_NAME] = $this->request->post[self::KEY_BRANCH_NAME]; + $this->session->data[self::KEY_BRANCH_DESCRIPTION] = $this->request->post[self::KEY_BRANCH_DESCRIPTION]; + $this->session->data[self::KEY_CARRIER_ID] = $this->request->post[self::KEY_CARRIER_ID]; + $this->session->data[self::KEY_CARRIER_PICKUP_POINT] = $this->request->post[self::KEY_CARRIER_PICKUP_POINT]; + } + + public function saveSelectedCountry($cartType) + { + // add new cart here 2/3 + switch ($cartType) { + case 'standard': + $countryId = $this->request->post[self::KEY_COUNTRY_ID]; + break; + case 'journal3': + $countryId = $this->request->post['order_data']['shipping_country_id']; + break; + } + if ($countryId) { + $this->session->data[self::KEY_COUNTRY_ID] = $countryId; + } + } + + /** + * Save additional order data to DB during "order confirm". + * All required records with order data are created in DB during this step. + * This method is called by "after" event on catalog/controller/checkout/confirm. + * + * @return void + */ + public function saveOrderData() { + // check if selected shipping method is stored in session, it should be saved in step 4 of checkout + if (!isset($this->session->data['shipping_method']['code'])) { + return; + } + + // check if shipping name contains word "zasilkovna", format shlould be "zasilkovna." + // title of shipping method for given country is set in settings of plugin + $selectedShipping = $this->session->data['shipping_method']['code']; + if (strpos($selectedShipping, 'zasilkovna') === false) { + return; + } + + // internal ID of order in e-shop + $orderId = (int) $this->session->data['order_id']; + + // TODO: optimize? + // this check is needed because the method is being called by checkout/save/after trigger of OPC journal3 + // and not only once by checkout/confirm/after as usual + if ( + !$orderId || + !isset($this->session->data[self::KEY_BRANCH_ID]) + ) { + return; + } + if (empty($this->session->data[self::KEY_CARRIER_ID])) { + // internal ID of selected target pick-up point ID + $branchId = (int) $this->session->data[self::KEY_BRANCH_ID]; + $carrierPickupPoint = null; + $isCarrier = 0; + } else { + $branchId = (int) $this->session->data[self::KEY_CARRIER_ID]; + $carrierPickupPoint = $this->session->data[self::KEY_CARRIER_PICKUP_POINT]; + $isCarrier = 1; + } + + // name of selected branch (provided by zasilkovna) + $branchName = $this->session->data[self::KEY_BRANCH_NAME]; + // total weight of all products in cart (including product options which can modify product weight) + $totalWeight = (double) $this->cart->getWeight(); + + $sql = sprintf('INSERT IGNORE INTO `%szasilkovna_orders` (`order_id`, `branch_id`, `branch_name`, `is_carrier`, `carrier_pickup_point`, `total_weight`) VALUES (%s, %s, "%s", %d, "%s", %s);', + DB_PREFIX, $orderId, $branchId, $this->db->escape($branchName), $isCarrier, $carrierPickupPoint, $totalWeight); + $this->db->query($sql); + } + + public function sessionCleanup() { + // check if order is already completed + // the same check is implemented in original method + if (isset($this->session->data['order_id'])) { + unset($this->session->data[self::KEY_BRANCH_ID]); + unset($this->session->data[self::KEY_BRANCH_NAME]); + unset($this->session->data[self::KEY_BRANCH_DESCRIPTION]); + unset($this->session->data[self::KEY_CARRIER_ID]); + unset($this->session->data[self::KEY_CARRIER_PICKUP_POINT]); + } + } +} diff --git a/catalog/view/javascript/zasilkovna/shippingExtension.js b/upload/catalog/view/javascript/zasilkovna/shippingExtension.js similarity index 63% rename from catalog/view/javascript/zasilkovna/shippingExtension.js rename to upload/catalog/view/javascript/zasilkovna/shippingExtension.js index d94cf66..302557b 100644 --- a/catalog/view/javascript/zasilkovna/shippingExtension.js +++ b/upload/catalog/view/javascript/zasilkovna/shippingExtension.js @@ -1,178 +1,240 @@ -// helper function used for shipping extension for zasilkovna - -/** - * Initialization of all required parts. - */ -function zasilkovnaInitAll() { - zasilkovnaCreateElementsandEvents(); - initializePacketaWidget(); - zasilkovnaLoadSelectedBranch(); -} - -/** - * Initialization of required elements and events. - */ -function zasilkovnaCreateElementsandEvents() { - var additionalElementsHtml = '' - + ''; - var selectedPointElementHtml = '
' - + '
' + window.zasilkovnaWidgetParameters.noBranchSelectedText + '
'; - var selectedPointElement; - var additionalElementsEnvelope; - var shippingElementEnvelope; - - // create envelope element with required additional html elements - additionalElementsEnvelope = document.createElement('div'); - additionalElementsEnvelope.setAttribute('id', 'packeta-envelope'); - additionalElementsEnvelope.innerHTML = additionalElementsHtml; - document.body.appendChild(additionalElementsEnvelope); - - // Adding additional visible element for display information about selected pickup point. - // Search for dummy element of "zasilkovna" shipping item is required because there is no id nor class which can - // be used for identification - shippingElementEnvelope = $('#packeta-first-shipping-item').parent().parent(); - selectedPointElement = document.createElement('div'); - selectedPointElement.setAttribute('class', 'packeta-shipping-item-envelope'); - selectedPointElement.innerHTML = selectedPointElementHtml; - shippingElementEnvelope.append(selectedPointElement); - - // adding onclick handler for radio buttons with list of shipping methods - $('input[name="shipping_method"]:radio').click(zasilkovnaShipmentMethodOnChange); -} - -/** - * Handler for change of shipping type (click on radio button) - */ -function zasilkovnaShipmentMethodOnChange() { - // check if radio button for zasilkovna is selected - var isZasilkovnaSelected = $(this).val().match('zasilkovna.*') !== null; - var selectedBranch = $('#packeta-branch-id').val(); - var isSubmitButtonDisabled = false; - - // disable "Continue" button if zasilkovna is selected but no branch is selected from map widget - isSubmitButtonDisabled = false; - if (isZasilkovnaSelected) { - if (selectedBranch === '') { - isSubmitButtonDisabled = true; - } - } - - $('#button-shipping-method').attr('disabled', isSubmitButtonDisabled); -} - -/** - * Handler for load of selected branch from session. - * It is called after initialization of additional HTML elements and JS events during after switch to "Step 4: Delivery Method" - * during "checkout". - */ -function zasilkovnaLoadSelectedBranch() { - $.ajax({ - url: 'index.php?route=extension/module/zasilkovna/loadSelectedBranch', - type: 'get', - dataType: 'json', - success: function(json) { - if (json.zasilkovna_branch_id !== '') { - $('#packeta-branch-id').val(json.zasilkovna_branch_id); - $('#packeta-branch-name').val(json.zasilkovna_branch_name); - $('#picked-delivery-place').html(json.zasilkovna_branch_description); - } - zasilkovnaUpdateSubmitButtonStatus(); - }, - error: function(xhr, ajaxOptions, thrownError) { - alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); - } - }); -} - -/** - * Sets status of "Continue" button according to selected shipping properties. - * Button "Continue" is disabled when "zasilkovna" is selected ad shipping method and target branch is not selected. - */ -function zasilkovnaUpdateSubmitButtonStatus() { - var selectedShipment = $('#collapse-shipping-method input[type=\'radio\']:checked'); - var isZasilkovnaSelected = selectedShipment.val().match('zasilkovna.*') !== null; - var selectedBranchId = $('#packeta-branch-id').val(); - - $('#button-shipping-method').attr('disabled', (isZasilkovnaSelected && selectedBranchId === '')); -} - -/** - * Handler for save selected branch to session. - * It it called after click on "Continue" button in "Step 4: Delivery Method". Another ajax request for save - * selected shipping method and comment is sent at the same time. - */ -function zasilkovnaSaveSelectedBranch() { - var branchId = $('#packeta-branch-id').val(), - dataToSend; - - dataToSend = { - zasilkovna_branch_id: branchId, - zasilkovna_branch_name: $('#packeta-branch-name').val(), - zasilkovna_branch_description: $('#picked-delivery-place').html() - }; - - $.ajax({ - url: 'index.php?route=extension/module/zasilkovna/saveSelectedBranch', - type: 'post', - data: dataToSend, - success: function() { - // enable "Continue" button for switch to next step in "checkout workflow" - $('#button-shipping-method').attr('disabled', false); - // mark carrier "Zasilkovna" as active when pickup point is selected - $('#packeta-first-shipping-item').parent().parent().find('input[type=radio]').prop('checked', true); - }, - error: function(xhr, ajaxOptions, thrownError) { - alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); - } - }); -} - -/** - * helper function for library to choose delivery point using map widget - * defined as function because it must be called when all required elements are created in DOM - */ -function initializePacketaWidget() { - // list of configuration properties for widget - var apiKey = window.zasilkovnaWidgetParameters.apiKey; - - - // preparation of parameters for widget - var widgetOptions = { - appIdentity: window.zasilkovnaWidgetParameters.appIdentity, - country: window.zasilkovnaWidgetParameters.enabledCountries, - language: window.zasilkovnaWidgetParameters.language - }; - - document.getElementById('open-packeta-widget').addEventListener('click', function (e) { - e.preventDefault(); - // displaying of map widget - Packeta.Widget.pick(apiKey, selectPickUpPointCallback, widgetOptions); - }); - -} - -/** - * Callback function for processing of pickup point selection using map widget. - * It is called by widget with object "ExtendedPoint" as parameter. - * - * @param targetPoint detail information about selected point - * @return void - */ -function selectPickUpPointCallback(targetPoint) { - if (null == targetPoint) { // selection of pickup point was cancelled - return; - } - - // save ID and name of selected point point to hidden input elements - document.getElementById('packeta-branch-id').value = targetPoint.id; - document.getElementById('packeta-branch-name').value = targetPoint.nameStreet; - - // show name of selected pickup point to user - document.getElementById('picked-delivery-place').innerHTML = targetPoint.nameStreet; - - // Save selected branch to session. It must be done now because it it not possible to send two ajax requests after click - // on "Continue" button. There is conflict if two php script wants to save to session. Session data saved by first script - // can be overwritten by second script. - // Button "Continue" is enabled when request is finished to avoid conflict described above. - zasilkovnaSaveSelectedBranch(); -} +// helper function used for shipping extension for zasilkovna + +// add new cart here 3/3 +var cartsConfig = { + urls: { + standard: /checkout\/shipping_method/, + journal3: /journal3\/checkout/, + }, + buttons: { + standard: '#button-shipping-method', + journal3: '#quick-checkout-button-confirm' + }, +}; + +var $widgetButton = false; + +$(function() { + /** + * Initialization of all required parts. + * Called every time ajax call finishes, several times for both classic and journal checkout + */ + $(document).ajaxSuccess(function(e, xhr, settings) { + var isFetchShippingMethodUrl = false; + for (var cartType in cartsConfig['urls']) { + if (settings.url.match(cartsConfig['urls'][cartType]) !== null) { + isFetchShippingMethodUrl = true; + break; + } + } + + if (! isFetchShippingMethodUrl) { + return; + } + + $('#packeta-envelope, .packeta-shipping-item-envelope').remove(); + + $widgetButton = $('#packeta-first-shipping-item'); + if (!$widgetButton.length) { + return; + } + + zasilkovnaCreateElementsandEvents(); + initializePacketaWidget(); + zasilkovnaLoadSelectedBranch(); + }); + +}); + +/** + * Initialization of required elements and events. + */ +function zasilkovnaCreateElementsandEvents() { + var additionalElementsHtml = '' + + '' + + '' + + ''; + var selectedPointElementHtml = '
' + + '
' + $widgetButton.data('no_branch_selected_text') + '
'; + var selectedPointElement; + var additionalElementsEnvelope; + var shippingElementEnvelope; + + // create envelope element with required additional html elements + additionalElementsEnvelope = document.createElement('div'); + additionalElementsEnvelope.setAttribute('id', 'packeta-envelope'); + additionalElementsEnvelope.innerHTML = additionalElementsHtml; + document.body.appendChild(additionalElementsEnvelope); + + // Adding additional visible element for display information about selected pickup point. + // Search for dummy element of "zasilkovna" shipping item is required because there is no id nor class which can + // be used for identification + shippingElementEnvelope = $widgetButton.parent().parent(); + selectedPointElement = document.createElement('div'); + selectedPointElement.setAttribute('class', 'packeta-shipping-item-envelope'); + selectedPointElement.innerHTML = selectedPointElementHtml; + shippingElementEnvelope.append(selectedPointElement); + + // adding onclick handler for radio buttons with list of shipping methods + $('input[name="shipping_method"]:radio').click(zasilkovnaShipmentMethodOnChange); + // for case it's selected + zasilkovnaShipmentMethodOnChange(); +} + +/** + * Handler for change of shipping type (click on radio button) + */ +function zasilkovnaShipmentMethodOnChange() { + // check if radio button for zasilkovna is selected + var isZasilkovnaSelected = detectPacketeryShippingMethod(); + var selectedBranch = $('#packeta-branch-id').val(); + var isSubmitButtonDisabled = false; + + // disable "Continue" button if zasilkovna is selected but no branch is selected from map widget + isSubmitButtonDisabled = false; + if (isZasilkovnaSelected) { + if (selectedBranch === '') { + isSubmitButtonDisabled = true; + } + } + + getConfirmationButton().attr('disabled', isSubmitButtonDisabled); +} + +function detectPacketeryShippingMethod() { + return $("input[name='shipping_method'][value^='zasilkovna.']:checked").length === 1; +} + +function getConfirmationButton() { + for (var cartType in cartsConfig['buttons']) { + var $element = $(cartsConfig['buttons'][cartType]); + if ($element.length) { + return $element; + } + } + + console.error('No supported confirmation button found.'); + return null; +} + +/** + * Handler for load of selected branch from session. + * It is called after initialization of additional HTML elements and JS events during after switch to "Step 4: Delivery Method" + * during "checkout". + */ +function zasilkovnaLoadSelectedBranch() { + $.ajax({ + url: 'index.php?route=extension/module/zasilkovna/loadSelectedBranch', + type: 'get', + dataType: 'json', + success: function(json) { + if (json.zasilkovna_branch_id !== '') { + $('#packeta-branch-id').val(json.zasilkovna_branch_id); + $('#packeta-branch-name').val(json.zasilkovna_branch_name); + $('#picked-delivery-place').html(json.zasilkovna_branch_description); + $('#packeta-carrier-id').val(json.zasilkovna_carrier_id); + $('#packeta-carrier-pickup-point').val(json.zasilkovna_carrier_pickup_point); + } + zasilkovnaUpdateSubmitButtonStatus(); + }, + error: function(xhr, ajaxOptions, thrownError) { + alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); + } + }); +} + +/** + * Sets status of "Continue" button according to selected shipping properties. + * Button "Continue" is disabled when "zasilkovna" is selected ad shipping method and target branch is not selected. + */ +function zasilkovnaUpdateSubmitButtonStatus() { + var selectedShipment = $('#collapse-shipping-method input[type=\'radio\']:checked'); + var isZasilkovnaSelected = detectPacketeryShippingMethod(); + var selectedBranchId = $('#packeta-branch-id').val(); + + getConfirmationButton().attr('disabled', (isZasilkovnaSelected && selectedBranchId === '')); +} + +/** + * Handler for save selected branch to session. + * It it called after click on "Continue" button in "Step 4: Delivery Method". Another ajax request for save + * selected shipping method and comment is sent at the same time. + */ +function zasilkovnaSaveSelectedBranch() { + var branchId = $('#packeta-branch-id').val(), + dataToSend; + + dataToSend = { + zasilkovna_branch_id: branchId, + zasilkovna_branch_name: $('#packeta-branch-name').val(), + zasilkovna_branch_description: $('#picked-delivery-place').html(), + zasilkovna_carrier_id: $('#packeta-carrier-id').val(), + zasilkovna_carrier_pickup_point: $('#packeta-carrier-pickup-point').val() + }; + + $.ajax({ + url: 'index.php?route=extension/module/zasilkovna/saveSelectedBranch', + type: 'post', + data: dataToSend, + success: function() { + // enable "Continue" button for switch to next step in "checkout workflow" + getConfirmationButton().attr('disabled', false); + // mark carrier "Zasilkovna" as active when pickup point is selected + $widgetButton.parent().parent().find('input[type=radio]').prop('checked', true); + }, + error: function(xhr, ajaxOptions, thrownError) { + alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); + } + }); +} + +/** + * helper function for library to choose delivery point using map widget + * defined as function because it must be called when all required elements are created in DOM + */ +function initializePacketaWidget() { + // list of configuration properties for widget + var apiKey = $widgetButton.data('api_key'); + + // preparation of parameters for widget + var widgetOptions = { + appIdentity: $widgetButton.data('app_identity'), + country: $widgetButton.data('enabled_countries'), + language: $widgetButton.data('language') + }; + + document.getElementById('open-packeta-widget').addEventListener('click', function (e) { + e.preventDefault(); + // displaying of map widget + Packeta.Widget.pick(apiKey, selectPickUpPointCallback, widgetOptions); + }); + +} + +/** + * Callback function for processing of pickup point selection using map widget. + * It is called by widget with object "ExtendedPoint" as parameter. + * + * @param targetPoint detail information about selected point + * @return void + */ +function selectPickUpPointCallback(targetPoint) { + if (null == targetPoint) { // selection of pickup point was cancelled + return; + } + + // save ID and name of selected point point to hidden input elements + document.getElementById('packeta-branch-id').value = targetPoint.pickupPointType === 'external' ? targetPoint.carrierId : targetPoint.id; + document.getElementById('packeta-branch-name').value = targetPoint.nameStreet; + document.getElementById('packeta-carrier-id').value = targetPoint.carrierId ? targetPoint.carrierId : ''; + document.getElementById('packeta-carrier-pickup-point').value = targetPoint.carrierPickupPointId ? targetPoint.carrierPickupPointId : ''; + + // show name of selected pickup point to user + document.getElementById('picked-delivery-place').innerHTML = targetPoint.nameStreet; + + // Save selected branch to session. It must be done now because it it not possible to send two ajax requests after click + // on "Continue" button. There is conflict if two php script wants to save to session. Session data saved by first script + // can be overwritten by second script. + // Button "Continue" is enabled when request is finished to avoid conflict described above. + zasilkovnaSaveSelectedBranch(); +} diff --git a/catalog/view/stylesheet/zasilkovna/zasilkovna.css b/upload/catalog/view/theme/zasilkovna/zasilkovna.css similarity index 93% rename from catalog/view/stylesheet/zasilkovna/zasilkovna.css rename to upload/catalog/view/theme/zasilkovna/zasilkovna.css index c48b002..cee7ff1 100644 --- a/catalog/view/stylesheet/zasilkovna/zasilkovna.css +++ b/upload/catalog/view/theme/zasilkovna/zasilkovna.css @@ -1,7 +1,7 @@ -.packeta-shipping-item-envelope { - padding-left: 20px; -} - -#picked-delivery-place { - padding-top: 5px; -} \ No newline at end of file +.packeta-shipping-item-envelope { + padding-left: 20px; +} + +#picked-delivery-place { + padding-top: 5px; +} diff --git a/catalog/view/image/zasilkovna.jpg b/upload/catalog/view/theme/zasilkovna/zasilkovna.jpg similarity index 100% rename from catalog/view/image/zasilkovna.jpg rename to upload/catalog/view/theme/zasilkovna/zasilkovna.jpg