* @copyright 2019 Futurenext srl * @license https://www.zakeke.com/privacy/#general_conditions */ if (!defined('_PS_VERSION_')) { exit; } include_once dirname(__FILE__) . '/webservice/WebserviceSpecificManagementZakekeCurrency.php'; include_once dirname(__FILE__) . '/classes/ZakekeEnabled.php'; include_once dirname(__FILE__) . '/classes/ZakekeConfiguratorEnabled.php'; include_once dirname(__FILE__) . '/classes/ZakekeApi.php'; include_once dirname(__FILE__) . '/classes/ZakekeItem.php'; include_once dirname(__FILE__) . '/classes/ZakekeConfiguratorItem.php'; include_once dirname(__FILE__) . '/classes/ZakekePrice.php'; include_once dirname(__FILE__) . '/classes/ZakekeCompatibility.php'; include_once dirname(__FILE__) . '/classes/ZakekeOrderService.php'; include_once dirname(__FILE__) . '/classes/ZakekeDisplayCustomization.php'; include_once dirname(__FILE__) . '/classes/ZakekeWebserviceService.php'; include_once dirname(__FILE__) . '/classes/ZakekeConnectService.php'; include_once dirname(__FILE__) . '/classes/ZakekeEditUrl.php'; define('ZAKEKE_VERSION', '3.2.9'); class Zakeke extends Module { /** @var ZakekeApi */ protected $zakekeApi; /** @var bool */ private $cart_action_is_executing = false; /** @var bool */ private $order_resend_is_executing = false; /** * Zakeke constructor. */ public function __construct() { $this->name = 'zakeke'; $this->tab = 'front_office_features'; $this->version = '3.2.9'; $this->author = 'Futurenext srl'; $this->need_instance = 0; $this->ps_versions_compliancy = array('min' => '1.6', 'max' => _PS_VERSION_); $this->bootstrap = true; $this->module_key = '8b06b71e6e82c5824a020e70178a4e1a'; parent::__construct(); $this->displayName = $this->l('Zakeke Interactive Product Designer'); $this->description = $this->l( 'Innovative platforms to enable your customers with live product customization on your e-store.' ); $this->confirmUninstall = $this->l('Are you sure you want to uninstall?'); if (!$this->hasAccountConnected()) { $this->warning = $this->l('You must set your Zakeke credentials'); } $this->zakekeApi = new ZakekeApi($this->isDebugModeActive()); if (!$this->isRegisteredInHook('actionCartSave')) { $this->registerHook('actionCartSave'); } if (!$this->isRegisteredInHook('actionObjectProductInCartDeleteBefore')) { $this->registerHook('actionObjectProductInCartDeleteBefore'); } if (!$this->isRegisteredInHook('actionAdminControllerSetMedia')) { $this->registerHook('actionAdminControllerSetMedia'); } } /** * Check if the module has all necessary data to work properly * * @return bool */ private function hasAccountConnected() { return Configuration::get('ZAKEKE_API_CLIENT_ID') && Configuration::get('ZAKEKE_API_SECRET_KEY'); } /** * Check if Zakeke is in debug mode. * * @return bool */ private function isDebugModeActive() { $debug = Configuration::get('ZAKEKE_DEBUG'); return $debug && $debug == 'yes'; } /** * Check if Zakeke is forcing the product customization * * @return bool */ protected function isForceActive() { $zakekeForce = Configuration::get('ZAKEKE_FORCE'); return $zakekeForce == 1; } /** * Get an instance of the Zakeke API. * * @return ZakekeApi */ public function getZakekeApi() { return $this->zakekeApi; } /** * Get an identifier for the Zakeke guest * * @param bool $create Create a Zakeke guest cookie if not present * * @return string */ public function getZakekeGuestCode($create = true) { $guestCode = $this->context->cookie->zakeke_guest; if ($create && !$guestCode) { if (_PS_VERSION_ < 1.7) { $guestCode = Tools::passwdGen(90, 'RANDOM'); } else { $guestCode = PhpEncryption::createNewRandomKey(); } $this->context->cookie->zakeke_guest = $guestCode; $this->context->cookie->write(); } return Tools::substr($guestCode, 0, 90); } public function displayForm() { // Get default language $default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); // Init Fields form array $fields_form = array(); $fields_form[] = array( 'form' => array( 'legend' => array( 'title' => $this->l('Zakeke API keys'), ), 'input' => array( array( 'type' => 'text', 'label' => $this->l('Zakeke API Client ID'), 'name' => 'ZAKEKE_API_CLIENT_ID', 'desc' => vsprintf( $this->l('Your Zakeke account API. Need a Zakeke account? %sClick here to get one%s'), array('', '') ), 'required' => true, 'class' => 'fixed-width-xl' ), array( 'type' => 'password', 'label' => $this->l('Zakeke API Secret Key'), 'name' => 'ZAKEKE_API_SECRET_KEY', 'desc' => $this->l('Your Zakeke account API secret key.'), 'required' => true, 'class' => 'fixed-width-xl' ) ), 'submit' => array( 'title' => $this->l('Save'), 'class' => 'btn btn-default pull-right', 'name' => 'ZAKEKE_SAVE_CREDENTIALS' ) ) ); $fields_form[] = array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), ), 'input' => array( array( 'type' => 'switch', 'label' => $this->l('Force product customization'), 'name' => 'ZAKEKE_FORCE', 'desc' => $this->l( 'Your customers will be forced to customize the product before adding it to cart.' ), 'align' => 'center', 'active' => 'status', 'orderby' => false, 'class' => 'fixed-width-xs', 'values' => array( array( 'id' => 'active_on', 'value' => 1, 'label' => $this->l('Enabled') ), array( 'id' => 'active_off', 'value' => 0, 'label' => $this->l('Disabled') ) ), ) ), 'submit' => array( 'title' => $this->l('Save'), 'class' => 'btn btn-default pull-right', 'name' => 'ZAKEKE_SAVE_SETTINGS' ) ) ); $helper = new HelperForm(); // Module, token and currentIndex $helper->module = $this; $helper->name_controller = $this->name; $helper->token = Tools::getAdminTokenLite('AdminModules'); $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; // Language $helper->default_form_language = $default_lang; $helper->allow_employee_form_lang = $default_lang; // Title and toolbar $helper->title = $this->displayName; $helper->show_toolbar = true; // false -> remove toolbar $helper->toolbar_scroll = true; // yes - > Toolbar is always visible on the top of the screen. $helper->submit_action = 'submit' . $this->name; $helper->toolbar_btn = array( 'save' => array( 'desc' => $this->l('Save'), 'href' => AdminController::$currentIndex . '&configure=' . $this->name . '&save' . $this->name . '&token=' . Tools::getAdminTokenLite('AdminModules'), ), 'back' => array( 'href' => AdminController::$currentIndex . '&token=' . Tools::getAdminTokenLite('AdminModules'), 'desc' => $this->l('Back to list') ) ); // Load current value $helper->fields_value['ZAKEKE_API_CLIENT_ID'] = Configuration::get('ZAKEKE_API_CLIENT_ID'); $helper->fields_value['ZAKEKE_API_SECRET_KEY'] = Configuration::get('ZAKEKE_API_SECRET_KEY'); $helper->fields_value['ZAKEKE_FORCE'] = Configuration::get('ZAKEKE_FORCE'); return $helper->generateForm($fields_form); } public function getContent() { $output = ''; if (Tools::isSubmit('ZAKEKE_SAVE_CREDENTIALS')) { $zakekeApiClientId = (string)Tools::getValue('ZAKEKE_API_CLIENT_ID'); $zakekeApiSecretKey = (string)Tools::getValue('ZAKEKE_API_SECRET_KEY'); if (empty($zakekeApiClientId) && empty($zakekeApiSecretKey)) { $zakekeApiClientId = ''; $zakekeApiSecretKey = ''; } elseif (empty($zakekeApiClientId) || empty($zakekeApiSecretKey)) { $zakekeApiClientId = ''; $zakekeApiSecretKey = ''; $output .= $this->displayError($this->l('Zakeke API Client ID and Secret Key must not be empty')); } else { if (!$this->getZakekeApi()->checkCredentials($zakekeApiClientId, $zakekeApiSecretKey)) { $zakekeApiClientId = ''; $zakekeApiSecretKey = ''; $output .= $this->displayError($this->l('Invalid Zakeke API keys')); } else { $output .= $this->displayConfirmation($this->l('Zakeke API keys updated')); } } Configuration::updateValue('ZAKEKE_API_CLIENT_ID', $zakekeApiClientId); Configuration::updateValue('ZAKEKE_API_SECRET_KEY', $zakekeApiSecretKey); } elseif (Tools::isSubmit('ZAKEKE_SAVE_SETTINGS')) { $zakekeForce = (string)Tools::getValue('ZAKEKE_FORCE'); if ($zakekeForce != 0 && $zakekeForce != 1) { $output .= $this->displayError($this->l('Invalid or empty value for force customization')); } else { $output .= $this->displayConfirmation($this->l('Settings updated')); } Configuration::updateValue('ZAKEKE_FORCE', $zakekeForce); } if (empty(Configuration::get('ZAKEKE_API_CLIENT_ID')) || empty(Configuration::get('ZAKEKE_API_CLIENT_ID'))) { ZakekeWebserviceService::enableWebservice(); $zakekeWebserviceKey = ZakekeWebserviceService::getOrCreateZakekeWebServiceKey(); $this->smarty->assign(array( 'zakeke_connect_url' => ZakekeConnectService::getConnectUrl($zakekeWebserviceKey), 'zakeke_showall_url' => $_SERVER['REQUEST_URI'] . '&showall=1' )); $output .= $this->display(__FILE__, 'infos.tpl'); if (Tools::getValue('showall', false)) { $output .= $this->displayForm(); } } else { $output .= $this->displayForm(); } return $output; } /** * Register the Zakeke WS endpoint * * @param array $params * * @return array */ public function hookAddWebserviceResources($params) { return array( 'zakeke_currency' => array( 'description' => 'ZakekeCurrency', 'specific_management' => true, 'forbidden_method' => array('POST', 'PUT', 'DELETE', 'HEAD') ), 'zakeke_enabled_list' => array('description' => 'Zakeke enabled products', 'class' => 'ZakekeEnabled'), 'zakeke_configurator_enabled_list' => array( 'description' => 'Zakeke configurator enabled products', 'class' => 'ZakekeConfiguratorEnabled' ) ); } private function customizerHookDisplayHeaderPS16() { $customizeButtonLabel = $this->l('Customize'); $zakekeAddUrl = $this->context->link->getModuleLink('zakeke', 'customizer'); Media::addJsDef(array( 'zakekeCustomizeLabel' => addcslashes($customizeButtonLabel, "'"), 'zakekeAddUrl' => addcslashes($zakekeAddUrl, "'") )); if ($this->isForceActive()) { $this->context->controller->addJS( $this->_path . '/views/js/ps16/product-page-force.js' ); } else { $this->context->controller->addJS( $this->_path . '/views/js/ps16/product-page.js' ); } } private function configuratorHookDisplayHeaderPS16() { $customizeButtonLabel = $this->l('Configure'); $zakekeConfiguratorAddUrl = $this->context->link->getModuleLink('zakeke', 'configurator'); Media::addJsDef(array( 'zakekeConfigureLabel' => addcslashes($customizeButtonLabel, "'"), 'zakekeConfiguratorAddUrl' => addcslashes($zakekeConfiguratorAddUrl, "'") )); if ($this->isForceActive()) { $this->context->controller->addJS( $this->_path . '/views/js/ps16/configurator/product-page-force.js' ); } else { $this->context->controller->addJS( $this->_path . '/views/js/ps16/configurator/product-page.js' ); } } /** * Add the Zakeke elements inside the product page of customizable products for PS 1.6 */ public function hookDisplayHeaderPS16() { try { $this->context->controller->addJS( $this->_path . '/views/js/ps16/update.js' ); $zakekeInfoUrl = $this->context->link->getModuleLink('zakeke', 'info'); Media::addJsDef(array( 'zakekeInfoUrl' => addcslashes($zakekeInfoUrl, "'") )); if (!method_exists($this->context->controller, 'getProduct')) { return; } $product = $this->context->controller->getProduct(); if (!$product) { return; } $zakekeEnabledId = ZakekeEnabled::productEnabledId($product->id); if ($zakekeEnabledId !== false) { $this->customizerHookDisplayHeaderPS16(); } $zakekeConfiguratorEnabledId = ZakekeConfiguratorEnabled::productEnabledId($product->id); if ($zakekeConfiguratorEnabledId !== false) { $this->configuratorHookDisplayHeaderPS16(); } } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookDisplayHeader Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to initialize'); } } private function customizerHookDisplayHeaderPS17() { $zakekeAddUrl = $this->context->link->getModuleLink('zakeke', 'customizer'); $customizeButtonLabel = $this->l('Customize'); Media::addJsDef(array( 'zakekeAddUrl' => addcslashes($zakekeAddUrl, "'"), 'zakekeCustomizeLabel' => addcslashes($customizeButtonLabel, "'") )); if ($this->isForceActive()) { $this->context->controller->registerJavascript( 'modules-zakake-product-page', 'modules/' . $this->name . '/views/js/product-page-force.js', array('position' => 'bottom', 'priority' => 150) ); } else { $this->context->controller->registerJavascript( 'modules-zakake-product-page', 'modules/' . $this->name . '/views/js/product-page.js', array('position' => 'bottom', 'priority' => 150) ); } } private function configuratorHookDisplayHeaderPS17() { $configureButtonLabel = $this->l('Configure'); $zakekeConfiguratorAddUrl = $this->context->link->getModuleLink('zakeke', 'configurator'); Media::addJsDef(array( 'zakekeConfigureLabel' => addcslashes($configureButtonLabel, "'"), 'zakekeConfiguratorAddUrl' => addcslashes($zakekeConfiguratorAddUrl, "'") )); if ($this->isForceActive()) { $this->context->controller->registerJavascript( 'modules-zakake-configurator-product-page', 'modules/' . $this->name . '/views/js/configurator/product-page-force.js', array('position' => 'bottom', 'priority' => 150) ); } else { $this->context->controller->registerJavascript( 'modules-zakake-configurator-product-page', 'modules/' . $this->name . '/views/js/configurator/product-page.js', array('position' => 'bottom', 'priority' => 150) ); } } /** * Add the Zakeke elements inside the product page of customizable products */ public function hookDisplayHeaderPS17() { try { if (!method_exists($this->context->controller, 'getProduct')) { return; } $product = $this->context->controller->getProduct(); if (!$product) { return; } $zakekeEnabledId = ZakekeEnabled::productEnabledId($product->id); if ($zakekeEnabledId !== false) { $this->customizerHookDisplayHeaderPS17(); } $zakekeConfiguratorEnabledId = ZakekeConfiguratorEnabled::productEnabledId($product->id); if ($zakekeConfiguratorEnabledId !== false) { $this->configuratorHookDisplayHeaderPS17(); } } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookDisplayHeader Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to initialize'); } } /** * Add the Zakeke elements inside the product page of customizable products * * @param array $params */ public function hookDisplayMobileHeader($params) { if (_PS_VERSION_ < 1.7) { $this->hookDisplayHeaderPS16(); } } /** * Add the Zakeke elements inside the product page of customizable products * * @param array $params */ public function hookDisplayHeader($params) { if (_PS_VERSION_ < 1.7) { $this->hookDisplayHeaderPS16(); } else { $this->hookDisplayHeaderPS17(); } } /** * dd the Zakeke elements inside the Prestashop's admin * * @param array $params */ public function hookActionAdminControllerSetMedia($params) { if (_PS_VERSION_ < 1.7) { $zakekeAdminInfo = $this->context->link->getAdminLink('AdminZakeke'); Media::addJsDef(array( 'zakekeAdminInfo' => addcslashes($zakekeAdminInfo, "'") )); $this->context->controller->addJS($this->_path . 'views/js/ps16/admin_update.js'); } else { $this->context->controller->addJS($this->_path . 'views/js/admin_update.js'); } } /** * Correctly display the Zakeke customization both on FO and BO * * @param array $params * * @return string */ public function hookDisplayCustomization($params) { try { $customization = $params['customization']; $zakekeData = json_decode($customization['value'], true); $zakekeDisplayCustomization = new ZakekeDisplayCustomization($this->getZakekeApi(), $this->context); $templatesVariables = $zakekeDisplayCustomization->getTemplatesVariables($zakekeData); $this->context->smarty->assign($templatesVariables); if (!$this->order_resend_is_executing && isset($templatesVariables['zakeke_download_show']) && $templatesVariables['zakeke_download_show'] && isset($templatesVariables['zakeke_download_ready']) && !$templatesVariables['zakeke_download_ready']) { $id_order = Tools::getValue('id_order'); if ($id_order) { $this->order_resend_is_executing = true; $order = new Order(Tools::getValue('id_order')); if (Validate::isLoadedObject($order) && ($order->hasBeenPaid() || $order->isInPreparation())) { $zakekeOrderService = new ZakekeOrderService($this); $zakekeOrderService->process($order); $this->order_resend_is_executing = false; } } } return $this->display(__FILE__, 'displayCustomization.tpl'); } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookDisplayCustomization Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to get the customization details'); } } /** * Correctly display the Zakeke customization both on FO and BO * * @param array $params * * @return string */ public function hookDisplayCartExtraProductActions($params) { try { $product = $params['product']; if (!isset($product['id_customization'])) { return ''; } $id_customization = $product['id_customization']; $customization_data = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 'SELECT `value` FROM `' . _DB_PREFIX_ . 'customized_data` WHERE `id_customization` = ' . (int)($id_customization) ); if (!$customization_data) { return ''; } $zakekeEditUrl = new ZakekeEditUrl($product, $customization_data, $this->context); $edit_url = $zakekeEditUrl->getEditUrl(); if ($edit_url === false) { return ''; } $this->context->smarty->assign('zakeke_edit_url', $edit_url); return $this->display(__FILE__, 'displayCartExtraProductActions.tpl'); } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookDisplayCartExtraProductActions Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to get the edit link'); } } /** * Register orders that contains Zakeke products * * @param array $params */ public function hookDisplayOrderConfirmation($params) { try { /** @var Order $order */ $order = null; if (_PS_VERSION_ < 1.7) { $order = $params['objOrder']; } else { $order = $params['order']; } $zakekeOrderService = new ZakekeOrderService($this); if ($order !== null && method_exists($order, 'getProducts')) { $zakekeOrderService->process($order); } } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookDisplayOrderConfirmation Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to register the order'); } } /** * Register the Admin controller for Zakeke * * @return bool * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function createAjaxController() { $tab = new Tab(); $tab->active = 1; $languages = Language::getLanguages(false); if (is_array($languages)) { foreach ($languages as $language) { $tab->name[$language['id_lang']] = 'zakeke'; } } $tab->class_name = 'AdminZakeke'; $tab->module = $this->name; $tab->id_parent = -1; return (bool)$tab->add(); } /** * Associate a Zakeke guest to the store customer * * @param $params */ public function hookActionAuthentication($params) { try { /** @var Customer $customer */ $customer = $params['customer']; $zakekeGuestCode = $this->getZakekeGuestCode(false); if (!$zakekeGuestCode) { return; } $this->getZakekeApi()->associateGuest($zakekeGuestCode, $customer->id); } catch (Exception $e) { PrestaShopLogger::addLog('Zakeke hookActionAuthentication Call error: ' . $e); $this->context->controller->errors[] = $this->l('Zakeke failed to associate the guest user'); } } public function hookActionCartSave() { if ($this->cart_action_is_executing) { return; } $this->cart_action_is_executing = true; ZakekePrice::updateCartZakekePrice($this->context, $this); $this->cart_action_is_executing = false; } public function hookActionObjectProductInCartDeleteBefore($data) { if ($this->cart_action_is_executing) { return; } $this->cart_action_is_executing = true; ZakekePrice::updateCartZakekePrice($this->context, $this); $this->cart_action_is_executing = false; } public function installDB() { $return = true; $return &= Db::getInstance()->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'zakeke_enabled` ( `id_zakeke_enabled` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id_product` int(11) NOT NULL, PRIMARY KEY (`id_zakeke_enabled`), UNIQUE KEY `id_product` (`id_product`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;'); $return &= Db::getInstance()->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'zakeke_item` ( `id_zakeke_item` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id_item` varchar(254) NOT NULL, `preview` varchar(254) NOT NULL, `pricing_json` varchar(254) NOT NULL, PRIMARY KEY (`id_zakeke_item`), UNIQUE KEY `id_item` (`id_item`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;'); $return &= Db::getInstance()->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'zakeke_configurator_enabled` ( `id_zakeke_configurator_enabled` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id_product` int(11) NOT NULL, PRIMARY KEY (`id_zakeke_configurator_enabled`), UNIQUE KEY `id_product` (`id_product`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;'); $return &= Db::getInstance()->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'zakeke_configurator_item` ( `id_zakeke_configurator_item` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id_configurator_item` varchar(254) NOT NULL, `preview` varchar(254) NOT NULL, `price` DECIMAL(20,6) NOT NULL, `items_json` varchar(6000) NOT NULL, PRIMARY KEY (`id_zakeke_configurator_item`), UNIQUE KEY `id_item` (`id_configurator_item`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;'); return $return; } private function registerHooks() { $result = $this->registerHook('displayHeader') && $this->registerHook('displayOrderConfirmation') && $this->registerHook('actionCartSave') && $this->registerHook('actionAdminControllerSetMedia'); if (_PS_VERSION_ < 1.7) { return $result && $this->registerHook('displayMobileHeader') && $this->createAjaxController(); } return $result && $this->registerHook('addWebserviceResources') && $this->registerHook('displayCustomization') && $this->registerHook('displayCartExtraProductActions'); } public function install() { if (Shop::isFeatureActive()) { Shop::setContext(Shop::CONTEXT_ALL); } return parent::install() && $this->installDB() && $this->registerHooks(); } public function uninstall() { Configuration::deleteByName('ZAKEKE_FORCE'); $id_tab = (int)Tab::getIdFromClassName('AdminZakeke'); if ($id_tab) { $tab = new Tab($id_tab); $tab->delete(); } return parent::uninstall(); } }