diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index da3f66a..22363ea 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,7 +2,7 @@ name: PHP tests on: [push, pull_request] jobs: php-linter: - name: PHP Syntax check 5.6|7.2|7.3 + name: PHP Syntax check 5.4|5.6|7.2|7.4|8.0|8.4 runs-on: ubuntu-latest steps: - name: Checkout @@ -17,14 +17,20 @@ jobs: - name: PHP syntax checker 7.2 uses: prestashop/github-action-php-lint/7.2@master - - name: PHP syntax checker 7.3 - uses: prestashop/github-action-php-lint/7.3@master + - name: PHP syntax checker 7.4 + uses: prestashop/github-action-php-lint/7.4@master + + - name: PHP syntax checker 8.0 + uses: prestashop/github-action-php-lint/8.0@master + + - name: PHP syntax checker 8.4 + uses: prestashop/github-action-php-lint/8.4@master php-cs-fixer: name: PHP-CS-Fixer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: composer install diff --git a/cache.php b/cache.php deleted file mode 100644 index 93e3cc1..0000000 --- a/cache.php +++ /dev/null @@ -1,32 +0,0 @@ -delete('doofinder_landing'); - } - exit; -} diff --git a/config.php b/config.php deleted file mode 100644 index 57f3906..0000000 --- a/config.php +++ /dev/null @@ -1,103 +0,0 @@ -shop->getBaseURL(true, false) - . $module->getPathUri() . 'config.php'; - $tmpToken = Tools::encrypt($redirect); - if ($tmpToken == $autoinstallerToken) { - $apiToken = Tools::getValue('api_token'); - $api_endpoint = Tools::getValue('api_endpoint'); - $admin_endpoint = Tools::getValue('admin_endpoint'); - if ($apiToken) { - DoofinderConfig::saveApiConfig($apiToken, $api_endpoint, $admin_endpoint); - } - echo json_encode(['success' => true]); - exit; - } else { - header('HTTP/1.1 403 Forbidden', true, 403); - $msgError = 'Forbidden access.' - . ' Token for autoinstaller invalid.'; - exit($msgError); - } -} - -$languages = []; -$configurations = []; -$currencies = array_keys(DfTools::getAvailableCurrencies()); - -$display_prices = (bool) Configuration::get('DF_GS_DISPLAY_PRICES'); -$prices_with_taxes = (bool) Configuration::get('DF_GS_PRICES_USE_TAX'); - -foreach (Language::getLanguages(true, $context->shop->id) as $lang) { - $lang = Tools::strtoupper($lang['iso_code']); - $currency = DfTools::getCurrencyForLanguage($lang); - - $languages[] = $lang; - $configurations[$lang] = [ - 'language' => $lang, - 'currency' => Tools::strtoupper($currency->iso_code), - 'prices' => $display_prices, - 'taxes' => $prices_with_taxes, - ]; -} - -$force_ssl = (Configuration::get('PS_SSL_ENABLED') && Configuration::get('PS_SSL_ENABLED_EVERYWHERE')); -$shop = $context->shop; -$base = (($force_ssl) ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain); - -$cfg = [ - 'platform' => [ - 'name' => 'Prestashop', - 'version' => _PS_VERSION_, - ], - 'module' => [ - 'version' => DoofinderConstants::VERSION, - 'feed' => $base . $shop->getBaseURI() . 'modules/doofinder/feed.php', - 'options' => [ - 'language' => $languages, - 'currency' => $currencies, - ], - 'configuration' => $configurations, - ], -]; - -echo DfTools::jsonEncode($cfg); diff --git a/controllers/front/ajax.php b/controllers/front/ajax.php new file mode 100644 index 0000000..66cd64b --- /dev/null +++ b/controllers/front/ajax.php @@ -0,0 +1,56 @@ +ajax = 1; + + $checkApiKey = Tools::getValue('check_api_key'); + if ($checkApiKey) { + exit(DoofinderApi::checkApiKey(true)); + } + + $autoinstaller = Tools::getValue('autoinstaller'); + $shopId = Tools::getValue('shop_id', null); + if ($autoinstaller) { + if (Tools::getValue('token') == Tools::encrypt('doofinder-ajax')) { + header('Content-Type:application/json; charset=utf-8'); + DoofinderInstallation::autoinstaller($shopId); + $this->ajaxRender(json_encode(['success' => true])); + exit; + } else { + $this->ajaxRender(json_encode([ + 'success' => false, 'errors' => ['Forbidden access. Invalid token for autoinstaller.'], + ])); + exit; + } + } + } +} diff --git a/controllers/front/cache.php b/controllers/front/cache.php new file mode 100644 index 0000000..5289c40 --- /dev/null +++ b/controllers/front/cache.php @@ -0,0 +1,39 @@ +ajax = 1; + + if (Tools::isSubmit('landing')) { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + return Db::getInstance()->delete('doofinder_landing'); + } + exit; + } + } +} diff --git a/controllers/front/config.php b/controllers/front/config.php new file mode 100644 index 0000000..e4f505f --- /dev/null +++ b/controllers/front/config.php @@ -0,0 +1,109 @@ +ajax = 1; + + header('Content-Type:application/json; charset=utf-8'); + + $module = Module::getInstanceByName('doofinder'); + $autoinstallerToken = Tools::getValue('token'); + if ($autoinstallerToken) { + $redirect = Context::getContext()->shop->getBaseURL(true, false) + . $module->getPathUri() . 'config.php'; + $tmpToken = Tools::encrypt($redirect); + if ($tmpToken == $autoinstallerToken) { + $apiToken = Tools::getValue('api_token'); + $api_endpoint = Tools::getValue('api_endpoint'); + $admin_endpoint = Tools::getValue('admin_endpoint'); + if ($apiToken) { + DoofinderConfig::saveApiConfig($apiToken, $api_endpoint, $admin_endpoint); + } + echo json_encode(['success' => true]); + exit; + } else { + header('HTTP/1.1 403 Forbidden', true, 403); + $msgError = 'Forbidden access.' + . ' Token for autoinstaller invalid.'; + exit($msgError); + } + } + + $languages = []; + $configurations = []; + $currencies = array_keys(DfTools::getAvailableCurrencies()); + + $display_prices = (bool) Configuration::get('DF_GS_DISPLAY_PRICES'); + $prices_with_taxes = (bool) Configuration::get('DF_GS_PRICES_USE_TAX'); + + foreach (Language::getLanguages(true, $this->context->shop->id) as $lang) { + $lang = Tools::strtoupper($lang['iso_code']); + $currency = DfTools::getCurrencyForLanguage($lang); + + $languages[] = $lang; + $configurations[$lang] = [ + 'language' => $lang, + 'currency' => Tools::strtoupper($currency->iso_code), + 'prices' => $display_prices, + 'taxes' => $prices_with_taxes, + ]; + } + + $force_ssl = (Configuration::get('PS_SSL_ENABLED') && Configuration::get('PS_SSL_ENABLED_EVERYWHERE')); + $shop = $this->context->shop; + $base = (($force_ssl) ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain); + + $cfg = [ + 'platform' => [ + 'name' => 'Prestashop', + 'version' => _PS_VERSION_, + ], + 'module' => [ + 'version' => DoofinderConstants::VERSION, + 'feed' => $base . $shop->getBaseURI() . 'module/doofinder/feed', + 'options' => [ + 'language' => $languages, + 'currency' => $currencies, + ], + 'configuration' => $configurations, + ], + ]; + + if (method_exists($this, 'ajaxRender')) { + $this->ajaxRender(DfTools::jsonEncode($cfg)); + } else { + // Workaround for PS 1.6 as ajaxRender is not available + echo DfTools::jsonEncode($cfg); + exit; + } + } +} diff --git a/controllers/front/feed.php b/controllers/front/feed.php new file mode 100644 index 0000000..cde2d5e --- /dev/null +++ b/controllers/front/feed.php @@ -0,0 +1,60 @@ +ajax = 1; + + ob_start(); + switch (Tools::getValue('type')) { + case 'category': + require self::get_plugin_dir() . 'feeds/category.php'; + break; + + case 'page': + require self::get_plugin_dir() . 'feeds/cms.php'; + break; + + case 'product': + default: + require self::get_plugin_dir() . 'feeds/product.php'; + break; + } + $feed = ob_get_clean(); + header('Content-Type: text/csv; charset=utf-8'); + $this->ajaxRender($feed); + exit; + } + + private static function get_plugin_dir() + { + return _PS_MODULE_DIR_ . DIRECTORY_SEPARATOR . DoofinderConstants::NAME . DIRECTORY_SEPARATOR; + } +} diff --git a/controllers/front/landing.php b/controllers/front/landing.php index c3181e8..80172f1 100644 --- a/controllers/front/landing.php +++ b/controllers/front/landing.php @@ -13,12 +13,8 @@ * @license GPLv3 */ -use PrestaShop\Module\Doofinder\Src\Entity\DoofinderApiLanding; -use PrestaShop\Module\Doofinder\Src\Entity\SearchEngine; -use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever; -use PrestaShop\PrestaShop\Adapter\Product\PriceFormatter; -use PrestaShop\PrestaShop\Adapter\Product\ProductColorsRetriever; -use PrestaShop\PrestaShop\Core\Product\ProductListingPresenter; +use PrestaShop\Module\Doofinder\Src\Entity\DoofinderConfig; +use PrestaShop\Module\Doofinder\Src\Entity\LanguageManager; if (!defined('_PS_VERSION_')) { exit; @@ -26,45 +22,6 @@ class DoofinderLandingModuleFrontController extends ModuleFrontController { - public $products = []; - public $landing_data = []; - private $display_column_right; - private $display_column_left; - - const RESULTS = 48; - const TTL_CACHE = 30; - - /** - * Initialize landing controller. - * - * @see FrontController::init() - */ - public function init() - { - parent::init(); - - $landing_name = Tools::getValue('landing_name'); - - $this->landing_data = $this->getLandingData($landing_name, $this->context->shop->id, $this->context->language->id, $this->context->currency->id); - - if (!$this->landing_data) { - Tools::redirect('index.php?controller=404'); - } - - $hashid = SearchEngine::getHashId($this->context->language->id, $this->context->currency->id); - $apiKey = Configuration::get('DF_API_KEY'); - $region = Configuration::get('DF_REGION'); - $dfApiLanding = new DoofinderApiLanding($hashid, $apiKey, $region); - - foreach ($this->landing_data['blocks'] as &$block) { - if ($productsResult = $dfApiLanding->searchOnApi($block['query'], $this->module, 1, self::RESULTS)) { - $block['products'] = $productsResult['result']; - } else { - $block['products'] = []; - } - } - } - /** * Assign template vars related to page content. * @@ -72,174 +29,30 @@ public function init() */ public function initContent() { - $this->display_column_right = false; - $this->display_column_left = false; - - parent::initContent(); + $hashid = Tools::getValue('hashid'); + $slug = Tools::getValue('slug'); - if (version_compare(_PS_VERSION_, '1.7', '>=')) { - $this->renderProductList(); - } else { - $this->renderProductList16(); - } - } - - private function renderProductList() - { - $assembler = new ProductAssembler($this->context); - $presenterFactory = new ProductPresenterFactory($this->context); - $presentationSettings = $presenterFactory->getPresentationSettings(); - $presenter = new ProductListingPresenter( - new ImageRetriever( - $this->context->link - ), - $this->context->link, - new PriceFormatter(), - new ProductColorsRetriever(), - $this->context->getTranslator() - ); - - foreach ($this->landing_data['blocks'] as &$block) { - $products = []; - foreach ($block['products'] as $productDetail) { - $products[] = $presenter->present( - $presentationSettings, - $assembler->assembleProduct($productDetail), - $this->context->language - ); - } - $block['products'] = $products; + if (!$hashid || !$slug) { + DoofinderConfig::debug('[Landing][Warning] Hashid and/or slug could not be retrieved: ' . PHP_EOL . '- slug: ' . DoofinderConfig::dump($slug) . '- hashid: ' . DoofinderConfig::dump($hashid)); + Tools::redirect('index.php?controller=404'); + exit; } - $this->context->smarty->assign( - [ - 'blocks' => $this->landing_data['blocks'], - 'title' => $this->landing_data['title'], - ] - ); - - $this->setTemplate('module:doofinder/views/templates/front/landing.tpl'); - } - - private function renderProductList16() - { - $this->context->smarty->assign( - [ - 'blocks' => $this->landing_data['blocks'], - 'title' => $this->landing_data['title'], - 'meta_title' => $this->landing_data['meta_title'], - 'meta_description' => $this->landing_data['meta_description'], - 'nobots' => $this->landing_data['index'] ? false : true, - ] - ); - - $this->addCSS([ - _THEME_CSS_DIR_ . 'category.css' => 'all', - _THEME_CSS_DIR_ . 'product_list.css' => 'all', - ]); - - $this->setTemplate('landing16.tpl'); - } + $idLang = LanguageManager::getLanguageByHashid($hashid); - /** - * Assign meta tags - * - * @return array - */ - public function getTemplateVarPage() - { - $page = parent::getTemplateVarPage(); - - $page['meta']['title'] = $this->landing_data['meta_title']; - $page['meta']['description'] = $this->landing_data['meta_description']; - $page['meta']['robots'] = $this->landing_data['index'] ? 'index' : 'noindex'; - - return $page; - } - - private function getLandingData($name, $id_shop, $id_lang, $id_currency) - { - $hashid = SearchEngine::getHashId($id_lang, $id_currency); - $cache = $this->getLandingCache($name, $hashid); - - if ($cache && !$this->refreshCache($cache)) { - return json_decode(base64_decode($cache['data']), true); - } else { - $response = $this->getApiCall($name, $hashid); - - if (!$response) { - $this->setLandingCache($name, $hashid, null); - - return false; - } - - $data = [ - 'title' => $response['title'], - 'meta_title' => $response['meta_title'], - 'meta_description' => $response['meta_description'], - 'index' => $response['index'], - ]; - - if (is_array($response['blocks']) && count($response['blocks']) > 0) { - $data['blocks'] = []; - foreach ($response['blocks'] as $block) { - $data['blocks'][] = [ - 'above' => base64_decode($block['above']), - 'below' => base64_decode($block['below']), - 'position' => $block['position'], - 'query' => $block['query'], - ]; - } - } - - $this->setLandingCache($name, $hashid, base64_encode(json_encode($data))); - - return $data; + if (!$idLang) { + DoofinderConfig::debug('[Landing][Warning] Invalid Language ID: ' . DoofinderConfig::dump($idLang)); + Tools::redirect('index.php?controller=404'); + exit; } - } - - private function getApiCall($name, $hashid) - { - $apiKey = explode('-', Configuration::get('DF_API_KEY')); - $apiKey = end($apiKey); - $region = Configuration::get('DF_REGION'); - - $api = new DoofinderApiLanding($hashid, $apiKey, $region); - - return $api->getLanding($name); - } - - private function setLandingCache($name, $hashid, $data) - { - return Db::getInstance()->insert( - 'doofinder_landing', - [ - 'name' => pSQL($name), - 'hashid' => pSQL($hashid), - 'data' => $data, - 'date_upd' => date('Y-m-d H:i:s'), - ], - false, - true, - Db::REPLACE - ); - } - private function getLandingCache($name, $hashid) - { - return Db::getInstance()->getRow( - ' - SELECT * FROM ' . _DB_PREFIX_ . "doofinder_landing - WHERE name = '" . pSQL($name) . "' AND hashid = '" . pSQL($hashid) . "'" + $link = $this->context->link->getPageLink( + 'module-doofinder-landingpage', + null, + $idLang, + ['landing_name' => $slug] ); - } - - private function refreshCache($row) - { - $last_exec_ts = strtotime($row['date_upd']); - - $diff_min = (time() - $last_exec_ts) / 60; - return $diff_min > self::TTL_CACHE; + Tools::redirect($link); } } diff --git a/controllers/front/landingPage.php b/controllers/front/landingPage.php new file mode 100644 index 0000000..79a7808 --- /dev/null +++ b/controllers/front/landingPage.php @@ -0,0 +1,245 @@ +landing_data = $this->getLandingData($landing_name, $this->context->shop->id, $this->context->language->id, $this->context->currency->id); + + if (!$this->landing_data) { + Tools::redirect('index.php?controller=404'); + } + + $hashid = SearchEngine::getHashId($this->context->language->id, $this->context->currency->id); + $apiKey = Configuration::get('DF_API_KEY'); + $region = Configuration::get('DF_REGION'); + $dfApiLanding = new DoofinderApiLanding($hashid, $apiKey, $region); + + foreach ($this->landing_data['blocks'] as &$block) { + if ($productsResult = $dfApiLanding->searchOnApi($block['query'], $this->module, 1, self::RESULTS)) { + $block['products'] = $productsResult['result']; + } else { + $block['products'] = []; + } + } + } + + /** + * Assign template vars related to page content. + * + * @see FrontController::initContent() + */ + public function initContent() + { + $this->display_column_right = false; + $this->display_column_left = false; + + parent::initContent(); + + if (version_compare(_PS_VERSION_, '1.7', '>=')) { + $this->renderProductList(); + } else { + $this->renderProductList16(); + } + } + + private function renderProductList() + { + $assembler = new ProductAssembler($this->context); + $presenterFactory = new ProductPresenterFactory($this->context); + $presentationSettings = $presenterFactory->getPresentationSettings(); + $presenter = new ProductListingPresenter( + new ImageRetriever( + $this->context->link + ), + $this->context->link, + new PriceFormatter(), + new ProductColorsRetriever(), + $this->context->getTranslator() + ); + + foreach ($this->landing_data['blocks'] as &$block) { + $products = []; + foreach ($block['products'] as $productDetail) { + $products[] = $presenter->present( + $presentationSettings, + $assembler->assembleProduct($productDetail), + $this->context->language + ); + } + $block['products'] = $products; + } + + $this->context->smarty->assign( + [ + 'blocks' => $this->landing_data['blocks'], + 'title' => $this->landing_data['title'], + ] + ); + + $this->setTemplate('module:doofinder/views/templates/front/landing.tpl'); + } + + private function renderProductList16() + { + $this->context->smarty->assign( + [ + 'blocks' => $this->landing_data['blocks'], + 'title' => $this->landing_data['title'], + 'meta_title' => $this->landing_data['meta_title'], + 'meta_description' => $this->landing_data['meta_description'], + 'nobots' => $this->landing_data['index'] ? false : true, + ] + ); + + $this->addCSS([ + _THEME_CSS_DIR_ . 'category.css' => 'all', + _THEME_CSS_DIR_ . 'product_list.css' => 'all', + ]); + + $this->setTemplate('landing16.tpl'); + } + + /** + * Assign meta tags + * + * @return array + */ + public function getTemplateVarPage() + { + $page = parent::getTemplateVarPage(); + + $page['meta']['title'] = $this->landing_data['meta_title']; + $page['meta']['description'] = $this->landing_data['meta_description']; + $page['meta']['robots'] = $this->landing_data['index'] ? 'index' : 'noindex'; + + return $page; + } + + private function getLandingData($name, $id_shop, $id_lang, $id_currency) + { + $hashid = SearchEngine::getHashId($id_lang, $id_currency); + $cache = $this->getLandingCache($name, $hashid); + + if ($cache && !$this->refreshCache($cache)) { + return json_decode(base64_decode($cache['data']), true); + } else { + $response = $this->getApiCall($name, $hashid); + + if (!$response) { + $this->setLandingCache($name, $hashid, null); + + return false; + } + + $data = [ + 'title' => $response['title'], + 'meta_title' => $response['meta_title'], + 'meta_description' => $response['meta_description'], + 'index' => $response['index'], + ]; + + if (is_array($response['blocks']) && count($response['blocks']) > 0) { + $data['blocks'] = []; + foreach ($response['blocks'] as $block) { + $data['blocks'][] = [ + 'above' => base64_decode($block['above']), + 'below' => base64_decode($block['below']), + 'position' => $block['position'], + 'query' => $block['query'], + ]; + } + } + + $this->setLandingCache($name, $hashid, base64_encode(json_encode($data))); + + return $data; + } + } + + private function getApiCall($name, $hashid) + { + $apiKey = explode('-', Configuration::get('DF_API_KEY')); + $apiKey = end($apiKey); + $region = Configuration::get('DF_REGION'); + + $api = new DoofinderApiLanding($hashid, $apiKey, $region); + + return $api->getLanding($name); + } + + private function setLandingCache($name, $hashid, $data) + { + return Db::getInstance()->insert( + 'doofinder_landing', + [ + 'name' => pSQL($name), + 'hashid' => pSQL($hashid), + 'data' => $data, + 'date_upd' => date('Y-m-d H:i:s'), + ], + false, + true, + Db::REPLACE + ); + } + + private function getLandingCache($name, $hashid) + { + return Db::getInstance()->getRow( + ' + SELECT * FROM ' . _DB_PREFIX_ . "doofinder_landing + WHERE name = '" . pSQL($name) . "' AND hashid = '" . pSQL($hashid) . "'" + ); + } + + private function refreshCache($row) + { + $last_exec_ts = strtotime($row['date_upd']); + + $diff_min = (time() - $last_exec_ts) / 60; + + return $diff_min > self::TTL_CACHE; + } +} diff --git a/doofinder-ajax.php b/doofinder-ajax.php deleted file mode 100644 index 44f7d95..0000000 --- a/doofinder-ajax.php +++ /dev/null @@ -1,53 +0,0 @@ - true]); - exit; - } else { - $msgError = 'Forbidden access.' - . ' Token for autoinstaller invalid.'; - exit($msgError); - } -} diff --git a/doofinder.php b/doofinder.php index 2cbd7a3..b399930 100644 --- a/doofinder.php +++ b/doofinder.php @@ -16,7 +16,7 @@ exit; } -require_once 'autoloader.php'; +require_once 'src/autoloader.php'; /* We cannot use the `use` statement in the main module file due to a eval function @@ -39,7 +39,7 @@ public function __construct() { $this->name = 'doofinder'; $this->tab = 'search_filter'; - $this->version = '4.11.0'; + $this->version = '4.12.0'; $this->author = 'Doofinder (http://www.doofinder.com)'; $this->ps_versions_compliancy = ['min' => '1.5', 'max' => _PS_VERSION_]; $this->module_key = 'd1504fe6432199c7f56829be4bd16347'; diff --git a/feed.php b/feed.php deleted file mode 100644 index e54526d..0000000 --- a/feed.php +++ /dev/null @@ -1,40 +0,0 @@ -link->getModuleLink( - DoofinderConstants::NAME, - 'landing', - ['landing_name' => $slug], - null, - $idLang -); - -Tools::redirect($link); diff --git a/src/Entity/DfTools.php b/src/Entity/DfTools.php index e4e14b6..2d67aa4 100644 --- a/src/Entity/DfTools.php +++ b/src/Entity/DfTools.php @@ -1232,7 +1232,7 @@ public static function getImageLink($idProduct, $idImage, $linkRewrite, $imageSi public static function getFeedURL($langIsoCode) { $currency = self::getCurrencyForLanguage($langIsoCode); - $feedUrl = self::getModuleLink('feed.php') . '?language=' . strtoupper($langIsoCode); + $feedUrl = self::getModuleLink('feed') . '?language=' . strtoupper($langIsoCode); $feedUrl .= '¤cy=' . strtoupper($currency->iso_code); return $feedUrl; diff --git a/src/Entity/DoofinderConfig.php b/src/Entity/DoofinderConfig.php index a631edd..f94862d 100644 --- a/src/Entity/DoofinderConfig.php +++ b/src/Entity/DoofinderConfig.php @@ -29,6 +29,7 @@ public static function debug($message, $logFile = 'doofinder.log') $debug = \Configuration::get('DF_DEBUG'); if (!empty($debug) && $debug) { + $message = is_string($message) ? $message : print_r($message, true); error_log("$message\n", 3, _PS_MODULE_DIR_ . DIRECTORY_SEPARATOR . 'doofinder' . DIRECTORY_SEPARATOR . $logFile); } } diff --git a/src/Entity/DoofinderConstants.php b/src/Entity/DoofinderConstants.php index 7db091f..4567b72 100644 --- a/src/Entity/DoofinderConstants.php +++ b/src/Entity/DoofinderConstants.php @@ -27,7 +27,7 @@ class DoofinderConstants const DOOPHOENIX_REGION_URL = 'https://%ssearch.doofinder.com'; const GS_SHORT_DESCRIPTION = 1; const GS_LONG_DESCRIPTION = 2; - const VERSION = '4.11.0'; + const VERSION = '4.12.0'; const NAME = 'doofinder'; const YES = 1; const NO = 0; diff --git a/src/Entity/DoofinderInstallation.php b/src/Entity/DoofinderInstallation.php index 3201594..00f331c 100644 --- a/src/Entity/DoofinderInstallation.php +++ b/src/Entity/DoofinderInstallation.php @@ -155,7 +155,7 @@ private static function _createStore($shop) 'language' => $langCode, 'currency' => $primaryCurrency->iso_code, 'feed_url' => $feedUrl, - 'callback_url' => UrlManager::getProcessCallbackUrl(), + 'callback_url' => UrlManager::getProcessCallbackUrl($shopId), ]; } @@ -171,7 +171,7 @@ private static function _createStore($shop) $createStoreRequest = ['store_data' => $storeData, 'prices' => $currencyCodes]; $jsonCreateStoreRequest = json_encode($createStoreRequest); - DoofinderConfig::debug('Create Store Start'); + DoofinderConfig::debug("Create Store Start for shop with id: $shopId , and group: $shopGroupId."); DoofinderConfig::debug(print_r($createStoreRequest, true)); $response = $client->post( @@ -208,6 +208,84 @@ private static function _createStore($shop) } } + /** + * Sends a request to the plugins API to update feed URLs to the new format + * + * @return void + * + * @throws \Exception If the request fails + */ + public static function updateFeedUrls() + { + $shops = \Shop::getShops(); + + DoofinderConfig::debug('SHOPS:'); + DoofinderConfig::debug(print_r($shops, true)); + + foreach ($shops as $shop) { + $feed_urls = []; + $client = new EasyREST(); + $apiKey = \Configuration::getGlobalValue('DF_AI_APIKEY'); + $languages = \Language::getLanguages(true, $shop['id_shop']); + $currencies = \Currency::getCurrenciesByIdShop($shop['id_shop']); + $shopId = $shop['id_shop']; + $shopGroupId = $shop['id_shop_group']; + $installationID = \Configuration::get('DF_INSTALLATION_ID', null, $shopGroupId, $shopId); + DoofinderConfig::debug("Updating feed urls for shop: {$shopId} and group: {$shopGroupId}"); + + foreach ($languages as $lang) { + if ($lang['active'] == 0) { + continue; + } + foreach ($currencies as $cur) { + if ($cur['deleted'] == 1 || $cur['active'] == 0) { + continue; + } + $ciso = $cur['iso_code']; + $langFullIso = $lang['iso_code']; + $feedUrl = UrlManager::getFeedUrl($shopId, $langFullIso, $ciso); + $hashidKey = 'DF_HASHID_' . strtoupper($ciso) . '_' . strtoupper($langFullIso); + $hashid = \Configuration::get($hashidKey, null, $shopGroupId, $shopId); + + DoofinderConfig::debug("Hashid for lang $langFullIso and currency $ciso : $hashid"); + + $feed_urls[$hashid] = $feedUrl; + } + } + + $json_feed_urls = json_encode([ + 'installation_id' => $installationID, + 'urls' => $feed_urls, + ]); + + DoofinderConfig::debug('Update feed urls Start'); + DoofinderConfig::debug(print_r($json_feed_urls, true)); + + $response = $client->post( + UrlManager::getUpdateFeedUrl(\Configuration::get('DF_REGION')), + $json_feed_urls, + false, + false, + 'application/json', + ['Authorization: Token ' . $apiKey] + ); + + if ($response->getResponseCode() === 200) { + $response = json_decode($response->response, true); + DoofinderConfig::debug('Update feed urls response:'); + DoofinderConfig::debug(print_r($response, true)); + } else { + $errorMsg = "Update feed urls failed with code {$response->getResponseCode()} and message '{$response->getResponseMessage()}'"; + $decodedResponse = json_decode($response->response); + DoofinderConfig::debug($errorMsg); + DoofinderConfig::debug($decodedResponse); + error_log('[Doofinder] An error occurred when updating feed urls.'); + + throw new \Exception('An error occurred when updating feed urls.', $response->getResponseCode()); + } + } + } + public static function installTabs() { $tab = new \Tab(); diff --git a/src/Entity/HookManager.php b/src/Entity/HookManager.php index 62ffc4a..f28b5f0 100644 --- a/src/Entity/HookManager.php +++ b/src/Entity/HookManager.php @@ -36,15 +36,15 @@ public function __construct($module) public function registerHooks() { return $this->module->registerHook('displayHeader') - && $this->module->registerHook('moduleRoutes') - && $this->module->registerHook('actionProductSave') - && $this->module->registerHook('actionProductDelete') - && $this->module->registerHook('actionObjectCmsAddAfter') - && $this->module->registerHook('actionObjectCmsUpdateAfter') - && $this->module->registerHook('actionObjectCmsDeleteAfter') - && $this->module->registerHook('actionObjectCategoryAddAfter') - && $this->module->registerHook('actionObjectCategoryUpdateAfter') - && $this->module->registerHook('actionObjectCategoryDeleteAfter'); + && $this->module->registerHook('moduleRoutes') + && $this->module->registerHook('actionProductSave') + && $this->module->registerHook('actionProductDelete') + && $this->module->registerHook('actionObjectCmsAddAfter') + && $this->module->registerHook('actionObjectCmsUpdateAfter') + && $this->module->registerHook('actionObjectCmsDeleteAfter') + && $this->module->registerHook('actionObjectCategoryAddAfter') + && $this->module->registerHook('actionObjectCategoryUpdateAfter') + && $this->module->registerHook('actionObjectCategoryDeleteAfter'); } /** @@ -100,6 +100,16 @@ public static function getHookModuleRoutes() return [ 'module-doofinder-landing' => [ 'controller' => 'landing', + 'rule' => 'module/doofinder/landing', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'landing', + ], + ], + 'module-doofinder-landingpage' => [ + 'controller' => 'landingPage', 'rule' => 'df/{landing_name}', 'keywords' => [ 'landing_name' => ['regexp' => '[_a-zA-Z0-9_-]+', 'param' => 'landing_name'], @@ -107,7 +117,57 @@ public static function getHookModuleRoutes() 'params' => [ 'fc' => 'module', 'module' => 'doofinder', - 'controller' => 'landing', + 'controller' => 'landingPage', + ], + ], + 'module-doofinder-cache' => [ + 'controller' => 'cache', + 'rule' => 'module/doofinder/cache', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'cache', + ], + ], + 'module-doofinder-config' => [ + 'controller' => 'config', + 'rule' => 'module/doofinder/config', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'config', + ], + ], + 'module-doofinder-ajax' => [ + 'controller' => 'ajax', + 'rule' => 'module/doofinder/ajax', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'ajax', + ], + ], + 'module-doofinder-feed' => [ + 'controller' => 'feed', + 'rule' => 'module/doofinder/feed', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'feed', + ], + ], + 'module-doofinder-callback' => [ + 'controller' => 'callback', + 'rule' => 'module/doofinder/callback', + 'keywords' => [], + 'params' => [ + 'fc' => 'module', + 'module' => 'doofinder', + 'controller' => 'callback', ], ], ]; diff --git a/src/Entity/SearchEngine.php b/src/Entity/SearchEngine.php index 8750fdd..23ceafd 100644 --- a/src/Entity/SearchEngine.php +++ b/src/Entity/SearchEngine.php @@ -31,11 +31,21 @@ class SearchEngine */ public static function getHashId($idLang, $idCurrency) { + $context = \Context::getContext(); $currIso = strtoupper(LanguageManager::getIsoCodeById($idCurrency)); $lang = new \Language($idLang); - $hashidKey = 'DF_HASHID_' . $currIso . '_' . strtoupper($lang->language_code); - $hashid = \Configuration::get($hashidKey); + $hashid = \Configuration::get($hashidKey, $idLang, $context->shop->id_shop_group, $context->shop->id); + + if (!$hashid) { + // If not found, try to obtain hashid without context + $hashid = \Configuration::get($hashidKey, $idLang); + } + + if (!$hashid) { + // If not found, try to obtain hashid without idLang + $hashid = \Configuration::get($hashidKey); + } if (!$hashid) { $hashidKey = 'DF_HASHID_' . $currIso . '_' . strtoupper(LanguageManager::getLanguageCode($lang->language_code)); @@ -52,6 +62,7 @@ public static function getHashId($idLang, $idCurrency) */ public static function setSearchEnginesByConfig() { + $context = \Context::getContext(); $installationID = \Configuration::get('DF_INSTALLATION_ID'); $apiKey = \Configuration::get('DF_API_KEY'); $region = \Configuration::get('DF_REGION'); @@ -60,7 +71,8 @@ public static function setSearchEnginesByConfig() foreach ($data['config']['search_engines'] as $lang => $currencies) { foreach ($currencies as $currency => $hashid) { - \Configuration::updateValue('DF_HASHID_' . strtoupper($currency) . '_' . strtoupper($lang), $hashid); + $hashidKey = 'DF_HASHID_' . strtoupper($currency) . '_' . strtoupper($lang); + \Configuration::updateValue($hashidKey, $hashid, false, $context->shop->id_shop_group, $context->shop->id); } } diff --git a/src/Entity/UpdateOnSave.php b/src/Entity/UpdateOnSave.php index 34f329d..387f53b 100644 --- a/src/Entity/UpdateOnSave.php +++ b/src/Entity/UpdateOnSave.php @@ -326,10 +326,14 @@ private static function deleteItemsApi($hashid, $type, $payload) */ public static function indexApiInvokeReindexing() { + $context = \Context::getContext(); $region = \Configuration::get('DF_REGION'); $apiKey = \Configuration::get('DF_API_KEY'); $api = new DoofinderApiIndex($apiKey, $region); - $response = $api->invokeReindexing(\Configuration::get('DF_INSTALLATION_ID'), UrlManager::getProcessCallbackUrl()); + $installationId = \Configuration::get('DF_INSTALLATION_ID', null, $context->shop->id_shop_group, $context->shop->id); + + DoofinderConfig::debug("Invoking reindexing for shop: {$context->shop->id} and group: {$context->shop->id_shop_group} with installation id: {$installationId}"); + $response = $api->invokeReindexing($installationId, UrlManager::getProcessCallbackUrl($context->shop->id)); if (empty($response) || 200 !== $response['status']) { DoofinderConfig::debug('Error while invoking reindexing: ' . json_encode($response)); @@ -352,7 +356,9 @@ public static function isValid() $region = \Configuration::get('DF_REGION'); $apiKey = \Configuration::get('DF_API_KEY'); $api = new DoofinderInstallation($apiKey, $region); - $decodeResponse = $api->isValidUpdateOnSave(\Configuration::get('DF_INSTALLATION_ID')); + $context = \Context::getContext(); + $installationId = \Configuration::get('DF_INSTALLATION_ID', null, $context->shop->id_shop_group, $context->shop->id); + $decodeResponse = $api->isValidUpdateOnSave($installationId); if (empty($decodeResponse['valid?'])) { DoofinderConfig::debug('Error checking search engines: ' . json_encode($decodeResponse)); diff --git a/src/Entity/UrlManager.php b/src/Entity/UrlManager.php index 2565f41..e2f00a2 100644 --- a/src/Entity/UrlManager.php +++ b/src/Entity/UrlManager.php @@ -48,13 +48,19 @@ public static function getShopURL($shop_id) */ public static function getFeedUrl($shopId, $language, $currency = null) { - $shopUrl = self::getShopURL($shopId); + $shop = new \Shop($shopId); + \Context::getContext()->shop = $shop; - return $shopUrl . ltrim('modules/' . DoofinderConstants::NAME, DIRECTORY_SEPARATOR) - . '/feed.php?' - . ($currency ? 'currency=' . $currency : '') - . 'language=' . \Tools::strtoupper($language) - . '&dfsec_hash=' . \Configuration::get('DF_API_KEY'); + $params = [ + 'language' => \Tools::strtoupper($language), + 'dfsec_hash' => \Configuration::get('DF_API_KEY'), + ]; + + if ($currency) { + $params['currency'] = \Tools::strtoupper($currency); + } + + return \Context::getContext()->link->getModuleLink(DoofinderConstants::NAME, 'feed', $params); } /** @@ -62,9 +68,12 @@ public static function getFeedUrl($shopId, $language, $currency = null) * * @return string */ - public static function getProcessCallbackUrl() + public static function getProcessCallbackUrl($shopId) { - return \Context::getContext()->link->getModuleLink('doofinder', 'callback', []); + $shop = new \Shop($shopId); + \Context::getContext()->shop = $shop; + + return \Context::getContext()->link->getModuleLink(DoofinderConstants::NAME, 'callback', []); } public static function getInstallUrl($region) @@ -72,6 +81,11 @@ public static function getInstallUrl($region) return self::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region, '/install'); } + public static function getUpdateFeedUrl($region) + { + return self::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region, '/prestashop/feed-url-update'); + } + /** * Gets an URL with its region filled in. You can also append a path (optional). * If the region is provided as '' it will return a regionless URL. diff --git a/autoloader.php b/src/autoloader.php similarity index 98% rename from autoloader.php rename to src/autoloader.php index 85c42c9..8487eaf 100644 --- a/autoloader.php +++ b/src/autoloader.php @@ -12,6 +12,9 @@ * @copyright Doofinder * @license GPLv3 */ + +namespace PrestaShop\Module\Doofinder\Src; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/upgrade/upgrade-4.12.0.php b/upgrade/upgrade-4.12.0.php new file mode 100644 index 0000000..0dbdbaa --- /dev/null +++ b/upgrade/upgrade-4.12.0.php @@ -0,0 +1,113 @@ + + * @copyright 2007-2022 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +use PrestaShop\Module\Doofinder\Src\Entity\DoofinderConfig; +use PrestaShop\Module\Doofinder\Src\Entity\DoofinderInstallation; + +if (!defined('_PS_VERSION_')) { + exit; +} + +require_once _PS_MODULE_DIR_ . 'doofinder/src/autoloader.php'; + +function upgrade_module_4_12_0($module) +{ + DoofinderConfig::debug('Initiating 4.12.0 upgrade'); + // Delete old *.php files + unlinkFiles(); + DoofinderConfig::debug('Old files deleted successfully.'); + + // Update feed URLs + DoofinderInstallation::updateFeedUrls(); + + DoofinderConfig::debug('Feed URLs updated successfully.'); + + return true; +} + +function unlinkFiles() +{ + $files = [ + 'autoloader.php', + 'cache.php', + 'config.php', + 'doofinder-ajax.php', + 'feed.php', + 'landing.php', + 'controllers/front/landingEntrypoint.php', + /* Files from older versions. */ + 'lib/doofinder_api.php', + 'lib/doofinder_api_landing.php', + 'views/templates/front/script.tpl', + 'lib/doofinder_api_index.php', + 'lib/doofinder_api_items.php', + 'lib/doofinder_layer_api.php', + 'lib/doofinder_api_unique_script.php', + 'lib/doofinder_installation.php', + 'lib/dfTools.class.php', + 'lib/dfCategory_build.php', + 'lib/dfCms_build.php', + 'lib/dfProduct_build.php', + 'lib/DfCategoryBuild.php', + 'lib/DfCmsBuild.php', + 'lib/DfProductBuild.php', + 'lib/DfTools.php', + 'lib/DoofinderAdminPanelView.php', + 'lib/DoofinderApi.php', + 'lib/DoofinderApiIndex.php', + 'lib/DoofinderApiItems.php', + 'lib/DoofinderApiLanding.php', + 'lib/DoofinderApiUniqueScript.php', + 'lib/DoofinderConfig.php', + 'lib/DoofinderConstants.php', + 'lib/DoofinderException.php', + 'lib/DoofinderInstallation.php', + 'lib/DoofinderLayerApi.php', + 'lib/DoofinderResults.php', + 'lib/DoofinderScript.php', + 'lib/EasyREST.php', + 'lib/FormManager.php', + 'lib/HookManager.php', + 'lib/LanguageManager.php', + 'lib/SearchEngine.php', + 'lib/UpdateOnSave.php', + 'lib/UrlManager.php', + ]; + + foreach ($files as $fileName) { + $filePath = _PS_MODULE_DIR_ . 'doofinder' . DIRECTORY_SEPARATOR . $fileName; + if (!file_exists($filePath)) { + continue; + } + + if (!unlink($filePath)) { + error_log('Couldn\'t delete file: ' . $filePath); + throw new Exception('Error when deleting file: ' . $fileName); + } + + error_log('Deleted file: ' . $filePath); + } +} diff --git a/views/js/doofinder-onboarding.js b/views/js/doofinder-onboarding.js index 4467cc4..fed9e10 100644 --- a/views/js/doofinder-onboarding.js +++ b/views/js/doofinder-onboarding.js @@ -72,7 +72,7 @@ function launchAutoinstaller() { post_data["shop_id"] = shop_id; } - $.post(shopDomain + df_module_dir + "doofinder-ajax.php", post_data, function (data) { + $.post(shopDomain + "/module/doofinder/ajax", post_data, function (data) { if (data.success) { //reload without resending post data history.go(0); @@ -140,7 +140,7 @@ function send_connect_data(data) { $.ajax({ type: "POST", dataType: "json", - url: shopDomain + df_module_dir + "config.php", + url: shopDomain + "/module/doofinder/config", data: data, success: function (response) { if (response.success) {