Overview

Packages

  • Eabi
    • Dpd
  • None
  • PHP

Classes

  • dpdcodpayment
  • eabi_dpd_courier
  • Eabi_dpd_courierCourierModuleFrontController
  • eabi_dpd_parcelstore
  • eabi_dpd_parcelstore_data_send_executor
  • eabi_dpd_parcelstore_dpd_api
  • eabi_dpd_parcelstore_dpd_helper
  • eabi_dpd_parcelstore_html_helper
  • eabi_dpd_parcelstore_validator_helper
  • Eabi_dpd_parcelstoreCourierModuleFrontController
  • Eabi_Postoffice
  • eabi_postoffice_dialcode_helper
  • Eabi_PostofficePostofficeModuleFrontController

Functions

  • upgrade_module_0_3
  • upgrade_module_0_6
  • upgrade_module_0_8
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: 
   3: /*
   4:   
   5:  *
   6:  * NOTICE OF LICENSE
   7:  *
   8:  * This source file is subject to the Open Software License (OSL 3.0)
   9:  * or OpenGPL v3 license (GNU Public License V3.0)
  10:  * that is bundled with this package in the file LICENSE.txt.
  11:  * It is also available through the world-wide-web at this URL:
  12:  * http://opensource.org/licenses/osl-3.0.php
  13:  * or
  14:  * http://www.gnu.org/licenses/gpl-3.0.txt
  15:  * If you did not receive a copy of the license and are unable to
  16:  * obtain it through the world-wide-web, please send an email
  17:  * to info@e-abi.ee so we can send you a copy immediately.
  18:  *
  19:  * DISCLAIMER
  20:  *
  21:  * Do not edit or add to this file if you wish to upgrade this module to newer
  22:  * versions in the future.
  23:  *
  24:  * @category   Eabi
  25:  * @package    Eabi_Dpd
  26:  * @copyright  Copyright (c) 2014 Aktsiamaailm LLC (http://en.e-abi.ee/)
  27:  * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
  28:  * @license    http://www.gnu.org/licenses/gpl-3.0.txt  GNU Public License V3.0
  29:  * @author     Matis Halmann
  30:  * 
  31: 
  32:  */
  33: if (!defined('_PS_VERSION_')) {
  34:     exit;
  35: }
  36: 
  37: /**
  38:  * <p>Represents DPD parcel terminal shipping method.</p>
  39:  * <p>Extra order data is stored under specialized order comment</p>
  40:  * <p>Can perform following business actions:</p>
  41:  * <ul>
  42:   <li>Calculate shipping price based on country and weight</li>
  43:   <li>Display list of user selectable parcel terminals, which is auto updated.</li>
  44:   <li>Send information about shipment data to DPD server.</li>
  45:   <li>Call courier to pick up the shipment that was ordered using this carrier.</li>
  46:   </ul>
  47:  * @author Matis
  48:  */
  49: class eabi_dpd_parcelstore extends Module {
  50: 
  51:     const CONST_PREFIX = 'E_DPDEEP_';
  52: 
  53:     /**
  54:      * <p>Copy of <code>CONST_PREFIX</code> for the instance</p>
  55:      * @var string
  56:      */
  57:     protected $_const_prefix;
  58: 
  59:     /**
  60:      * <p>@ will be replaced with actual tracking number.</p>
  61:      */
  62:     const TRACKING_URL = 'https://tracking.dpd.de/cgi-bin/delistrack?typ=1&lang=en&pknr=@';
  63: 
  64:     /**
  65:      * If order comment starts with prefix marked here and is not visible on the frontend, then it is considered as extra data order comment.
  66:      */
  67:     const ORDER_COMMENT_START_PREFIX = '-----EABI_DPDEE-----';
  68: 
  69:     /**
  70:      * Shipping method code
  71:      */
  72:     const NAME = 'eabi_dpd_parcelstore';
  73: 
  74:     /**
  75:      * <p>Holds generated form fields data</p>
  76:      * @var array
  77:      * @see eabi_dpd_parcelstore_html_helper
  78:      */
  79:     private $form_fields = array();
  80: 
  81:     /**
  82:      * <p>For making sure that subclass would not call the footer itself again.</p>
  83:      * @var bool
  84:      */
  85:     protected static $_footerCalled = false;
  86: 
  87:     /**
  88:      * <p>To prevent displaying carrier extra code more than once.</p>
  89:      * @var bool
  90:      */
  91:     protected $_carrierDisplayed = false;
  92: 
  93:     /**
  94:      * <p>Makes sure only one instance is queried</p>
  95:      * @var Eabi_Postoffice
  96:      */
  97:     protected static $_helperModuleInstance;
  98: 
  99:     /**
 100:      * <p>Handles functions related to automated data sending and packing slip sending</p>
 101:      * @var eabi_dpd_parcelstore_data_send_executor
 102:      */
 103:     public $dataSendExecutor;
 104: 
 105:     /**
 106:      * <p>Default constructor</p>
 107:      */
 108:     final public function __construct() {
 109: 
 110:         $this->_construct();
 111: 
 112: 
 113:         parent::__construct();
 114:         $this->init();
 115:         
 116:     }
 117: 
 118:     /**
 119:      * Any setup parameters can be set up here.
 120:      */
 121:     protected function _construct() {
 122:         $this->_const_prefix = self::CONST_PREFIX;
 123:         $this->name = self::NAME;
 124:         $this->tab = 'shipping_logistics';
 125:         $this->version = '0.9';
 126:         $this->dependencies[] = 'eabi_postoffice';
 127:         $this->ps_versions_compliancy = array('min' => '1.5', 'max' => '1.7');
 128:         $this->displayName = $this->l('DPD Pakipoodi');
 129:         $this->description = $this->l('DPD offers high-quality shipping service from Estonia to whole Europe.');
 130:         $this->confirmUninstall = $this->l('Are you sure you want to delete your details?');
 131:         if (file_exists(_PS_MODULE_DIR_ . $this->name . '/datasend-executor.php')) {
 132:             require_once(_PS_MODULE_DIR_ . $this->name . '/datasend-executor.php');
 133:             $executorClass = $this->name . '_data_send_executor';
 134:             $this->dataSendExecutor = new $executorClass($this);
 135:         }
 136:         if (!$this->_getHelperModule() || !$this->getConfigData('TITLE') OR !$this->getConfigData('HANDLING_FEE')) {
 137:             $this->warning = $this->l('Details must be configured in order to use this module correctly');
 138:         }
 139:         
 140:         
 141:     }
 142: 
 143:     /**
 144:      * <p>Not used</p>
 145:      */
 146:     private function init() {
 147:         
 148:     }
 149: 
 150:     /**
 151:      * <p>Performs install function for this module</p>
 152:      * @return boolean
 153:      */
 154:     final public function install() {
 155:         if (Shop::isFeatureActive()) {
 156:             Shop::setContext(Shop::CONTEXT_ALL);
 157:         }
 158:         if (!parent::install() or !$this->_install()) {
 159:             return false;
 160:         }
 161:         return true;
 162:     }
 163: 
 164:     /**
 165:      * <p>Performs uninstall function for this module</p>
 166:      * @return boolean
 167:      */
 168:     final public function uninstall() {
 169:         if (Shop::isFeatureActive()) {
 170:             Shop::setContext(Shop::CONTEXT_ALL);
 171:         }
 172:         if (!$this->_uninstall() || !parent::uninstall()) {
 173:             return false;
 174:         }
 175:         return true;
 176:     }
 177: 
 178:     /**
 179:      * <p>Performs following actions:</p>
 180:      * <ul>
 181:       <li>Registers hook <code>extraCarrier</code></li>
 182:       <li>Registers carrier with name <code>eabi_dpdee_parcelstore</code></li>
 183:       <li>Registers hook with name <code>paymentConfirm</code> - for auto sending data after payment</li>
 184:       <li>Registers hook with name <code>actionAdminControllerSetMedia</code> - for adding css,js scripts</li>
 185:       <li>Registers hook with name <code>displayBackOfficeFooter</code> - for adding call to courier button</li>
 186:       <li>Registers hook with name <code>displayHeader</code> - for adding css,js scripts</li>
 187:       <li>Registers hook with name <code>displayFooter</code> - for removing shipping methods at onepage checkouts, if <code>HOOK_EXTRACARRIER</code> is not called</li>
 188:       </ul>
 189:      * @return boolean
 190:      */
 191:     protected function _install() {
 192:         if (!$this->registerHook('extraCarrier') 
 193:                 || !$this->_getHelperModule()->addCarrierModule($this->name, get_class($this), self::TRACKING_URL) 
 194:                 || !$this->registerHook('paymentConfirm') 
 195:                 || !$this->registerHook('actionAdminControllerSetMedia') 
 196:                 || !$this->registerHook('displayBackOfficeFooter') 
 197:                 || !$this->registerHook('displayHeader') 
 198:                 || !$this->registerHook('displayFooter')
 199:                 || !$this->upgrade_module_0_6()
 200:                 || !$this->upgrade_module_0_8()) {
 201:             return false;
 202:         }
 203:         return true;
 204:     }
 205: 
 206:     /**
 207:      * <p>Performs following actions:</p>
 208:      * <ul>
 209:       <li>Unregisters hook <code>extraCarrier</code></li>
 210:       <li>Removes carrier with name <code>eabi_dpdee_parcelstore</code></li>
 211:       <li>Unregisters hook with name <code>paymentConfirm</code></li>
 212:       <li>Unregisters hook with name <code>actionAdminControllerSetMedia</code></li>
 213:       <li>Unregisters hook with name <code>displayBackOfficeFooter</code></li>
 214:       <li>Unregisters hook with name <code>displayHeader</code></li>
 215:       <li>Unregisters hook with name <code>displayFooter</code></li>
 216:       <li>If <code>dataSendExecutor</code> is available, then <code>uninstall()</code> method will be called on same object</li>
 217:       </ul>
 218:      * @return boolean
 219:      */
 220:     protected function _uninstall() {
 221:         if (!$this->unregisterHook('extraCarrier') 
 222:                 || !$this->unregisterHook('paymentConfirm') 
 223:                 || !$this->unregisterHook('displayBackOfficeFooter') 
 224:                 || !$this->unregisterHook('displayHeader') 
 225:                 || !$this->unregisterHook('displayFooter')) {
 226:             return false;
 227:         }
 228:         //TODO: remove in future releases, now it is left because there was hook rename from:
 229:         //displayBackOfficeHeader => actionAdminControllerSetMedia
 230:         $this->unregisterHook('displayBackOfficeHeader');
 231:         $this->unregisterHook('actionAdminControllerSetMedia');
 232:         if (!$this->_getHelperModule()->removeCarrierModule($this->name)) {
 233:             return false;
 234:         }
 235: 
 236:         if ($this->dataSendExecutor != null) {
 237:             $this->dataSendExecutor->uninstall();
 238:         }
 239:         return true;
 240:     }
 241: 
 242:     /**
 243:      * <p>Prepares op=order request to be sent via DPD API.</p>
 244:      * @param OrderCore $order Order that is using this carrier
 245:      * @param AddressCore $address address, that is from that order (one order can support multiple addresses)
 246:      * @param array $selectedOfficeId selected entry from table <code>eabi_postoffice</code>
 247:      * @param bool $forceCod true if request should be sent as COD request
 248:      * @return array request that is passed directly to DPD
 249:      */
 250:     public function getRequestForAutoSendData($order, $address, $selectedOfficeId, $forceCod = false) {
 251:         $telephone = $address->phone_mobile;
 252:         if (!$telephone) {
 253:             $telephone = $address->phone;
 254:         }
 255:         
 256:         //address is selected office address
 257: 
 258:         $requestData = array(
 259:             'Sh_name' => $address->firstname.' '.$address->lastname,
 260:             'Sh_company' => '',
 261:             'Sh_street' => $this->_getStreetFromDescription($selectedOfficeId),
 262:             'Sh_postal' => $selectedOfficeId['zip_code'],
 263:             'Sh_country' => strtolower($selectedOfficeId['country']),
 264:             'Sh_city' => $selectedOfficeId['city'],
 265:             'Sh_contact' => $selectedOfficeId['name'],
 266:             'Sh_phone' => $this->_getPhoneFromDescription($selectedOfficeId),
 267:             'Po_remark' => $this->_getRemark($order),
 268:             'Sh_remark' => '',
 269:             'Sh_pudo' => 'true',
 270:             'Sh_pudo_id' => $selectedOfficeId['remote_place_id'],
 271:             'Sh_parcel_qty' => $this->_getNumberOfPackagesForOrder($order),
 272:             'Sh_cust_reference' => $order->id,
 273:         );
 274:         if ($address->id_state) {
 275:             $requestData['Sh_city'] = $address->city . ', ' . State::getNameById($address->id_state);
 276:         }
 277: 
 278: 
 279:         $phoneNumbers = $this->_getDialCodeHelper()->separatePhoneNumberFromCountryCode($telephone, Country::getIsoById($address->id_country));
 280:         $requestData['Sh_notify_phone_code'] = $phoneNumbers['dial_code'];
 281:         $requestData['Sh_notify_contact_phone'] = $phoneNumbers['phone_number'];
 282:         
 283:         //add cash on delivery
 284:         if (!$order->hasBeenPaid() && $forceCod) {
 285:             //we need forceCod parameter, because built in bankwire payment on payment confirmation causes order.hasBeenPaid to return false
 286:             //thus causing module to send COD request for an order which has actually been paid
 287:             $requestData['Sh_cod_amount'] = number_format(round($order->total_paid_tax_incl, 2), 2, '.', '');
 288:             $requestData['Sh_cod_currency'] = $this->_getCurrencyIso($order->id_currency);
 289:         }
 290:         
 291:         
 292:         return $requestData;
 293:     }
 294:     
 295:     
 296:     protected function _getCurrencyIso($id_currency) {
 297:         $currency = Currency::getCurrency($id_currency);
 298:         return $currency['iso_code'];
 299:     }
 300:     
 301:     
 302:     private function _getStreetFromDescription($selectedOffice) {
 303:         $zip = $selectedOffice['zip_code'];
 304:         $encoding = 'UTF-8';
 305:         return trim(mb_substr($selectedOffice['description'], 0, mb_strpos($selectedOffice['description'], $zip, 0, $encoding), $encoding));
 306:     }
 307:     private function _getPhoneFromDescription($selectedOffice) {
 308:         $zip = $selectedOffice['zip_code'];
 309:         $country = $selectedOffice['country'];
 310:         $matches = array();
 311:         $isMatched = preg_match('/(?s:[\+][0-9]+)/', $selectedOffice['description'], $matches);
 312:         if ($isMatched) {
 313:             return $matches[0];
 314:         }
 315:         return '';
 316:     }
 317: 
 318:     /**
 319:      * <p>Automatic data sending is executed at the moment, when order is marked as Paid.</p>
 320:      * @param array $params
 321:      * @return string
 322:      */
 323:     public function hookPaymentConfirm(&$params) {
 324:         if ($this->dataSendExecutor != null) {
 325:             $this->dataSendExecutor->hookpaymentConfirm($params);
 326:         }
 327:         return '';
 328:     }
 329: 
 330:     /**
 331:      * <p>For adding CSS,JS scripts in backoffice</p>
 332:      */
 333:     public function hookActionAdminControllerSetMedia() {
 334:         $this->context->controller->addCSS($this->_path . 'css/' . self::NAME . '.css', 'all');
 335:         $this->context->controller->addJqueryPlugin('loadTemplate', $this->_path . 'js/plugins/');
 336:         $this->context->controller->addJS($this->_path . 'js/' . self::NAME . '.js');
 337:     }
 338: 
 339:     /**
 340:      * Module that adds the button to call the courier.
 341:      */
 342:     public function hookDisplayBackOfficeFooter() {
 343:         $className = 'AdminOrdersController';
 344: 
 345:         //detect order id, and if detected, then add the button.....
 346: 
 347: 
 348:         if (!self::$_footerCalled && $this->context->controller instanceof $className && $this->getConfigData('SENDDATA_ENABLE') == 'yes') {
 349:             self::$_footerCalled = true;
 350: 
 351:             $targetName = eabi_dpd_parcelstore::NAME;
 352:             if (!ModuleCore::isEnabled(eabi_dpd_parcelstore::NAME)) {
 353:                 $targetName = 'eabi_dpd_courier';
 354:             }
 355:         $buttonCssClass = 'process-icon-new';
 356:         if (substr(_PS_VERSION_, 0, 3) == "1.6") {
 357:             $buttonCssClass = 'process-icon-dpd';
 358:         }
 359:             
 360:             if (!(Tools::getValue('vieworder', false) === '')) {
 361:                 //we are in list
 362:                 if ($this->getConfigData('COURIER_ENABLE') == 'yes') {
 363:                     $jsonParams = array(
 364:                         'url' => $this->context->link->getModuleLink($targetName, 'courier', array('fc' => 'module')),
 365:                         'button' => '<li id="eabi-dpdee-courier"><a href="#" ><span class="'.$buttonCssClass.'"></span><div></div></a></li>',
 366:                         'a_class' => 'toolbar_btn eabi_dpdee_call_courier_button',
 367:                         'title' => $this->l('Order in DPD Courier'),
 368:                     );
 369:                 }
 370:             } else {
 371:                 //we have a order
 372:                 $order = new Order(Tools::getValue('id_order'));
 373:                 if ($pdf = $this->dataSendExecutor->getBarcode($order)) {
 374:                     $jsonParams = array(
 375:                         'url' => '',
 376:                         'button' => '<li id="eabi-dpdee-parcel-pdf"><a href="#" ><span class="'.$buttonCssClass.'"></span><div></div></a></li>',
 377:                         'a_class' => 'toolbar_btn eabi_dpdee_print_slip_button',
 378:                         'title' => $this->l('Print packing slip'),
 379:                         'redirect_click' => urldecode($pdf),
 380:                     );
 381:                 } else {
 382:                     return '';
 383:                 }
 384:             }
 385: 
 386:             return $this->_getFooterJs($jsonParams);
 387:         }
 388:     }
 389: 
 390:     protected function _getFooterJs($jsonParams) {
 391:         $js = '';
 392:         $jQuerySelector = 'div.toolbarHead ul.cc_button';
 393:         if (substr(_PS_VERSION_, 0, 3) == "1.6") {
 394:             $jQuerySelector = 'ul#toolbar-nav';
 395:         }
 396:         
 397:         $js .= <<<JS
 398: <script type="text/javascript">
 399: //                    <![CDATA[
 400:                     jQuery(document).ready(function() {
 401:                         jQuery({$this->_toJson($jQuerySelector)}).eabi_dpdee_courierbutton(
 402:                             {$this->_toJson($jsonParams)});
 403:                         });
 404: //                    ]]>
 405: </script>
 406: JS;
 407:         return $js;
 408:     }
 409: 
 410:     protected function _toJson($input) {
 411:         return json_encode($input);
 412:     }
 413: 
 414:     /**
 415:      * <p>For adding CSS,JS scripts in frontend</p>
 416:      */
 417:     public function hookDisplayHeader() {
 418:         
 419:         $this->context->controller->addCSS($this->_path . 'css/' . self::NAME . '-public.css', 'all');
 420:         
 421:         if (substr(_PS_VERSION_, 0, 3) == "1.5") {
 422:             $this->context->controller->addCSS($this->_path . 'css/' . self::NAME . '-public_1.5.css', 'all');
 423:         }
 424:         $this->context->controller->addJS($this->_path . 'js/' . self::NAME . '-public.js');
 425:         
 426:     }
 427: 
 428:     /**
 429:      * <p>For adding HOOK_EXTRACARRIER callout, when original callout did not occur when it had to.</p>
 430:      * <p>PrestaShop does not call extracarrier when for example address is not entered yet</p>
 431:      * @return string
 432:      */
 433:     public function hookDisplayFooter() {
 434:         $className = 'OrderOpcController';
 435:         $php_self = 'order-opc';
 436:         if ($this->context->controller instanceof $className && $this->context->controller->php_self == $php_self) {
 437:             if (!$this->_carrierDisplayed) {
 438:                 return $this->hookExtraCarrier(array('cart' => $this->context->cart));
 439:             }
 440:         }
 441:     }
 442: 
 443:     /**
 444:      * <p>Validates Admin Configuration Form and returns array of error message on validation failure.</p>
 445:      * @return array error messages as array, if any
 446:      */
 447:     protected function _postValidation() {
 448:         $errors = array();
 449:         if (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') {
 450:             //on no post mode do not validate
 451:             return $errors;
 452:         }
 453:         foreach ($this->_initFormFields() as $formFieldName => $formFieldData) {
 454:             $validationRules = isset($formFieldData['validate']) ? $formFieldData['validate'] : array();
 455:             $validationIfRules = isset($formFieldData['validate-if']) ? $formFieldData['validate-if'] : array();
 456:             //select,multiselect validation
 457:             $selectValidationResult = $this->_getValidator()->validate('validate_select', strtoupper($formFieldName), $_POST, $validationIfRules, $formFieldData);
 458:             if (is_string($selectValidationResult)) {
 459:                 //we have validation error
 460:                 $errors[] = sprintf($selectValidationResult, $formFieldData['title']);
 461:                 break;
 462:             }
 463: 
 464:             foreach ($validationRules as $validationRule) {
 465:                 $validationResult = $this->_getValidator()->validate($validationRule, strtoupper($formFieldName), $_POST, $validationIfRules, $formFieldData);
 466:                 if (is_string($validationResult)) {
 467:                     //we have validation error
 468:                     $errors[] = sprintf($this->l($validationResult), $formFieldData['title']);
 469:                     break;
 470:                 }
 471:             }
 472:         }
 473: 
 474: 
 475:         return $errors;
 476:     }
 477: 
 478:     /**
 479:      * <p>Saves configuration entered from the admin form and returns HTML generated during process.</p>
 480:      * @return string any generated HTML from the postprocess, for example success message
 481:      */
 482:     private function _postProcess() {
 483:         $html = '';
 484:         $data = $_POST;
 485:         if (isset($data['btnSubmit'])) {
 486:             foreach ($this->_initFormFields() as $formFieldName => $formFieldDataSet) {
 487:                 //multiselect must be normalized to CSV string
 488:                 if ($formFieldDataSet['type'] == 'multiselect') {
 489:                     if (!isset($data[strtoupper($formFieldName)])) {
 490:                         $data[strtoupper($formFieldName)] = '';
 491:                     }
 492:                     if (is_array($data[strtoupper($formFieldName)])) {
 493:                         $data[strtoupper($formFieldName)] = implode(',', $data[strtoupper($formFieldName)]);
 494:                     }
 495:                 }
 496: 
 497:                 //country_select_html is always submitted as array
 498:                 if (is_array($data[strtoupper($formFieldName)])) {
 499:                     $data[strtoupper($formFieldName)] = serialize($data[strtoupper($formFieldName)]);
 500:                 }
 501:                 Configuration::updateValue(self::CONST_PREFIX . strtoupper($formFieldName), $data[strtoupper($formFieldName)]);
 502:                 
 503:             }
 504:             $this->_getHelperModule()->setTaxGroup($this->name, $this->getConfigData('TAX'));
 505:             $title = $this->getConfigData('TITLE');
 506:             $finalTitle = $title;
 507:             if ($this->_isSerialized($title)) {
 508:                 $title = @unserialize($title);
 509:                 if (is_array($title)) {
 510:                     $finalTitle = isset($title[$this->context->language->id]) ? $title[$this->context->language->id] : $title[0];
 511:                 }
 512:             }
 513:             $this->_getHelperModule()->setDisplayName($this->name, $finalTitle);
 514:             $this->_getHelperModule()->refresh($this->name, true);
 515: 
 516: 
 517:             $html .= '<div class="conf confirm"><img src="../img/admin/ok.gif" alt="' . $this->l('ok') . '" /> ' . $this->l('Settings updated') . '</div>';
 518:         }
 519:         return $html;
 520:     }
 521: 
 522:     /**
 523:      * <p>Displayes admin configuration form header</p>
 524:      * @return string html
 525:      */
 526:     private function _displayFormHeader() {
 527:         $html = '<img src="../modules/' . $this->name . '/' . $this->name . '.png" style="float:left; margin-right:15px;"><b>' . $this->l('DPD offers high-quality shipping service from Estonia to whole Europe.') . '</b><br /><br />
 528:         ' . $this->l('DPD ParcelShop service can be used in Estonia, Latvia, and Lithuania by selecting preferred DPD ParcelShop from select menu.') . '<br />';
 529:         return $html;
 530:     }
 531: 
 532:     /**
 533:      * <p>This hook is called right after PrestaShop own carriers are rendered.</p>
 534:      * <p>Returns HTML, which:</p>
 535:      * <ul>
 536:       <li>Replaces PrestaShop carrier element with ajax refreshing select menu element</li>
 537:       <li>Hides PrestaShop carrier, when this carrier should not be available</li>
 538:       </ul>
 539:      * <p>Current PrestaShops <code>extraCarrier</code> hook uses following parameters:</p>
 540:      * <ul>
 541:       <li><code>cart</code> - Cart instance for current customer or order</li>
 542:       <li><code>address</code> - Address instance for current cart</li>
 543:       </ul>
 544:      * <p>Checks following and hides if:</p>
 545:      * <ul>
 546:       <li>Shipping is not allowed for the specified country</li>
 547:       <li>Products in the cart contain specific HTML comment string</li>
 548:       <li>Any of the products is overweight</li>
 549:       </ul>
 550:      * @param array $params
 551:      * @return string
 552:      */
 553:     public function hookExtraCarrier($params) {
 554:         //if this shipping method is available or not
 555:         $shouldHide = false;
 556:         $this->_carrierDisplayed = true;
 557: 
 558:         /* @var $cart CartCore */
 559:         $cart = $params['cart'];
 560:         $summaryDetails = $cart->getSummaryDetails();
 561: 
 562:         //check if this shipping method is in allowed country list
 563:         if ($this->getConfigData('SALLOWSPECIFIC') == '1') {
 564:             $allowedCountries = explode(',', $this->getConfigData('SPECIFICCOUNTRY'));
 565:             if (!in_array(Country::getIsoById($summaryDetails['delivery']->id_country), $allowedCountries)) {
 566:                 $shouldHide = true;
 567:             }
 568:         }
 569: 
 570:         //check if address exists and if not, then create dummy address
 571:         if (!$summaryDetails['delivery']->country) {
 572:             $shouldHide = true;
 573:             if (!isset($params['address']) || !$params['address']) {
 574:                 $params['address'] = (object) array(
 575:                             'id' => '0',
 576:                 );
 577:             }
 578:         }
 579: 
 580:         //check if cart contains any of the products which contain forbidden html comment
 581:         if ($this->getConfigData('CHECKITEMS') == 'yes' && !$shouldHide) {
 582:             $prods = $cart->getProducts();
 583:             foreach ($prods as $prod) {
 584:                 if (stripos($prod['description_short'], '<!-- no dpd_ee_module -->') !== false) {
 585:                     $shouldHide = true;
 586:                     break;
 587:                 }
 588:             }
 589:         }
 590: 
 591:         //weight check
 592:         $loadedProductWeights = array();
 593:         if (($this->getConfigData('MAX_PACKAGE_WEIGHT') > 0 || $this->getConfigData('MIN_PACKAGE_WEIGHT') > 0) && !$shouldHide) {
 594:             $products = $cart->getProducts();
 595:             foreach ($products as $product) {
 596:                 if ($product['is_virtual']) {
 597:                     continue;
 598:                 }
 599:                 for ($i = 0; $i < $product['cart_quantity']; $i++) {
 600:                     $loadedProductWeights[] = $product['weight'];
 601:                 }
 602:                 if ($this->getConfigData('MAX_PACKAGE_WEIGHT') > 0 && !$shouldHide) {
 603:                     if (max($loadedProductWeights) > (float) $this->getConfigData('MAX_PACKAGE_WEIGHT')) {
 604:                         $shouldHide = true;
 605:                     }
 606:                 }
 607:                 if ($this->getConfigData('MIN_PACKAGE_WEIGHT') > 0 && !$shouldHide) {
 608:                     if (min($loadedProductWeights) > (float) $this->getConfigData('MIN_PACKAGE_WEIGHT')) {
 609:                         $shouldHide = true;
 610:                     }
 611:                 }
 612:             }
 613:         }
 614:         $title = $this->getConfigData('TITLE');
 615:         $finalTitle = $title;
 616:         if ($this->_isSerialized($title)) {
 617:             $title = @unserialize($title);
 618:             if (is_array($title)) {
 619:                 $finalTitle = isset($title[$this->context->language->id]) ? $title[$this->context->language->id] : $title[0];
 620:             }
 621:         }
 622: 
 623:         $extraParams = array(
 624:             'id_address_delivery' => $cart->id_address_delivery,
 625:             'price' => $this->getOrderShippingCost($cart),
 626:             'title' => $finalTitle,
 627:             'logo' => __PS_BASE_URI__ . 'modules/' . $this->name . '/logo.gif',
 628:             'id_address_invoice' => $cart->id_address_invoice,
 629:             'error_message' => '', //not required since phone nr is mandatory
 630:             'is_default' => false,
 631:         );
 632: 
 633:         return $this->_getHelperModule()->displayExtraCarrier($this->name, $extraParams, $shouldHide);
 634:     }
 635:     
 636:     protected function _isSerialized($input) {
 637:         return preg_match('/^([adObis]):/', $input);
 638:     }
 639:     
 640: 
 641:     /**
 642:      * <p>PrestaShop implementation for displaing configuration form for this module</p>
 643:      * @return string
 644:      */
 645:     public function getContent() {
 646:         $html = '<h2>' . $this->displayName . '</h2>';
 647: 
 648:         if (!empty($_POST)) {
 649:             $postErrors = $this->_postValidation();
 650:             if (!sizeof($postErrors)) {
 651:                 $html .= $this->_postProcess();
 652:             } else {
 653:                 foreach ($postErrors as $err) {
 654:                     $html .= '<div class="alert error">' . $err . '</div>';
 655:                 }
 656:             }
 657:         } else {
 658:             $html .= '<br />';
 659:         }
 660:         $html .= $this->_displayFormHeader();
 661:         $html .= $this->_displayForm();
 662: 
 663: 
 664:         return $html;
 665:     }
 666: 
 667:     /**
 668:      * <p>Generates actual configuration form by rules</p>
 669:      * @return string
 670:      */
 671:     protected function _displayForm() {
 672:         $html = '';
 673:         $html .= $this->_getFormHtml($_SERVER['REQUEST_URI'], 'post', $this->_initFormFields());
 674:         return $html;
 675:     }
 676: 
 677:     /**
 678:      * <p>Fetches configuration for this instance.</p>
 679:      * @param string $param
 680:      * @return mixed
 681:      */
 682:     public function getConfigData($param) {
 683:         $value = Configuration::get(self::CONST_PREFIX . $param);
 684:         if ($value === null || $value === false) {
 685:             $formFields = $this->_initFormFields();
 686:             if (isset($formFields[strtolower($param)]) && $formFields[strtolower($param)]['default']) {
 687:                 return $formFields[strtolower($param)]['default'];
 688:             }
 689:         }
 690:         return $value;
 691:     }
 692: 
 693:     /**
 694:      * <p>Gets configuration data only for this instance.</p>
 695:      * @param string $param
 696:      * @return mixed
 697:      */
 698:     public function getConfigDataForThis($param) {
 699:         $value = Configuration::get($this->_const_prefix . $param);
 700:         if ($value === null || $value === false) {
 701:             $formFields = $this->_initFormFields();
 702:             if (isset($formFields[strtolower($param)]) && $formFields[strtolower($param)]['default']) {
 703:                 return $formFields[strtolower($param)]['default'];
 704:             }
 705:         }
 706:         return $value;
 707:     }
 708: 
 709:     /**
 710:      * <p>Renders form HTML from the form fields configuration</p>
 711:      * @param string $action action url
 712:      * @param string $method form element method attribute
 713:      * @param array $formFields form fields array
 714:      * @return string resulting html
 715:      */
 716:     protected function _getFormHtml($action, $method, $formFields) {
 717:         $action = Tools::htmlentitiesUTF8($action);
 718:         $html = '';
 719: 
 720:         $formElementsHtml = '';
 721:         $formElementHelper = $this->_getHtmlHelper();
 722:         foreach ($formFields as $fieldName => $fieldData) {
 723:             $methodName = 'get' . ucfirst($fieldData['type']) . 'Html';
 724:             $fieldPropertyName = '_' . $fieldName;
 725:             $value = Tools::getValue(strtoupper($fieldName), $this->getConfigData(strtoupper($fieldName)));
 726:             if (method_exists($formElementHelper, $methodName)) {
 727:                 $formElementsHtml .= $formElementHelper->$methodName(strtoupper($fieldName), $fieldData, $value);
 728:             } else {
 729:                 $formElementsHtml .= $formElementHelper->getTextHtml(strtoupper($fieldName), $fieldData, $value);
 730:             }
 731:         }
 732: 
 733:         $formClass = eabi_dpd_parcelstore::NAME;
 734: 
 735:         $html .= <<<HTML
 736:    <form action="{$action}" method="{$method}">
 737:        <fieldset>
 738:            <legend><img src="../img/admin/contact.gif" alt="" />{$this->l('Configuration details')}</legend>
 739:                <table id="form" class="{$formClass}">
 740:                 <colgroup class="label"></colgroup>
 741:                 <colgroup class="value"></colgroup>
 742:                <tbody>
 743:                {$formElementsHtml}
 744:                    <tr>
 745:                        <td colspan="2"><input class="button" name="btnSubmit" value="{$this->l('Update settings')}" type="submit" /></td>
 746:                    </tr>
 747:                 </tbody>
 748:                </table>
 749:        </fieldset>
 750:    </form>
 751: HTML;
 752:         return $html;
 753:     }
 754: 
 755:     /**
 756:      * @see eabi_dpd_parcelstore::getOrderShippingCost()
 757:      * @param CartCode $cartObject
 758:      * @return float
 759:      */
 760:     public function getOrderShippingCostExternal($cartObject) {
 761:         return $this->getOrderShippingCost($cartObject, 0);
 762:     }
 763: 
 764:     /**
 765:      * <p>Attemps to calculate shipping price from price-country shipping price matrix.</p>
 766:      * <p>If unsuccessful, then default handling fee is returned.</p>
 767:      * <p>If shipping calculation mode is set Per Item, then price will be multiplied by number of packages</p>
 768:      * @param CartCore $cartObject
 769:      * @param float $shippingPrice
 770:      * @return float
 771:      */
 772:     public function getOrderShippingCost($cartObject, $shippingPrice = 0) {
 773:         $codFee = 0;
 774:         if ($cartObject->id_customer > 0) {
 775:             //check the customer group
 776:             $freeGroups = explode(',', $this->getConfigData('FREE_GROUPS'));
 777:             if (count($freeGroups) > 0 && $this->getConfigData('FREE_GROUPS') != '') {
 778:                 $customerGroups = CustomerCore::getGroupsStatic($cartObject->id_customer);
 779:                 foreach ($customerGroups as $customerGroup) {
 780:                     if (in_array($customerGroup, $freeGroups)) {
 781:                         //free shipping if customer belongs to group
 782:                         return 0;
 783:                     }
 784:                 }
 785:             }
 786:         }
 787:         if (isset($cartObject->codFee) && $cartObject->codFee) {
 788:             $codFee = $cartObject->codFee;
 789: 
 790:             //strip VAT, because we want prices to be VAT inclusive under the configuration screen.
 791:             $idTaxRulesGroup = $this->getConfigDataForThis('TAX');
 792:             if ($idTaxRulesGroup) {
 793:                 $codFee = $codFee / (1 + ($this->_getTaxRateForCart($cartObject) / 100));
 794:             }
 795:         }
 796: 
 797: 
 798: 
 799:         $totalSum = $cartObject->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING);
 800: 
 801:         //check if free shipping by total sum  is enabled
 802:         if ($this->getConfigData('ENABLE_FREE_SHIPPING') == 'yes' && $totalSum >= Tools::convertPrice($this->getConfigData('FREE_SHIPPING_FROM'), Currency::getCurrencyInstance((int) ($cartObject->id_currency)))) {
 803:             return 0 + $codFee;
 804:         }
 805: 
 806:         $shippingMatrix = $this->_decodeShippingMatrix($this->getConfigDataForThis('HANDLING_FEE_COUNTRY'));
 807:         $destinationAddress = new Address($cartObject->id_address_delivery);
 808: 
 809:         if ($destinationAddress->id_country && ($destCountry = Country::getIsoById($destinationAddress->id_country)) && isset($shippingMatrix[$destCountry])) {
 810:             //free price
 811:             if ($shippingMatrix[$destCountry]['free_shipping_from'] !== '') {
 812:                 if ($totalSum >= Tools::convertPrice($shippingMatrix[$destCountry]['free_shipping_from'], Currency::getCurrencyInstance((int) ($cartObject->id_currency)))) {
 813:                     return 0 + $codFee;
 814:                 }
 815:             }
 816: 
 817:             //weight check
 818:             $loadedProductWeights = array();
 819:             if (($this->getConfigData('MAX_PACKAGE_WEIGHT') > 0 || $this->getConfigData('MIN_PACKAGE_WEIGHT') > 0)) {
 820:                 $products = $cartObject->getProducts();
 821:                 foreach ($products as $product) {
 822:                     if ($product['is_virtual']) {
 823:                         continue;
 824:                     }
 825:                     for ($i = 0; $i < $product['cart_quantity']; $i++) {
 826:                         $loadedProductWeights[] = $product['weight'];
 827:                     }
 828:                 }
 829:             }
 830: 
 831: 
 832: 
 833:             //subtraction is required because edges are 0-10,10.00001-20,....,....
 834:             $packageWeight = $cartObject->getTotalWeight() - 0.000001;
 835:             $weightSet = 10;
 836:             //we need to have price per every kg, where
 837:             //0-10kg consists only base price
 838:             //10,1-20kg equals base price + extra price
 839:             //20,1-30kg equals base price + extra price * 2
 840:             $extraWeightCost = max(floor($packageWeight / $weightSet) * $shippingMatrix[$destCountry]['kg_price'], 0);
 841: 
 842:             $handlingFee = $shippingMatrix[$destCountry]['base_price'];
 843:             if ($this->getConfigData('HANDLING_ACTION') == 'P') {
 844:                 $handlingFee = ($this->_getDpdHelper()->getNumberOfPackagesFromItemWeights($loadedProductWeights, $this->getConfigData('MAX_PACKAGE_WEIGHT'))) * $handlingFee;
 845:             }
 846:             $handlingFee += $extraWeightCost;
 847:             
 848:             //strip VAT, because we want prices to be VAT inclusive under the configuration screen.
 849:             $idTaxRulesGroup = $this->getConfigDataForThis('TAX');
 850:             if ($idTaxRulesGroup) {
 851:                 $handlingFee = $handlingFee / (1 + ($this->_getTaxRateForCart($cartObject)/100));
 852:             }
 853: 
 854: 
 855:             return Tools::convertPrice($handlingFee + $codFee, Currency::getCurrencyInstance((int) ($cartObject->
 856:                                     id_currency)));
 857:         }
 858: 
 859:         $handlingFee = (float) str_replace(',', '.', $this->getConfigData('HANDLING_FEE'));
 860:         if ($this->getConfigData('HANDLING_ACTION') == 'P') {
 861:             $price = ($this->_getDpdHelper()->getNumberOfPackagesFromItemWeights($loadedProductWeights, $this->getConfigData('MAX_PACKAGE_WEIGHT'))) * $handlingFee;
 862:         } else {
 863:             $price = $handlingFee;
 864:         }
 865:         //strip VAT, because we want prices to be VAT inclusive under the configuration screen.
 866:         $idTaxRulesGroup = $this->getConfigDataForThis('TAX');
 867:         if ($idTaxRulesGroup) {
 868:             $price = $price / (1 + ($this->_getTaxRateForCart($cartObject)/100));
 869:         }
 870: 
 871: 
 872:         return Tools::convertPrice($price + $codFee, Currency::getCurrencyInstance((int) ($cartObject->
 873:                                 id_currency)));
 874:     }
 875:     
 876:     
 877:     
 878:     /**
 879:      * <p>Returns cash on delivery fee based on address</p>
 880:      * <p>Does not check if Cash on delivery is available</p>
 881:      * <p>Returns false if country is unspecified.</p>
 882:      * @param AddressCore $address
 883:      * @return boolean|float
 884:      */
 885:     public function getCodFee($address) {
 886:         if ($address->id_country) {
 887:             $shippingMatrix = $this->_decodeShippingMatrix($this->getConfigDataForThis('HANDLING_FEE_COUNTRY'));
 888:             if ($address->id_country && ($destCountry = Country::getIsoById($address->id_country)) && isset($shippingMatrix[$destCountry])) {
 889:                 //free price?
 890:                 if (isset($shippingMatrix[$destCountry]['cod_fee']) && $shippingMatrix[$destCountry]['cod_fee'] !== '') {
 891:                     $codFee = (float) str_replace(',', '.', $shippingMatrix[$destCountry]['cod_fee']);
 892:                     if ($codFee >= 0) {
 893: 
 894: 
 895:                         return $codFee;
 896:                     }
 897:                 }
 898:             }
 899:         }
 900: 
 901:         return false;
 902:     }
 903: 
 904:     /**
 905:      * <p>Returns true if cash on delivery is allowed for specified address</p>
 906:      * <p>Checks if COD is allowed by configuration variable <code>payment/dpdcodpayment/active</code> and COD fee is determined in shipping price matrix</p>
 907:      * @param AddressCore $address
 908:      * @return boolean
 909:      */
 910:     public function isCodEnabled($address) {
 911:         if ($address->id_country) {
 912:             $shippingMatrix = $this->_decodeShippingMatrix($this->getConfigDataForThis('HANDLING_FEE_COUNTRY'));
 913:             if ($address->id_country && ($destCountry = Country::getIsoById($address->id_country)) && isset($shippingMatrix[$destCountry])) {
 914:                 //free price?
 915:                 if (isset($shippingMatrix[$destCountry]['cod_fee']) && $shippingMatrix[$destCountry]['cod_fee'] !== '') {
 916:                     $codFee = (float) str_replace(',', '.', $shippingMatrix[$destCountry]['cod_fee']);
 917:                     if ($codFee >= 0) {
 918:                         return true;
 919:                     }
 920:                 }
 921:             }
 922:         }
 923: 
 924:         return true;
 925:     }
 926:     
 927: 
 928:     /**
 929:      * 
 930:      * @param CartCore $cart
 931:      */
 932:     protected function _getTaxRateForCart($cart) {
 933: 
 934:         $complete_product_list = $cart->getProducts();
 935:         $products = $complete_product_list;
 936: 
 937:         if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice') {
 938:             $address_id = (int) $cart->id_address_invoice;
 939:         } elseif (count($products)) {
 940:             $prod = current($products);
 941:             $address_id = (int) $prod['id_address_delivery'];
 942:         } else {
 943:             $address_id = null;
 944:         }
 945:         if (!Address::addressExists($address_id)) {
 946:             $address_id = null;
 947:         }
 948: 
 949:         $carrier = $this->_getHelperModule()->getCarrierFromCode($this->name);
 950:         /* @var $carrier CarrierCore */
 951:         $address = Address::initialize((int) $address_id);
 952: 
 953:         return $carrier->getTaxesRate($address);
 954:     }
 955: 
 956:     /**
 957:      * <p>Decodes json encoded string to assoc array (array keys are country ISO codes) and returns in following format:</p>
 958:      * <ul>
 959:       <li><code>country_id</code> - Country ISO code, also array key for this element</li>
 960:       <li><code>base_price</code> - base shipping price up to 10kg</li>
 961:       <li><code>kg_price</code> - additional shipping price for each 10kg</li>
 962:       <li><code>free_shipping_from</code> - when and if to apply free shipping</li>
 963:       </ul>
 964:      * @param string $input
 965:      * @return array
 966:      */
 967:     protected function _decodeShippingMatrix($input) {
 968:         $shippingMatrix = @unserialize($input);
 969:         $result = array();
 970:         if (!is_array($shippingMatrix)) {
 971:             return $result;
 972:         }
 973:         foreach ($shippingMatrix as $countryDefinition) {
 974:             $result[$countryDefinition['country_id']] = $countryDefinition;
 975:         }
 976:         return $result;
 977:     }
 978: 
 979:     /**
 980:      * <p>Creates admin form fields for this module and caches them after creation</p>
 981:      * @see eabi_dpd_parcelstore_html_helper
 982:      * @return array
 983:      */
 984:     protected function _initFormFields() {
 985:         if (count($this->form_fields)) {
 986:             return $this->form_fields;
 987:         }
 988:         $yesno = array(
 989:             'yes' => $this->l('Yes'),
 990:             'no' => $this->l('No'),
 991:         );
 992:         $boxUnits = array(
 993:             'order' => $this->l('Per Order'),
 994:             'item' => $this->l('Per Package'),
 995:         );
 996:         $countryUnits = array(
 997:             '0' => $this->l('All Allowed Countries'),
 998:             '1' => $this->l('Specific Countries'),
 999:         );
1000: 
1001:         $this->form_fields = array(
1002:             'title' => array(
1003:                 'title' => $this->l('Title'),
1004:                 'type' => 'multilang',
1005:                 'description' => $this->l('This controls the title which the user sees during checkout.'),
1006:                 'default' => $this->l('DPD Pakipoodi'),
1007:                 'css' => 'width: 300px;',
1008:             ),
1009:             'handling_fee' => array(
1010:                 'title' => $this->l('Price'),
1011:                 'type' => 'text',
1012:                 'description' => '',
1013:                 'default' => '4.90',
1014:                 'css' => 'width: 300px;',
1015:                 'validate' => array('required_entry', 'validate_number'),
1016:             ),
1017:             'tax' => array(
1018:                 'title' => $this->l('Tax Id'),
1019:                 'type' => 'select',
1020:                 'description' => $this->l('Prices here are tax inclusive'),
1021:                 'default' => '',
1022:                 'css' => 'width: 300px;',
1023:                 'validate' => array(),
1024:                 'options' => $this->_getHelperModule()->getTaxes(),
1025:             ),
1026:             'handling_fee_country' => array(
1027:                 'title' => $this->l('Price per country'),
1028:                 'type' => 'countryprice',
1029:                 'description' => $this->l('If country is not listed here, but this method is available, then general handling fee is applied'),
1030:                 'default' => 'a:3:{s:18:"_1388439524852_852";a:5:{s:10:"country_id";s:2:"EE";s:10:"base_price";s:4:"4.90";s:8:"kg_price";s:1:"0";s:18:"free_shipping_from";s:0:"";s:7:"cod_fee";s:4:"2.50";}s:17:"_1388442604053_53";a:5:{s:10:"country_id";s:2:"LV";s:10:"base_price";s:5:"12.90";s:8:"kg_price";s:1:"0";s:18:"free_shipping_from";s:0:"";s:7:"cod_fee";s:4:"2.50";}s:18:"_1388709088932_932";a:5:{s:10:"country_id";s:2:"LT";s:10:"base_price";s:5:"13.90";s:8:"kg_price";s:1:"0";s:18:"free_shipping_from";s:0:"";s:7:"cod_fee";s:4:"2.50";}}',
1031:                 'css' => 'width: 300px;',
1032:                 'validate' => array('validate_handling_fee_country'),
1033:             ),
1034:             'shortname' => array(
1035:                 'title' => $this->l('Show short office names'),
1036:                 'type' => 'select',
1037:                 'description' => $this->l('Yes: Shows only office name') . '<br/>' . $this->l('No: Shows office name and address'),
1038:                 'default' => 'yes',
1039:                 'css' => 'width: 300px;',
1040:                 'options' => $yesno,
1041:             ),
1042:             'sort_offices' => array(
1043:                 'title' => $this->l('Sort offices by priority'),
1044:                 'type' => 'select',
1045:                 'description' => $this->l('Yes: Offices from bigger cities will be in front')
1046:                 . '<br/>' . $this->l('No: Offices are sorted alphabetically')
1047:                 ,
1048:                 'default' => 'yes',
1049:                 'css' => 'width: 300px;',
1050:                 'options' => $yesno,
1051:             ),
1052:             'checkitems' => array(
1053:                 'title' => sprintf($this->l('Disable this carrier if product\'s short description contains HTML comment %s'), '&lt;!-- no dpd_ee_module --&gt;'),
1054:                 'type' => 'select',
1055:                 'description' => '',
1056:                 'default' => 'no',
1057:                 'css' => 'width: 300px;',
1058:                 'options' => $yesno,
1059:             ),
1060:             'max_package_weight' => array(
1061:                 'title' => $this->l('Maximum allowed package weight for this carrier'),
1062:                 'type' => 'text',
1063:                 'description' => '',
1064:                 'default' => '20',
1065:                 'css' => 'width: 300px;',
1066:             ),
1067:             'handling_action' => array(
1068:                 'title' => $this->l('Handling action'),
1069:                 'type' => 'select',
1070:                 'description' => $this->l('Per Order: Shipping cost equals Shipping price')
1071:                 . '<br/>' . $this->l('Per Package: Shipping cost equals Number of Items in cart multiplied by shipping price')
1072:                 ,
1073:                 'default' => 'yes',
1074:                 'css' => 'width: 300px;',
1075:                 'options' => $boxUnits,
1076:             ),
1077:             'free_groups' => array(
1078:                 'title' => $this->l('Client groups who can get free shipping'),
1079:                 'type' => 'multiselect',
1080:                 'description' => $this->l('hold down CTRL / CMD button to select/deselect multiple'),
1081:                 'default' => '',
1082:                 'css' => 'width: 300px;',
1083:                 'options' => $this->_getHelperModule()->getClientGroups(),
1084:             ),
1085:             'enable_free_shipping' => array(
1086:                 'title' => $this->l('Enable free shipping'),
1087:                 'type' => 'select',
1088:                 'description' => '',
1089:                 'default' => 'no',
1090:                 'css' => 'width: 300px;',
1091:                 'options' => $yesno,
1092:             ),
1093:             'free_shipping_from' => array(
1094:                 'title' => $this->l('Free shipping subtotal'),
1095:                 'type' => 'text',
1096:                 'description' => '',
1097:                 'default' => '',
1098:                 'css' => 'width: 300px;',
1099:                 'validate' => array('validate_number'),
1100:             ),
1101:             'sallowspecific' => array(
1102:                 'title' => $this->l('Ship to applicable countries'),
1103:                 'type' => 'select',
1104:                 'description' => '',
1105:                 'default' => '1',
1106:                 'css' => 'width: 300px;',
1107:                 'options' => $countryUnits,
1108:             ),
1109:             'specificcountry' => array(
1110:                 'title' => $this->l('Ship to Specific countries'),
1111:                 'type' => 'multiselect',
1112:                 'description' => '',
1113:                 'default' => 'EE,LV,LT',
1114:                 'css' => 'width: 300px;',
1115:                 'options' => $this->_getHelperModule()->getCountriesAsOptions(),
1116:             ),
1117:             'dis_first' => array(
1118:                 'title' => $this->l('Show customer one dropdown instead of two'),
1119:                 'type' => 'select',
1120:                 'description' => $this->l('If this setting is enabled, then customer will be displayed only with a list of stores you entered.')
1121:                 . '<br/>' . $this->l('If this setting is disabled, then customer has to pick country/city first and then customer will be displayed second select menu, which contains stores in the selected county/city.'),
1122:                 'default' => 'yes',
1123:                 'css' => 'width: 300px;',
1124:                 'options' => $yesno,
1125:             ),
1126:             'gr_width' => array(
1127:                 'title' => $this->l('Width in pixels for city select menu'),
1128:                 'type' => 'text',
1129:                 'description' => $this->l('Use only when you feel that select menu is too wide.'),
1130:                 'default' => '',
1131:                 'css' => 'width: 300px;',
1132:             ),
1133:             'of_width' => array(
1134:                 'title' => $this->l('Width in pixels for office select menu'),
1135:                 'type' => 'text',
1136:                 'description' => $this->l('Use only when you feel that select menu is too wide.'),
1137:                 'default' => '250',
1138:                 'css' => 'width: 300px;',
1139:             ),
1140:         );
1141: 
1142: 
1143:         if ($this->dataSendExecutor) {
1144:             $this->form_fields = $this->addArrayAfterKey($this->form_fields, $this->dataSendExecutor->initFormFields(), 'free_shipping_from');
1145:         }
1146: 
1147:         return $this->form_fields;
1148:     }
1149: 
1150:     /**
1151:      * <p>This function is called when store administrator is viewing the order.</p>
1152:      * <p>Renders the selected parcelstore</p>
1153:      * @param int $cart_id id cart for the order
1154:      * @return string html string
1155:      */
1156:     public function displayInfoByCart($cart_id) {
1157:         $offices = $this->_getHelperModule()->getOfficesFromCart($cart_id);
1158:         $terminals = array();
1159:         foreach ($offices as $address_id => $office) {
1160:             $terminals[] = $this->getAdminTerminalTitle($office);
1161:         }
1162:         if ($this->dataSendExecutor != null) {
1163:             $extraInfo = $this->dataSendExecutor->displayInfoByCart($cart_id);
1164:         }
1165:         return '<div class="eabi_dpd_parcelstore_chosen">'.$this->l('Chosen parcel terminal:') . ' <b>' . implode(', ', $terminals) . '</b>' . $extraInfo.'</div>';
1166:     }
1167: 
1168:     /**
1169:      * <p>Adds <code>$appendArray</code> after specified <p>$afterKey</p> inside <code>$inputArray</code></p>
1170:      * @param array $inputArray original assoc array
1171:      * @param array $appendArray array to be appended to original assoc array
1172:      * @param boolean $afterKey when not supplied or key is not found, then appendArray is added to the end
1173:      * @return array
1174:      */
1175:     public function addArrayAfterKey($inputArray, $appendArray, $afterKey = false) {
1176:         $resultingArray = array();
1177:         $appended = false;
1178:         if (!is_string($afterKey)) {
1179:             $afterKey = false;
1180:         }
1181: 
1182:         foreach ($inputArray as $key => $value) {
1183:             $resultingArray[$key] = $value;
1184:             if ($key === $afterKey) {
1185:                 foreach ($appendArray as $iKey => $iValue) {
1186:                     $resultingArray[$iKey] = $iValue;
1187:                 }
1188:                 $appended = true;
1189:             }
1190:         }
1191: 
1192:         if (!$appended) {
1193:             foreach ($appendArray as $iKey => $iValue) {
1194:                 $resultingArray[$iKey] = $iValue;
1195:             }
1196:             $appended = true;
1197:         }
1198:         return $resultingArray;
1199:     }
1200: 
1201:     /**
1202:      * <p>Returns cached instance of base helper module</p>
1203:      * @return Eabi_Postoffice
1204:      */
1205:     public function _getHelperModule() {
1206:         if (is_null(self::$_helperModuleInstance)) {
1207:             self::$_helperModuleInstance = Module::getInstanceByName('eabi_postoffice');
1208:         }
1209:         return self::$_helperModuleInstance;
1210:     }
1211: 
1212:     /**
1213:      * <p>Indicates that parcel terminals should be updated once every 1440 minutes (24h)</p>
1214:      * @return int
1215:      */
1216:     public function getUpdateInterval() {
1217:         $interval = $this->getConfigData('UPD_INTERVAL');
1218:         if (!$interval) {
1219:             return 1440;
1220:         }
1221:         return $interval;
1222:     }
1223: 
1224:     /**
1225:      * <p>Marks that this modules pickup point list updated as of timestamp</p>
1226:      * @param int $lastUpdated timestamp
1227:      * @return null
1228:      */
1229:     public function setLastUpdated($lastUpdated) {
1230:         Configuration::updateValue(self::CONST_PREFIX . 'LST_UPD', $lastUpdated);
1231:         return;
1232:     }
1233: 
1234:     /**
1235:      * <p>Returns unix timestamp, when pickup points for this module were last updated</p>
1236:      * @return int timestamp
1237:      */
1238:     public function getLastUpdated() {
1239:         return $this->getConfigData('LST_UPD');
1240:     }
1241: 
1242:     /**
1243:      * <p>Should return group title for current pickup point.</p>
1244:      * @param array $group row from database <code>eabi_postoffice</code>
1245:      * @return string
1246:      */
1247:     public function getGroupTitle($group) {
1248:         return htmlspecialchars($group['group_name']);
1249:     }
1250: 
1251:     /**
1252:      * <p>Returns parcel terminal name when short names are enabled.</p>
1253:      * <p>Returns parcel terminal name with address, telephone, opening times when short names are disabled.</p>
1254:      * @param string $terminal
1255:      * @return string
1256:      */
1257:     public function getTerminalTitle($terminal) {
1258:         if ($this->getConfigData('SHORTNAME') == 'yes') {
1259:             return htmlspecialchars($terminal['name']);
1260:         }
1261:         return htmlspecialchars($terminal['name'] . ' (' . $terminal['description'] . ')');
1262:     }
1263: 
1264:     /**
1265:      * <p>Returns parcel terminal name when short names are enabled.</p>
1266:      * <p>Returns parcel terminal name with address, telephone, opening times when short names are disabled.</p>
1267:      * @param array $terminal
1268:      * @return string
1269:      */
1270:     public function getAdminTerminalTitle($terminal) {
1271:         if ($this->getConfigData('SHORTNAME') == 'yes') {
1272:             return htmlspecialchars($terminal['group_name'] . ' - ' . $terminal['name']);
1273:         }
1274:         return htmlspecialchars($terminal['group_name'] . ' - ' . $terminal['name'] . ' ' . $terminal['description']);
1275:     }
1276: 
1277:     /**
1278:      * <p>Calls this modules <code>l</code> function</p>
1279:      * @param string $string
1280:      * @return string
1281:      */
1282:     public function ls($string) {
1283:         return $this->l($string);
1284:     }
1285: 
1286:     /**
1287:      * <p>Returns array of parcel terminals from DPD server or boolean false if fetching failed.</p>
1288:      * @return array|boolean
1289:      */
1290:     public function getOfficeList() {
1291:         
1292:         $url = $this->getConfigData('API_URL');
1293:         
1294:         $auth = '';
1295:         if (strpos($url, 'dpd.surflink.ee') > 0) {
1296:             $auth = "Authorization: Basic " . base64_encode("demo:demo") . "\r\n";
1297:         }
1298: 
1299:         $options = array(
1300:             'http' => array(
1301:                 'method' => 'POST',
1302:                 'header' => $auth ."Content-type: application/x-www-form-urlencoded\r\n",
1303: //                'header' => 'Content-type: application/x-www-form-urlencoded',
1304:                 'content' => http_build_query(array('op' => 'pudo')),
1305:         ));
1306:         $context = stream_context_create($options);
1307:         $postRequestResult = file_get_contents($url, false, $context);
1308:         $body = @json_decode($postRequestResult, true);
1309:         if (!is_array($body) || !isset($body['Error_code']) || $body['Error_code'] !== 0) {
1310:             throw new Exception(sprintf($this->l('DPD request failed with response: %s'), print_r($body, true)));
1311:         }
1312: 
1313: 
1314: 
1315: 
1316:         if (!$body || !is_array($body) || !isset($body['data'])) {
1317:             return false;
1318:         }
1319:         $result = array();
1320:         foreach ($body['data'] as $remoteParcelTerminal) {
1321:             $result[] = array(
1322:                 'place_id' => $remoteParcelTerminal['Sh_pudo_id'],
1323:                 'name' => $remoteParcelTerminal['Pudo_name'],
1324:                 'city' => trim($remoteParcelTerminal['Sh_city']),
1325:                 'county' => '',
1326:                 'description' => $this->_getDescription($remoteParcelTerminal),
1327:                 'country' => $remoteParcelTerminal['Sh_country'],
1328:                 'zip' => $remoteParcelTerminal['Sh_postal'],
1329:                 'group_sort' => $this->getGroupSort($remoteParcelTerminal['Sh_city']),
1330:             );
1331:         }
1332:         if (count($result) == 0) {
1333:             return false;
1334:         }
1335:         return $result;
1336:     }
1337: 
1338:     /**
1339:      * <p>Fetches one line long human readable parcel terminal description from DPD Pudo instance</p>
1340:      * @param array $parcelT
1341:      * @return string
1342:      */
1343:     protected function _getDescription($parcelT) {
1344:         if (!isset($parcelT['Pudo_worktime']) || !$parcelT['Pudo_worktime']) {
1345:             return trim($parcelT['Sh_street'] . ' ' . $parcelT['Sh_city'] . ' ' . $parcelT['Sh_postal'] . ', ' . $parcelT['Sh_country'] . ' ' . $parcelT['Sh_phone']);
1346:         } else {
1347:             return trim($parcelT['Sh_street'] . ' ' . $parcelT['Sh_city'] . ' ' . $parcelT['Sh_postal'] . ', ' . $parcelT['Sh_country'] . ' ' . $parcelT['Sh_phone']
1348:                     . ' ' . $this->_getDpdHelper()->getOpeningsDescriptionFromTerminal($parcelT['Pudo_worktime'], $this->_getDpdHelper()->getLocaleToTerritory(strtoupper($parcelT['Sh_country']))));
1349:         }
1350:     }
1351: 
1352:     /**
1353:      * <p>Groups parcel terminals by following rules:</p>
1354:      * <ul>
1355:       <li>In Estonia parcel terminals from Tallinn, Tartu, Pärnu are displayed first respectively and remaining parcel terminals are displayed in alphabetical order.</li>
1356:       <li>In Latvia parcel terminals from Riga, Daugavpils, Liepaja, Jelgava, Jurmala are displayed first respectively and remaining parcel terminals are displayed in alphabetical order.</li>
1357:       <li>In Lithuania parcel terminals from Vilnius, Kaunas, Klaipeda, Siauliai, Alytus are displayed first respectively and remaining parcel terminals are displayed in alphabetical order.</li>
1358:       </ul>
1359:      * @param string $group_name
1360:      * @return int
1361:      * @see Eabi_Postoffice_Model_Carrier_Abstract::getGroupSort()
1362:      */
1363:     public function getGroupSort($group_name) {
1364:         $group_name = trim(strtolower($group_name));
1365:         $sorts = array(
1366:             //Estonia
1367:             'tallinn' => 20,
1368:             'tartu' => 19,
1369:             'pärnu' => 18,
1370:             //Latvia
1371:             'riga' => 20,
1372:             'daugavpils' => 19,
1373:             'liepaja' => 18,
1374:             'jelgava' => 17,
1375:             'jurmala' => 16,
1376:             //Lithuania
1377:             'vilnius' => 20,
1378:             'kaunas' => 19,
1379:             'klaipeda' => 18,
1380:             'siauliai' => 17,
1381:             'alytus' => 16,
1382:         );
1383:         if (isset($sorts[$group_name]) && $this->getConfigData('SORT_OFFICES')) {
1384:             return $sorts[$group_name];
1385:         }
1386:         if (strpos($group_name, '/') > 0 && $this->getConfigData('SORT_OFFICES')) {
1387:             return 0;
1388:         }
1389:         return 0;
1390:     }
1391: 
1392:     /**
1393:      * 
1394:      * @return eabi_dpd_parcelstore_validator_helper
1395:      */
1396:     protected function _getValidator() {
1397:         return $this->_getHelperModule()->helper('validator_helper', eabi_dpd_parcelstore::NAME);
1398:     }
1399: 
1400:     /**
1401:      * 
1402:      * @return eabi_dpd_parcelstore_html_helper
1403:      */
1404:     protected function _getHtmlHelper() {
1405:         /* @var $helper eabi_dpd_parcelstore_html_helper */
1406:         $helper = $this->_getHelperModule()->helper('html_helper', eabi_dpd_parcelstore::NAME);
1407:         $helper->setContext($this->context)->setModuleInstance($this);
1408:         return $helper;
1409:     }
1410: 
1411:     /**
1412:      * 
1413:      * @return eabi_dpd_parcelstore_dpd_helper
1414:      */
1415:     protected function _getDpdHelper() {
1416:         return $this->_getHelperModule()->helper('dpd_helper', eabi_dpd_parcelstore::NAME);
1417:     }
1418: 
1419:     /**
1420:      * 
1421:      * @return eabi_postoffice_dialcode_helper
1422:      */
1423:     protected function _getDialCodeHelper() {
1424:         return $this->_getHelperModule()->helper('dialcode_helper', 'eabi_postoffice');
1425:     }
1426: 
1427:     /**
1428:      * <p>Returns empty string</p>
1429:      * @param OrderCore $order
1430:      * @return string
1431:      */
1432:     protected function _getRemark($order) {
1433:         return '';
1434:     }
1435: 
1436:     /**
1437:      * <p>Returns number or parcels for the order according to Maximum Package Weight defined in DPD settings</p>
1438:      * @param OrderCore $order
1439:      * @return int
1440:      * @see Eabi_Postoffice_Helper_Data::getNumberOfPackagesFromItemWeights()
1441:      */
1442:     protected function _getNumberOfPackagesForOrder($order) {
1443:         $productWeights = array();
1444:         $orderItems = $order->getProducts();
1445:         foreach ($orderItems as $orderItem) {
1446:             /* @var $orderItem Mage_Sales_Model_Order_Item */
1447:             for ($i = 0; $i < ($orderItem['product_quantity'] - $orderItem['product_quantity_refunded']); $i++) {
1448:                 $productWeights[] = $orderItem['product_weight'];
1449:             }
1450:         }
1451:         return $this->_getDpdHelper()->getNumberOfPackagesFromItemWeights($productWeights, $this->getConfigData('MAX_PACKAGE_WEIGHT'));
1452:     }
1453:     
1454:     /* module upgrades */
1455: 
1456:     /**
1457:      * <p>Adds 2.5€ COD fee to Estonia, Latvia, Lithuania</p>
1458:      * @return bool
1459:      */
1460:     public function upgrade_module_0_6() {
1461:         /*
1462:          * Set default CASH on delivery fee to following countries:
1463:          *  EE
1464:          *  LV
1465:          *  LT
1466:          * 
1467:          * Set this fee to all defined config scopes
1468:          */
1469:         $configPaths = array(
1470:             'E_DPDEEP_HANDLING_FEE_COUNTRY',
1471:         );
1472:         $countriesToApply = array(
1473:             'EE', 'LV', 'LT',
1474:         );
1475:         $db = Db::getInstance();
1476: 
1477: 
1478: 
1479:         $inConfigPaths = implode("','", $configPaths);
1480:         $sql = "SELECT * FROM `"._DB_PREFIX_."configuration` where `name` IN ('{$inConfigPaths}')";
1481:         $configDatas = $db->executeS($sql, true);
1482: 
1483:         foreach ($configDatas as $configData) {
1484:             $oldShippingPriceSet = @unserialize($configData['value']);
1485:             //update value
1486:             if (is_array($oldShippingPriceSet)) {
1487:                 foreach ($oldShippingPriceSet as $randomKey => $countryPrices) {
1488:                     if (in_array($countryPrices['country_id'], $countriesToApply)) {
1489:                         if (!isset($countryPrices['cod_fee']) || !$countryPrices['cod_fee']) {
1490:                             $oldShippingPriceSet[$randomKey]['cod_fee'] = '2.50';
1491:                         }
1492:                     }
1493:                 }
1494:                 //update done -re-serialize and save
1495:                 $configData['value'] = serialize($oldShippingPriceSet);
1496:                 $idConfiguration = $configData['id_configuration'];
1497:                 
1498:                 //we cannot update primary key
1499:                 unset($configData['id_configuration']);
1500:                 
1501:                 //we do not need to update those, since mainly they are empty strings
1502:                 //and on int check they throw error like incorrect integer value
1503:                 unset($configData['id_shop']);
1504:                 unset($configData['id_shop_group']);
1505:                 unset($configData['name']);
1506:                 
1507:                 $res = $db->update('configuration', $configData, "id_configuration = '{$db->escape($idConfiguration)}'");
1508:                 if ($res === false) {
1509:                     return false;
1510:                 }
1511:             }
1512:         }
1513:         return true;
1514:     }
1515:     
1516:     
1517:     public function upgrade_module_0_8() {
1518:         $configPaths = array(
1519:             'E_DPDEEP_API_URL',
1520:         );
1521:         $db = Db::getInstance();
1522:         $inConfigPaths = implode("','", $configPaths);
1523:         $sql = "SELECT * FROM `" . _DB_PREFIX_ . "configuration` where `name` IN ('{$inConfigPaths}')";
1524:         $configDatas = $db->executeS($sql, true);
1525: 
1526: 
1527:         foreach ($configDatas as $configData) {
1528:             $oldValue = $configData['value'];
1529:             if ($oldValue == 'http://dpd.surflink.ee/rpc/gateway/' || $oldValue == 'http://dpd.surflink.ee/rpc/gateway') {
1530:                 $configData['value'] = 'http://demo.surflink.ee:51680/rpc/gateway/';
1531:                 $idConfiguration = $configData['id_configuration'];
1532: 
1533:                 //we cannot update primary key
1534:                 unset($configData['id_configuration']);
1535: 
1536:                 //we do not need to update those, since mainly they are empty strings
1537:                 //and on int check they throw error like incorrect integer value
1538:                 unset($configData['id_shop']);
1539:                 unset($configData['id_shop_group']);
1540:                 unset($configData['name']);
1541: 
1542:                 $res = $db->update('configuration', $configData, "id_configuration = '{$db->escape($idConfiguration)}'");
1543:                 if ($res === false) {
1544:                     return false;
1545:                 }
1546:             }
1547:         }
1548:         return true;
1549:     }
1550: 
1551: }
1552: 
API documentation generated by ApiGen 2.8.0