├── logo.gif ├── logo.png ├── views ├── img │ ├── cogs.gif │ ├── edit.gif │ ├── cross.png │ ├── icon │ │ ├── less.gif │ │ ├── more.gif │ │ └── index.php │ ├── doofinder_logo.png │ ├── doofinder_mac.png │ ├── bg_search_input.png │ ├── bg_search_submit.png │ ├── template_1_cards.png │ ├── template_1_logo.png │ ├── template_2_cards.png │ ├── template_2_logo.png │ └── index.php ├── templates │ ├── admin │ │ ├── dummy │ │ │ ├── after_tab.tpl │ │ │ ├── pre_tab.tpl │ │ │ └── index.php │ │ ├── message_manage_one_shop.tpl │ │ ├── configure_footer.tpl │ │ ├── reindex.tpl │ │ ├── index.php │ │ ├── display_msg.tpl │ │ ├── configure_administration_panel.tpl │ │ ├── configure.tpl │ │ ├── support_tab.tpl │ │ ├── indexation_status.tpl │ │ └── onboarding_tab.tpl │ ├── index.php │ └── front │ │ ├── index.php │ │ ├── catalog │ │ ├── index.php │ │ └── _partials │ │ │ ├── index.php │ │ │ └── productlist.tpl │ │ └── scriptV9.tpl ├── css │ ├── admin-panel.css │ └── index.php ├── index.php └── js │ ├── index.php │ ├── plugins │ └── index.php │ ├── add-to-cart │ ├── index.php │ ├── doofinder-add_to_cart_ps16.js │ └── doofinder-add_to_cart_ps17.js │ ├── admin-panel.js │ └── doofinder-onboarding.js ├── vendor ├── composer │ ├── installed.json │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── installed.php │ ├── LICENSE │ ├── autoload_real.php │ ├── autoload_classmap.php │ └── autoload_static.php └── autoload.php ├── .htaccess ├── src ├── Exception │ ├── DoofinderException.php │ └── index.php ├── index.php ├── Api │ ├── index.php │ ├── DoofinderLayerApi.php │ ├── DoofinderApiSingleScript.php │ ├── DoofinderApiIndex.php │ └── DoofinderApiItems.php ├── Core │ ├── index.php │ ├── DoofinderConstants.php │ ├── DoofinderScript.php │ └── SearchEngine.php ├── Feed │ ├── index.php │ ├── DfCmsBuild.php │ └── DfCategoryBuild.php ├── Manager │ ├── index.php │ ├── LanguageManager.php │ ├── UrlManager.php │ ├── HookManager.php │ └── FormManager.php ├── Utils │ ├── index.php │ └── DfDb.php ├── View │ └── index.php ├── Installer │ └── index.php ├── Configuration │ └── index.php └── Autoloader.php ├── index.php ├── feeds ├── index.php ├── category.php ├── cms.php └── product.php ├── controllers ├── index.php ├── admin │ ├── index.php │ └── DoofinderAdminController.php └── front │ ├── index.php │ ├── callback.php │ ├── ajax.php │ ├── feed.php │ └── config.php ├── translations └── index.php ├── phpdoc.dist.xml ├── upgrade ├── index.php ├── upgrade-4.6.1.php ├── upgrade-4.3.1.php ├── upgrade-7.0.0.php ├── upgrade-4.8.2.php ├── upgrade-4.4.8.php ├── upgrade-4.5.0.php ├── upgrade-5.1.4.php ├── upgrade-4.2.0.php ├── upgrade-4.6.4.php ├── upgrade-6.0.0.php └── upgrade-4.12.0.php └── README.md /logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/logo.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/logo.png -------------------------------------------------------------------------------- /views/img/cogs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/cogs.gif -------------------------------------------------------------------------------- /views/img/edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/edit.gif -------------------------------------------------------------------------------- /views/img/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/cross.png -------------------------------------------------------------------------------- /views/img/icon/less.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/icon/less.gif -------------------------------------------------------------------------------- /views/img/icon/more.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/icon/more.gif -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": false, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /views/img/doofinder_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/doofinder_logo.png -------------------------------------------------------------------------------- /views/img/doofinder_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/doofinder_mac.png -------------------------------------------------------------------------------- /views/img/bg_search_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/bg_search_input.png -------------------------------------------------------------------------------- /views/img/bg_search_submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/bg_search_submit.png -------------------------------------------------------------------------------- /views/img/template_1_cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/template_1_cards.png -------------------------------------------------------------------------------- /views/img/template_1_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/template_1_logo.png -------------------------------------------------------------------------------- /views/img/template_2_cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/template_2_cards.png -------------------------------------------------------------------------------- /views/img/template_2_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doofinder/doofinder-prestashop/HEAD/views/img/template_2_logo.png -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | # For Apache 2.2 3 | 4 | 5 | Order allow,deny 6 | Deny from all 7 | 8 | 9 | 10 | # For Apache 2.4 11 | 12 | 13 | Require all denied 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Exception/DoofinderException.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/templates/admin/message_manage_one_shop.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 |

{$text_one_shop|escape:'htmlall':'UTF-8'}

-------------------------------------------------------------------------------- /views/templates/admin/dummy/pre_tab.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 |
-------------------------------------------------------------------------------- /views/templates/admin/configure_footer.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | {if $configured} 15 |
{include file='./support_tab.tpl'}
16 | {/if} 17 |
18 | 19 | -------------------------------------------------------------------------------- /views/css/admin-panel.css: -------------------------------------------------------------------------------- 1 | .df-checkboxes-table { 2 | padding: 8px 16px; 3 | font-size: 12px; 4 | line-height: 1.42857; 5 | color: #555; 6 | background-color: #fff; 7 | background-image: none; 8 | border: 1px solid #bbcdd2; 9 | border-radius: 4px; 10 | max-height: 300px; 11 | overflow-y: auto; 12 | } 13 | 14 | .df-checkboxes-table table { 15 | width: 100%; 16 | } 17 | 18 | .df-checkboxes-table tr td:first-child { 19 | width: 28px; 20 | } 21 | 22 | .df-checkboxes-table td { 23 | padding: 2px; 24 | } 25 | 26 | .df-checkboxes-table label { 27 | margin-bottom: 0; 28 | font-weight: normal; 29 | } 30 | 31 | .df-checkboxes-table input[type="checkbox"] { 32 | width: 20px; 33 | height: 20px; 34 | } 35 | -------------------------------------------------------------------------------- /views/templates/admin/reindex.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 |

{$text_data_changed|escape:'htmlall':'UTF-8'}

15 |

16 |

17 | 20 |
21 |

22 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'pretty_version' => '7.0.2', 4 | 'version' => '7.0.2.0', 5 | 'type' => 'prestashop-module', 6 | 'install_path' => __DIR__ . '/../../', 7 | 'aliases' => array(), 8 | 'reference' => null, 9 | 'name' => 'prestashop/doofinder', 10 | 'dev' => false, 11 | ), 12 | 'versions' => array( 13 | 'prestashop/doofinder' => array( 14 | 'pretty_version' => '7.0.2', 15 | 'version' => '7.0.2.0', 16 | 'type' => 'prestashop-module', 17 | 'install_path' => __DIR__ . '/../../', 18 | 'aliases' => array(), 19 | 'reference' => null, 20 | 'dev_requirement' => false, 21 | ), 22 | ), 23 | ); 24 | -------------------------------------------------------------------------------- /phpdoc.dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | phpDocumentor 8 | 9 | build/docs 10 | 11 | 12 | 13 | 14 | . 15 | 16 | 22 | 23 | php 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /views/templates/front/catalog/_partials/productlist.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | 15 | {capture assign="productClasses"}{if !empty($productClass)}{$productClass}{else}col-xs-6 col-xl-4{/if}{/capture} 16 | 17 |
18 | {foreach from=$products item="product" key="position"} 19 | {include file="catalog/_partials/miniatures/product.tpl" product=$product position=$position productClasses=$productClasses} 20 | {/foreach} 21 |
22 | -------------------------------------------------------------------------------- /views/js/add-to-cart/doofinder-add_to_cart_ps16.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | */ 14 | 15 | //implementation of "add to cart" functionality for prestashop 1.6.x 16 | 17 | let doofinderManageCart = (cartOptions) => { 18 | 19 | let IdProductCart; 20 | let IdCustomization; 21 | if (cartOptions.productID.includes('VAR-')) { 22 | IdProductCart = cartOptions.group_id; 23 | IdCustomization = cartOptions.productID.replace('VAR-', ''); 24 | } else { 25 | IdProductCart = cartOptions.productID; 26 | IdCustomization = cartOptions.customizationID; 27 | } 28 | 29 | ajaxCart.add(IdProductCart, IdCustomization, undefined, undefined, cartOptions.quantity); 30 | } 31 | -------------------------------------------------------------------------------- /views/templates/admin/display_msg.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 |
15 |
16 | 17 | {if isset($d_raw) && $d_raw} 18 | {html_entity_decode($d_message|escape:'htmlall':'UTF-8')} 19 | {elseif isset($d_link) && $d_link} 20 | 21 | {$d_message|escape:'htmlall':'UTF-8'} 22 | 23 | {else} 24 | {$d_message|escape:'htmlall':'UTF-8'} 25 | {/if} 26 |
27 |
28 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /views/templates/admin/configure_administration_panel.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | 15 |
16 |
{l s='Administration panel' mod='doofinder'}
17 |
18 |
19 |
20 |
21 |

{l s='Config Doofinder in my shop' mod='doofinder'}

22 |

{l s='Access the Doofinder dashboard and discover all the functionalities to increase your sales.' mod='doofinder'}

23 |
24 | 30 |
31 |
32 |
33 |
-------------------------------------------------------------------------------- /upgrade/index.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2020 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 27 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 28 | 29 | header('Cache-Control: no-store, no-cache, must-revalidate'); 30 | header('Cache-Control: post-check=0, pre-check=0', false); 31 | header('Pragma: no-cache'); 32 | 33 | header('Location: ../'); 34 | exit; 35 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.6.1.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Deletes the table used to store landing page data, since this feature was removed. 32 | * 33 | * @param Doofinder $module the module instance being upgraded 34 | * 35 | * @return bool true if the query executes successfully, false otherwise 36 | */ 37 | function upgrade_module_4_6_1($module) 38 | { 39 | return Db::getInstance()->delete('doofinder_landing'); 40 | } 41 | -------------------------------------------------------------------------------- /views/templates/admin/configure.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | {if $oldPS} 15 | 22 | {/if} 23 | {if isset($formUpdatedToClick)} 24 | 29 | {/if} 30 | 31 | 32 | 33 | 44 | 45 | 46 |
47 | {if !$configured || $is_new_shop} 48 |
{include file='./onboarding_tab.tpl'}
49 | {/if} -------------------------------------------------------------------------------- /src/Api/DoofinderLayerApi.php: -------------------------------------------------------------------------------- 1 | get( 44 | $apiEndpoint, 45 | null, 46 | null, 47 | null, 48 | 'application/json', 49 | ['Authorization: Token ' . $apiKey] 50 | ); 51 | 52 | return json_decode($response->response, true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /controllers/front/callback.php: -------------------------------------------------------------------------------- 1 | 'success'])); 47 | } else { 48 | http_response_code(405); 49 | exit; 50 | } 51 | } 52 | 53 | /** 54 | * Initializes the content for the front controller. 55 | * 56 | * Calls the parent implementation. No additional content is rendered. 57 | * 58 | * @return void 59 | */ 60 | public function initContent() 61 | { 62 | parent::initContent(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.3.1.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Upgrade the module to version 4.3.1. 32 | * 33 | * This upgrade step: 34 | * - Updates the DF_AI_ADMIN_ENDPOINT configuration value by replacing 35 | * 'app' with 'admin' in the existing endpoint URL. 36 | * 37 | * @param Doofinder $module the module instance being upgraded (not used here but required by the signature) 38 | * 39 | * @return bool true if the value was updated successfully, false otherwise 40 | */ 41 | function upgrade_module_4_3_1($module) 42 | { 43 | $current_admin_endpoint = Configuration::getGlobalValue('DF_AI_ADMIN_ENDPOINT'); 44 | $admin_endpoint = str_replace('app', 'admin', $current_admin_endpoint); 45 | 46 | return Configuration::updateGlobalValue('DF_AI_ADMIN_ENDPOINT', $admin_endpoint); 47 | } 48 | -------------------------------------------------------------------------------- /src/Manager/LanguageManager.php: -------------------------------------------------------------------------------- 1 | getValue( 46 | ' 47 | SELECT `iso_code` FROM ' . _DB_PREFIX_ . 'currency WHERE `id_currency` = ' . (int) $id 48 | ); 49 | } 50 | 51 | /** 52 | * Gets the ISO code of a language code 53 | * 54 | * @param string $code 3-letter Month abbreviation 55 | * 56 | * @return string 57 | */ 58 | public static function getLanguageCode($code) 59 | { 60 | // $code is in the form of 'xx-YY' where xx is the language code 61 | // and 'YY' a country code identifying a variant of the language. 62 | $langCountry = explode('-', $code); 63 | 64 | return $langCountry[0]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 30 | if ($useStaticLoader) { 31 | require __DIR__ . '/autoload_static.php'; 32 | 33 | call_user_func(\Composer\Autoload\ComposerStaticInitddb45daa7f46128bfeb9153853763869::getInitializer($loader)); 34 | } else { 35 | $map = require __DIR__ . '/autoload_namespaces.php'; 36 | foreach ($map as $namespace => $path) { 37 | $loader->set($namespace, $path); 38 | } 39 | 40 | $map = require __DIR__ . '/autoload_psr4.php'; 41 | foreach ($map as $namespace => $path) { 42 | $loader->setPsr4($namespace, $path); 43 | } 44 | 45 | $classMap = require __DIR__ . '/autoload_classmap.php'; 46 | if ($classMap) { 47 | $loader->addClassMap($classMap); 48 | } 49 | } 50 | 51 | $loader->register(false); 52 | 53 | return $loader; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Core/DoofinderConstants.php: -------------------------------------------------------------------------------- 1 | To modify this file, edit the corresponding template in: | 25 | * | templates/ directory instead | 26 | * | | 27 | * | ~ This file will be regenerated automatically! | 28 | * +---------------------------------------------------------------------------+ 29 | */ 30 | 31 | namespace PrestaShop\Module\Doofinder\Core; 32 | 33 | if (!defined('_PS_VERSION_')) { 34 | exit; 35 | } 36 | 37 | class DoofinderConstants 38 | { 39 | const DOOMANAGER_REGION_URL = 'https://%sadmin.doofinder.com'; 40 | const DOOPLUGINS_REGION_URL = 'https://%splugins.doofinder.com'; 41 | const DOOPHOENIX_REGION_URL = 'https://%ssearch.doofinder.com'; 42 | const CONFIG_REGION_URL = 'https://%s-config.doofinder.com'; 43 | const GS_SHORT_DESCRIPTION = 1; 44 | const GS_LONG_DESCRIPTION = 2; 45 | const VERSION = '7.0.2'; 46 | const NAME = 'doofinder'; 47 | const YES = 1; 48 | const NO = 0; 49 | } 50 | -------------------------------------------------------------------------------- /upgrade/upgrade-7.0.0.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | 27 | use PrestaShop\Module\Doofinder\Configuration\DoofinderConfig; 28 | 29 | if (!defined('_PS_VERSION_')) { 30 | exit; 31 | } 32 | 33 | /** 34 | * Upgrades the Doofinder module to version 7.0.0. 35 | * 36 | * This upgrade removes the landing pages feature which has been deprecated. 37 | * It drops the doofinder_landing table that was used to cache landing page data. 38 | * 39 | * @param Doofinder $module the Doofinder module instance being upgraded 40 | * 41 | * @return bool true on success, false if an error occurs 42 | */ 43 | function upgrade_module_7_0_0($module) 44 | { 45 | DoofinderConfig::debug('Initiating 7.0.0 upgrade - Removing landing pages feature'); 46 | 47 | $result = Db::getInstance()->execute( 48 | 'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'doofinder_landing`' 49 | ); 50 | 51 | if ($result) { 52 | DoofinderConfig::debug('Landing pages table dropped successfully.'); 53 | } else { 54 | DoofinderConfig::debug('Failed to drop landing pages table.'); 55 | } 56 | 57 | return $result; 58 | } 59 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.8.2.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | 27 | use PrestaShop\Module\Doofinder\Api\DoofinderApiSingleScript; 28 | 29 | if (!defined('_PS_VERSION_')) { 30 | exit; 31 | } 32 | 33 | /** 34 | * Upgrade the module to version 4.8.2. 35 | * 36 | * This upgrade: 37 | * - Uses existing installation credentials (installation ID, region, API key) 38 | * to initialize DoofinderApiSingleScript. 39 | * - Calls `setSingleScriptFlag()` to mark that the single-script is enabled. 40 | * - Stores DF_UNIQUE_SCRIPT = true in configuration. 41 | * 42 | * @param Doofinder $module the module instance being upgraded 43 | * 44 | * @return bool true if the flag is set successfully, false otherwise 45 | */ 46 | function upgrade_module_4_8_2($module) 47 | { 48 | $installationId = Configuration::get('DF_INSTALLATION_ID'); 49 | $region = Configuration::get('DF_REGION'); 50 | $apiKey = Configuration::get('DF_API_KEY'); 51 | 52 | $apiModule = new DoofinderApiSingleScript($installationId, $region, $apiKey); 53 | $apiModule->setSingleScriptFlag(); 54 | 55 | return Configuration::updateValue('DF_UNIQUE_SCRIPT', true); 56 | } 57 | -------------------------------------------------------------------------------- /feeds/category.php: -------------------------------------------------------------------------------- 1 | shop->id); 49 | if (!$shop->id) { 50 | exit('NOT PROPERLY CONFIGURED'); 51 | } 52 | $lang = DfTools::getLanguageFromRequest(); 53 | $context->language = $lang; 54 | 55 | // CATEGORY DATA 56 | $categories = DfTools::getCategories($lang->id); 57 | $builder = new DfCategoryBuild($shop->id, $lang->id); 58 | $builder->setCategories($categories); 59 | $rows = $builder->build(false); 60 | 61 | // HEADERS 62 | $header = ['id', 'title', 'description', 'meta_title', 'meta_description', 'link', 'image_link']; 63 | 64 | $csv = fopen('php://output', 'w'); 65 | fputcsv($csv, $header, DfTools::TXT_SEPARATOR); 66 | 67 | // CATEGORIES 68 | foreach ($rows as $row) { 69 | $csvRow = []; 70 | foreach ($header as $field) { 71 | $csvRow[$field] = array_key_exists($field, $row) ? $row[$field] : ''; 72 | } 73 | fputcsv($csv, $csvRow, DfTools::TXT_SEPARATOR); 74 | } 75 | fclose($csv); 76 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.4.8.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Upgrade the module to version 4.4.8. 32 | * 33 | * This upgrade step: 34 | * - Sets the DF_FEED_INDEXED configuration value to true. 35 | * - Installs a back-office tab for the Doofinder admin controller. 36 | * 37 | * @param Doofinder $module the module instance being upgraded 38 | * 39 | * @return bool true if the configuration update and tab installation succeed, false otherwise 40 | */ 41 | function upgrade_module_4_4_8($module) 42 | { 43 | return Configuration::updateGlobalValue('DF_FEED_INDEXED', true) 44 | && installTabs_4_4_8(); 45 | } 46 | 47 | /** 48 | * Install an inactive admin tab for the Doofinder admin controller. 49 | * 50 | * This tab is created but not displayed in the back-office menu (active = false). 51 | * 52 | * @return bool true if the tab was saved successfully, false otherwise 53 | */ 54 | function installTabs_4_4_8() 55 | { 56 | $tab = new Tab(); 57 | $tab->active = false; 58 | $tab->class_name = 'DoofinderAdmin'; 59 | $tab->name = []; 60 | foreach (Language::getLanguages() as $lang) { 61 | $tab->name[$lang['id_lang']] = 'Doofinder admin controller'; 62 | } 63 | $tab->id_parent = 0; 64 | $tab->module = 'doofinder'; 65 | 66 | return $tab->save(); 67 | } 68 | -------------------------------------------------------------------------------- /feeds/cms.php: -------------------------------------------------------------------------------- 1 | shop->id); 49 | if (!$shop->id) { 50 | exit('NOT PROPERLY CONFIGURED'); 51 | } 52 | $lang = DfTools::getLanguageFromRequest(); 53 | $context->language = $lang; 54 | 55 | // CMS DATA 56 | $cms_pages = DfTools::getCmsPages($lang->id, $shop->id); 57 | $builder = new DfCmsBuild($shop->id, $lang->id); 58 | $builder->setCmsPages($cms_pages); 59 | $rows = $builder->build(false); 60 | 61 | // HEADERS 62 | $header = ['id', 'title', 'description', 'meta_title', 'meta_description', 'tags', 'content', 'link']; 63 | echo implode(DfTools::TXT_SEPARATOR, $header) . PHP_EOL; 64 | 65 | // CMS Pages 66 | foreach ($rows as $row) { 67 | echo $row['id'] . DfTools::TXT_SEPARATOR; 68 | echo $row['title'] . DfTools::TXT_SEPARATOR; 69 | echo $row['description'] . DfTools::TXT_SEPARATOR; 70 | echo $row['meta_title'] . DfTools::TXT_SEPARATOR; 71 | echo $row['meta_description'] . DfTools::TXT_SEPARATOR; 72 | // Tags does not seem to exist on PrestaShop 9 73 | echo !empty($row['tags']) ? $row['tags'] : '' . DfTools::TXT_SEPARATOR; 74 | echo $row['content'] . DfTools::TXT_SEPARATOR; 75 | echo $row['link']; 76 | echo PHP_EOL; 77 | } 78 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.5.0.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Upgrade the module to version 4.5.0. 32 | * 33 | * This upgrade step: 34 | * - Creates the doofinder_landing table if it does not already exist. 35 | * - Registers the 'moduleRoutes' hook. 36 | * - Calls setSearchEnginesByConfig() to configure search engines. 37 | * 38 | * @param Doofinder $module the module instance being upgraded 39 | * 40 | * @return bool true if all steps succeed, false otherwise 41 | */ 42 | function upgrade_module_4_5_0($module) 43 | { 44 | return installDb_4_5_0() 45 | && $module->registerHook('moduleRoutes') 46 | && $module->setSearchEnginesByConfig(); 47 | } 48 | 49 | /** 50 | * Install database structure for module version 4.5.0. 51 | * 52 | * Creates a table to store landing page data if it does not exist. 53 | * 54 | * @return bool true if the query executes successfully, false otherwise 55 | */ 56 | function installDb_4_5_0() 57 | { 58 | return Db::getInstance()->execute( 59 | ' 60 | CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'doofinder_landing` ( 61 | `name` VARCHAR(45) NOT NULL, 62 | `hashid` VARCHAR(45) NOT NULL, 63 | `data` TEXT NOT NULL, 64 | `date_upd` DATETIME NOT NULL, 65 | PRIMARY KEY (`name`, `hashid`) 66 | ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;' 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/Api/DoofinderApiSingleScript.php: -------------------------------------------------------------------------------- 1 | installationId = $installationId; 37 | $this->apiKey = $apiKey; 38 | $this->apiUrl = UrlManager::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region); 39 | } 40 | 41 | /** 42 | * Make a request to the API to SET single script flag to notify the migration of this customer 43 | * 44 | * This function does not require any parameters. 45 | * 46 | * @return mixed The response from the API request 47 | */ 48 | public function setSingleScriptFlag() 49 | { 50 | $endpoint = '/prestashop/migrate-unique-script'; 51 | 52 | $url = $this->apiUrl . $endpoint; 53 | 54 | return $this->post($url); 55 | } 56 | 57 | /** 58 | * Execute a POST request to the Doofinder API with a JSON payload. 59 | * 60 | * The payload will be the installation ID. 61 | * 62 | * @param string $url Full API endpoint URL 63 | * 64 | * @return array|null Decoded JSON response from the API, or null if the request fails 65 | */ 66 | private function post($url) 67 | { 68 | $client = new EasyREST(); 69 | 70 | $body = [ 71 | 'installation_id' => $this->installationId, 72 | ]; 73 | 74 | $jsonStoreData = json_encode($body); 75 | 76 | $response = $client->post( 77 | $url, 78 | $jsonStoreData, 79 | null, 80 | null, 81 | 'application/json', 82 | ['Authorization: Token ' . $this->apiKey] 83 | ); 84 | 85 | return json_decode($response->response, true); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /views/templates/admin/support_tab.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |

{l s='Need help configuring your search engine?' mod='doofinder'}

22 |
23 |
24 |
25 |
26 |

{l s='All documentation in one place!' mod='doofinder'} https://support.doofinder.com/

27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
· {l s='Understand how the product feed works to display results in the Doofinder search layer' mod='doofinder'}
35 |
Visitar página
36 | 37 |
· {l s='How to add information in the Doofinder search layer' mod='doofinder'}
38 |
Visitar página
39 | 40 |
· {l s='How to configure the search layer filters' mod='doofinder'}
41 |
Visitar página
42 | 43 |
· {l s='Learn the basics about Live Layer' mod='doofinder' mod='doofinder'}
44 |
Visitar página
45 | 46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |

{l s='Or contact directly with us. We will be glad to help you' mod='doofinder'}

55 |
56 |
https://admin.doofinder.com/admin/support/contact-us
57 |
58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /controllers/front/ajax.php: -------------------------------------------------------------------------------- 1 | ajax = true; 35 | 36 | $checkApiKey = Tools::getValue('check_api_key'); 37 | if ($checkApiKey) { 38 | exit(DoofinderApi::checkApiKey(true)); 39 | } 40 | 41 | $autoinstaller = Tools::getValue('autoinstaller'); 42 | $shopId = Tools::getValue('shop_id', null); 43 | if ($autoinstaller) { 44 | if (Tools::getValue('token') == DfTools::encrypt('doofinder-ajax')) { 45 | header('Content-Type:application/json; charset=utf-8'); 46 | DoofinderInstallation::autoinstaller($shopId); 47 | $this->compatRender(json_encode(['success' => true])); 48 | } else { 49 | $this->compatRender(json_encode([ 50 | 'success' => false, 'errors' => ['Forbidden access. Invalid token for autoinstaller.'], 51 | ])); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * The native function exists only after PrestaShop 1.7, so in order 58 | * to keep compatibility with PrestaShop 1.6 we must keep `ajaxDie` as a fallback. 59 | * 60 | * @param string|null $value 61 | * @param string|null $controller 62 | * @param string|null $method 63 | * 64 | * @throws PrestaShopException 65 | */ 66 | private function compatRender($value = null, $controller = null, $method = null) 67 | { 68 | if (method_exists($this, 'ajaxRender')) { 69 | $this->ajaxRender($value, $controller, $method); 70 | exit; 71 | } elseif (method_exists($this, 'ajaxDie')) { 72 | $this->ajaxDie($value, $controller, $method); 73 | exit; 74 | } 75 | 76 | echo $value; 77 | exit; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /upgrade/upgrade-5.1.4.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | 27 | use PrestaShop\Module\Doofinder\Configuration\DoofinderConfig; 28 | use PrestaShop\Module\Doofinder\Installer\DoofinderInstallation; 29 | 30 | if (!defined('_PS_VERSION_')) { 31 | exit; 32 | } 33 | 34 | /** 35 | * Upgrades the Doofinder module to version 5.1.4. 36 | * 37 | * This upgrade step updates feed URLs used by the module, because in older versions the paths 38 | * were /modules/doofinder/{name}.php (where {name} could be config, feed, etc.) which were physical 39 | * PHP files and PrestaShop 9 flags direct calls as errors, so instead it encourages to call a Module FrontController. 40 | * So from this versions the paths are /module/doofinder/{name} (Note that this time is `module` and not `modules` and the .php 41 | * extension has been removed since it is now a rewritten URL and not a real path to a physical file) 42 | * 43 | * Logs progress messages and captures any exceptions during the update process. 44 | * If an error occurs, it records the issue in PrestaShop logs and stops the upgrade. 45 | * 46 | * @param Doofinder $module the Doofinder module instance being upgraded 47 | * 48 | * @return bool true on success, false if an exception occurs during the update 49 | */ 50 | function upgrade_module_5_1_4($module) 51 | { 52 | DoofinderConfig::debug('Initiating 5.1.4 upgrade'); 53 | 54 | try { 55 | DoofinderInstallation::updateFeedUrls(); 56 | } catch (Exception $exception) { 57 | PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id); 58 | 59 | return false; 60 | } 61 | 62 | DoofinderConfig::debug('Feed URLs updated successfully.'); 63 | 64 | return true; 65 | } 66 | -------------------------------------------------------------------------------- /controllers/front/feed.php: -------------------------------------------------------------------------------- 1 | ajax = true; 49 | 50 | ob_start(); 51 | switch (Tools::getValue('type')) { 52 | case 'category': 53 | require self::get_plugin_dir() . 'feeds/category.php'; 54 | break; 55 | 56 | case 'page': 57 | require self::get_plugin_dir() . 'feeds/cms.php'; 58 | break; 59 | 60 | case 'product': 61 | default: 62 | require self::get_plugin_dir() . 'feeds/product.php'; 63 | break; 64 | } 65 | $feed = ob_get_clean(); 66 | if (method_exists($this, 'ajaxRender')) { 67 | $this->ajaxRender($feed); 68 | exit; 69 | } elseif (method_exists($this, 'ajaxDie')) { 70 | // Workaround for PS 1.6 as ajaxRender is not available 71 | $this->ajaxDie($feed); 72 | } else { 73 | // Workaround for PS 1.5 74 | echo $feed; 75 | exit; 76 | } 77 | } 78 | 79 | /** 80 | * Returns the full path to the Doofinder module directory. 81 | * 82 | * @return string module directory path 83 | */ 84 | private static function get_plugin_dir() 85 | { 86 | return _PS_MODULE_DIR_ . DIRECTORY_SEPARATOR . DoofinderConstants::NAME . DIRECTORY_SEPARATOR; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'Doofinder' => $baseDir . '/doofinder.php', 11 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApi' => $baseDir . '/src/Api/DoofinderApi.php', 12 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiIndex' => $baseDir . '/src/Api/DoofinderApiIndex.php', 13 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiItems' => $baseDir . '/src/Api/DoofinderApiItems.php', 14 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiSingleScript' => $baseDir . '/src/Api/DoofinderApiSingleScript.php', 15 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderLayerApi' => $baseDir . '/src/Api/DoofinderLayerApi.php', 16 | 'PrestaShop\\Module\\Doofinder\\Api\\EasyREST' => $baseDir . '/src/Api/EasyREST.php', 17 | 'PrestaShop\\Module\\Doofinder\\Autoloader' => $baseDir . '/src/Autoloader.php', 18 | 'PrestaShop\\Module\\Doofinder\\Configuration\\DoofinderConfig' => $baseDir . '/src/Configuration/DoofinderConfig.php', 19 | 'PrestaShop\\Module\\Doofinder\\Core\\DoofinderConstants' => $baseDir . '/src/Core/DoofinderConstants.php', 20 | 'PrestaShop\\Module\\Doofinder\\Core\\DoofinderScript' => $baseDir . '/src/Core/DoofinderScript.php', 21 | 'PrestaShop\\Module\\Doofinder\\Core\\SearchEngine' => $baseDir . '/src/Core/SearchEngine.php', 22 | 'PrestaShop\\Module\\Doofinder\\Core\\UpdateOnSave' => $baseDir . '/src/Core/UpdateOnSave.php', 23 | 'PrestaShop\\Module\\Doofinder\\Exception\\DoofinderException' => $baseDir . '/src/Exception/DoofinderException.php', 24 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfCategoryBuild' => $baseDir . '/src/Feed/DfCategoryBuild.php', 25 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfCmsBuild' => $baseDir . '/src/Feed/DfCmsBuild.php', 26 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfProductBuild' => $baseDir . '/src/Feed/DfProductBuild.php', 27 | 'PrestaShop\\Module\\Doofinder\\Installer\\DoofinderInstallation' => $baseDir . '/src/Installer/DoofinderInstallation.php', 28 | 'PrestaShop\\Module\\Doofinder\\Manager\\FormManager' => $baseDir . '/src/Manager/FormManager.php', 29 | 'PrestaShop\\Module\\Doofinder\\Manager\\HookManager' => $baseDir . '/src/Manager/HookManager.php', 30 | 'PrestaShop\\Module\\Doofinder\\Manager\\LanguageManager' => $baseDir . '/src/Manager/LanguageManager.php', 31 | 'PrestaShop\\Module\\Doofinder\\Manager\\UrlManager' => $baseDir . '/src/Manager/UrlManager.php', 32 | 'PrestaShop\\Module\\Doofinder\\Utils\\DfDb' => $baseDir . '/src/Utils/DfDb.php', 33 | 'PrestaShop\\Module\\Doofinder\\Utils\\DfTools' => $baseDir . '/src/Utils/DfTools.php', 34 | 'PrestaShop\\Module\\Doofinder\\View\\DoofinderAdminPanelView' => $baseDir . '/src/View/DoofinderAdminPanelView.php', 35 | ); 36 | -------------------------------------------------------------------------------- /src/Core/DoofinderScript.php: -------------------------------------------------------------------------------- 1 | isMobile(); 46 | } elseif (method_exists($context, 'getMobileDetect')) { 47 | $isMobile = $context->getMobileDetect()->isMobile(); 48 | } 49 | 50 | return $displayGeneral && (!$isMobile || $displayMobile); 51 | } 52 | 53 | /** 54 | * Gets the script for the Livelayer search layer according to the PrestaShop version. 55 | * 56 | * @return string 57 | */ 58 | public static function getSingleScriptPath($modulePath) 59 | { 60 | /* 61 | * Loads different cart handling assets depending on the version of PrestaShop used 62 | * (uses different javascript implementations for this purpose in PrestaShop 1.6.x and 1.7.x) 63 | */ 64 | if (version_compare(_PS_VERSION_, '1.7', '<') === true) { 65 | return $modulePath . 'views/js/add-to-cart/doofinder-add_to_cart_ps16.js'; 66 | } 67 | 68 | return $modulePath . 'views/js/add-to-cart/doofinder-add_to_cart_ps17.js'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /controllers/admin/DoofinderAdminController.php: -------------------------------------------------------------------------------- 1 | context = Context::getContext(); 35 | $this->bootstrap = true; 36 | $this->lang = false; 37 | parent::__construct(); 38 | } 39 | 40 | /** 41 | * AJAX action to update the DF_FEED_INDEXED configuration flag. 42 | * 43 | * Sets DF_FEED_INDEXED to true and returns a JSON success response. 44 | * 45 | * @return void 46 | */ 47 | public function displayAjaxUpdateConfigurationField() 48 | { 49 | Configuration::updateValue('DF_FEED_INDEXED', true); 50 | $this->ajaxRender(json_encode(['success' => true])); 51 | } 52 | 53 | /** 54 | * AJAX action to check the DF_FEED_INDEXED configuration flag. 55 | * 56 | * Returns a JSON response indicating whether the flag is enabled. 57 | * 58 | * @return void 59 | */ 60 | public function displayAjaxCheckConfigurationField() 61 | { 62 | $is_feed_indexed = Configuration::get('DF_FEED_INDEXED', null, null, null, false); 63 | $this->ajaxRender(json_encode(['success' => $is_feed_indexed])); 64 | } 65 | 66 | /** 67 | * Renders AJAX responses, with compatibility for several PrestaShop versions. 68 | * 69 | * Note that `ajaxRender` method is only available since PrestaShop 1.7 70 | * 71 | * @param string|null $value the response content to render 72 | * @param string|null $controller optional controller name for compatibility 73 | * @param string|null $method optional method name for compatibility 74 | * 75 | * @return void 76 | */ 77 | public function ajaxRender($value = null, $controller = null, $method = null) 78 | { 79 | if (method_exists('ModuleAdminController', 'ajaxRender')) { 80 | parent::ajaxRender($value, $controller, $method); 81 | 82 | return; 83 | } 84 | 85 | echo $value; 86 | exit; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.2.0.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Upgrade the module to version 4.2.0. 32 | * 33 | * This upgrade step: 34 | * - Installs or updates the required database table. 35 | * - Registers new hooks to handle product save and delete actions for the Update On Save. 36 | * 37 | * @param Doofinder $module the module instance being upgraded 38 | * 39 | * @return bool true on success, false on failure 40 | */ 41 | function upgrade_module_4_2_0($module) 42 | { 43 | return installDb_4_2_0() 44 | && $module->registerHook('actionProductSave') 45 | && $module->registerHook('actionProductDelete'); 46 | } 47 | 48 | /** 49 | * Create or update the `doofinder_product` table if it does not already exist. 50 | * 51 | * The table stores product changes to synchronize with Doofinder: 52 | * - `id_shop` and `id_product` identify the product in a specific shop. 53 | * - `action` stores the type of change (e.g., save or delete). 54 | * - `date_upd` stores the timestamp of the last change. 55 | * A unique key ensures there are no duplicate entries per shop/product combination. 56 | * 57 | * @return bool true if the query executed successfully, false otherwise 58 | */ 59 | function installDb_4_2_0() 60 | { 61 | return Db::getInstance()->execute( 62 | ' 63 | CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'doofinder_product` ( 64 | `id_doofinder_product` INT UNSIGNED NOT NULL AUTO_INCREMENT, 65 | `id_shop` INT(10) UNSIGNED NOT NULL, 66 | `id_product` INT(10) UNSIGNED NOT NULL, 67 | `action` VARCHAR(45) NOT NULL, 68 | `date_upd` DATETIME NOT NULL, 69 | PRIMARY KEY (`id_doofinder_product`), 70 | CONSTRAINT uc_shop_product UNIQUE KEY (id_shop,id_product) 71 | ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;' 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /views/templates/admin/indexation_status.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 |
15 |
16 |
17 |

{l s='Doofinder Indexation Status' mod='doofinder'}

18 |
19 |
20 | 23 |
24 |
25 |
26 |

{l s='The product feed is being processed. Depending on the size of the product catalog in the store, this process may take a few minutes.' mod='doofinder'}

27 |
28 |
29 |

{l s='Your products may not appear correctly updated in the search results until the process has been completed.' mod='doofinder'}

30 |
31 |
32 |
33 | 34 | 50 | 51 | 82 | -------------------------------------------------------------------------------- /src/Api/DoofinderApiIndex.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 51 | $this->apiUrl = UrlManager::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region); 52 | } 53 | 54 | /** 55 | * Invoke reindexing for all feeds in the store. 56 | * 57 | * Sends a request to the Doofinder Plugins API to reprocess all feeds. 58 | * 59 | * @param string $installationId Unique identifier of the store installation 60 | * @param string $callbackUrl Optional callback URL to receive notifications once the process completes 61 | * 62 | * @return array|null Returns the decoded JSON response from the API, or null on failure 63 | */ 64 | public function invokeReindexing($installationId, $callbackUrl = '') 65 | { 66 | $apiEndpoint = $this->apiUrl . '/process-feed'; 67 | $jsonData = json_encode(['store_id' => $installationId, 'callback_url' => $callbackUrl]); 68 | 69 | return $this->post($apiEndpoint, $jsonData); 70 | } 71 | 72 | /** 73 | * Execute a POST request to a given URL with a JSON payload. 74 | * 75 | * @param string $url The API endpoint URL 76 | * @param string $payload JSON-encoded string to send in the request body 77 | * 78 | * @return array|null Returns the decoded JSON response, or null if the request fails 79 | */ 80 | private function post($url, $payload) 81 | { 82 | $client = new EasyREST(); 83 | 84 | $response = $client->post( 85 | $url, 86 | $payload, 87 | null, 88 | null, 89 | 'application/json', 90 | ['Authorization: Token ' . $this->apiKey] 91 | ); 92 | 93 | return json_decode($response->response, true); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /views/templates/front/scriptV9.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | {if isset($installation_ID) && $installation_ID} 15 | 16 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 70 | 71 | 72 | {/if} 73 | -------------------------------------------------------------------------------- /src/Autoloader.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | if (!defined('_PS_VERSION_')) { 27 | exit; 28 | } 29 | 30 | /** 31 | * Upgrade the module to version 4.6.4. 32 | * 33 | * This upgrade step: 34 | * - Replaces the old doofinder_product table with a new unified doofinder_updates table. 35 | * - Adds support for tracking updates from multiple indexable objects (products, CMS pages, categories). 36 | * - Registers hooks for CMS and category CRUD operations. 37 | * 38 | * @param Doofinder $module the module instance being upgraded 39 | * 40 | * @return bool true if all steps succeed, false otherwise 41 | */ 42 | function upgrade_module_4_6_4($module) 43 | { 44 | return installDb_4_6_4() 45 | && $module->registerHook('actionObjectCmsAddAfter') 46 | && $module->registerHook('actionObjectCmsUpdateAfter') 47 | && $module->registerHook('actionObjectCmsDeleteAfter') 48 | && $module->registerHook('actionObjectCategoryAddAfter') 49 | && $module->registerHook('actionObjectCategoryUpdateAfter') 50 | && $module->registerHook('actionObjectCategoryDeleteAfter'); 51 | } 52 | 53 | /** 54 | * Install database structure for module version 4.6.4. 55 | * 56 | * Drops the old `doofinder_product` table (used only for products) 57 | * and creates a new `doofinder_updates` table to handle multiple item types. 58 | * 59 | * @return bool true if the new table is created successfully, false otherwise 60 | */ 61 | function installDb_4_6_4() 62 | { 63 | Db::getInstance()->execute('DROP TABLE `' . _DB_PREFIX_ . 'doofinder_product`'); 64 | 65 | return Db::getInstance()->execute( 66 | ' 67 | CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'doofinder_updates` ( 68 | `id_doofinder_update` INT UNSIGNED NOT NULL AUTO_INCREMENT, 69 | `id_shop` INT(10) UNSIGNED NOT NULL, 70 | `object` varchar(45) NOT NULL, 71 | `id_object` INT(10) UNSIGNED NOT NULL, 72 | `action` VARCHAR(45) NOT NULL, 73 | `date_upd` DATETIME NOT NULL, 74 | PRIMARY KEY (`id_doofinder_update`), 75 | CONSTRAINT uc_shop_update UNIQUE KEY (id_shop,object,id_object) 76 | ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 ;' 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /upgrade/upgrade-6.0.0.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | 27 | use PrestaShop\Module\Doofinder\Configuration\DoofinderConfig; 28 | 29 | if (!defined('_PS_VERSION_')) { 30 | exit; 31 | } 32 | 33 | /** 34 | * Upgrades the Doofinder module to version 6.0.0. 35 | * 36 | * This upgrade step updates the file paths to the new namespace structure deleting the old ones. 37 | * 38 | * @param Doofinder $module the Doofinder module instance being upgraded 39 | * 40 | * @return bool true on success, false if an exception occurs during the update 41 | */ 42 | function upgrade_module_6_0_0($module) 43 | { 44 | DoofinderConfig::debug('Initiating 6.0.0 upgrade'); 45 | 46 | $files = [ 47 | 'src/Entity/DfCategoryBuild.php', 48 | 'src/Entity/DfCmsBuild.php', 49 | 'src/Entity/DfDb.php', 50 | 'src/Entity/DfProductBuild.php', 51 | 'src/Entity/DfTools.php', 52 | 'src/Entity/DoofinderAdminPanelView.php', 53 | 'src/Entity/DoofinderApi.php', 54 | 'src/Entity/DoofinderApiIndex.php', 55 | 'src/Entity/DoofinderApiItems.php', 56 | 'src/Entity/DoofinderApiLanding.php', 57 | 'src/Entity/DoofinderApiSingleScript.php', 58 | 'src/Entity/DoofinderConfig.php', 59 | 'src/Entity/DoofinderConstants.php', 60 | 'src/Entity/DoofinderException.php', 61 | 'src/Entity/DoofinderInstallation.php', 62 | 'src/Entity/DoofinderLayerApi.php', 63 | 'src/Entity/DoofinderResults.php', 64 | 'src/Entity/DoofinderScript.php', 65 | 'src/Entity/EasyREST.php', 66 | 'src/Entity/FormManager.php', 67 | 'src/Entity/HookManager.php', 68 | 'src/Entity/LanguageManager.php', 69 | 'src/Entity/SearchEngine.php', 70 | 'src/Entity/UpdateOnSave.php', 71 | 'src/Entity/UrlManager.php']; 72 | 73 | foreach ($files as $fileName) { 74 | $filePath = realpath(_PS_MODULE_DIR_ . 'doofinder' . DIRECTORY_SEPARATOR . $fileName); 75 | if (!file_exists($filePath) || is_dir($filePath)) { 76 | continue; 77 | } 78 | 79 | if (!unlink($filePath)) { 80 | DoofinderConfig::debug('Couldn\'t delete file: ' . $filePath); 81 | continue; 82 | } 83 | 84 | DoofinderConfig::debug('Deleted file: ' . $filePath); 85 | } 86 | 87 | return true; 88 | } 89 | -------------------------------------------------------------------------------- /views/js/admin-panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | */ 14 | 15 | $(document).ready(function() { 16 | 17 | addVisibilityDependency('DF_GS_DISPLAY_PRICES', 'DF_GS_PRICES_USE_TAX_on'); 18 | addVisibilityDependency('DF_SHOW_PRODUCT_FEATURES', 'DF_FEATURES_SHOWN[]'); 19 | addVisibilityDependency('DF_SHOW_PRODUCT_VARIATIONS', 'DF_GROUP_ATTRIBUTES_SHOWN[]'); 20 | 21 | function addVisibilityDependency(triggeringElementId, targetElementId) { 22 | var trigeringElementOnId = "#" + triggeringElementId + "_on"; 23 | var trigeringElementOffId = "#" + triggeringElementId + "_off"; 24 | hideOrShowElement(trigeringElementOnId, targetElementId); 25 | 26 | $(trigeringElementOnId + "," + trigeringElementOffId).change(function() { 27 | hideOrShowElement(trigeringElementOnId, targetElementId); 28 | }); 29 | } 30 | 31 | function hideOrShowElement(trigeringElementOnId, targetElementId) { 32 | var value = $(trigeringElementOnId).is(':checked'); 33 | var parent = getParent(targetElementId); 34 | 35 | if (value) { 36 | parent.show() 37 | } else { 38 | parent.hide(); 39 | } 40 | } 41 | 42 | /* We get the element from the dom and then convert it to jquery 43 | because jquery has problems with some ids that prestashop sets, 44 | for example DF_FEATURES_SHOWN[]. */ 45 | 46 | function getParent(targetElementId) { 47 | var targetElement = $(document.getElementById(targetElementId)); 48 | var parent = targetElement.parents().filter(function() { 49 | return $(this).is('.form-group'); 50 | }); 51 | return parent; 52 | } 53 | 54 | const $apiKeyNode = $('#DF_API_KEY'); 55 | const $regionNode = $('#DF_REGION'); 56 | $apiKeyNode.on('change', function() { 57 | if (0 === $apiKeyNode.length || 0 === $regionNode.length) { 58 | return; 59 | } 60 | const apiKey = $apiKeyNode.val().trim(); 61 | if (!/eu1-|ap1-|us1-/.test(apiKey)) { 62 | return; 63 | } 64 | const region = apiKey.split('-').shift(); 65 | const previousRegion = $regionNode.val(); 66 | $regionNode.val(region); 67 | 68 | if (previousRegion === region) { 69 | return; 70 | } 71 | 72 | // Small animation to catch user attention 73 | $regionNode.animate({ 74 | opacity:"0.5" 75 | }, 1000, function() { 76 | $regionNode.animate({ 77 | opacity:"1" 78 | }, 1000); 79 | }); 80 | }); 81 | 82 | $('.df-checkboxes-table [data-checkboxes-toggle]').on('click', function(event){ 83 | const $parentTable = $(this).closest('.df-checkboxes-table'); 84 | const isChecked = $(this).is(':checked'); 85 | $parentTable.find('input[type="checkbox"]').prop('checked', isChecked); 86 | }); 87 | 88 | const commonCheckboxSelector = 'input[type="checkbox"]:not([data-checkboxes-toggle])'; 89 | 90 | $('.df-checkboxes-table ' + commonCheckboxSelector).on('click', function(event){ 91 | const $parentTable = $(this).closest('.df-checkboxes-table'); 92 | const checkboxesTotalCount = $parentTable.find(commonCheckboxSelector).length; 93 | const checkboxesCheckedcount = $parentTable.find(commonCheckboxSelector + ':checked').length; 94 | const allChecked = checkboxesTotalCount === checkboxesCheckedcount; 95 | $parentTable.find('[data-checkboxes-toggle]').prop('checked', allChecked); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/Core/SearchEngine.php: -------------------------------------------------------------------------------- 1 | language_code); 49 | $shopGroupId = isset($shopGroupId) ? $shopGroupId : $context->shop->id_shop_group; 50 | $shopId = isset($shopId) ? $shopId : $context->shop->id; 51 | $hashid = \Configuration::get($hashidKey, $idLang, $shopGroupId, $shopId); 52 | 53 | if (!$hashid) { 54 | // If not found, try to obtain hashid without context 55 | $hashid = \Configuration::get($hashidKey, $idLang); 56 | } 57 | 58 | if (!$hashid) { 59 | // If not found, try to obtain hashid without idLang 60 | $hashid = \Configuration::get($hashidKey); 61 | } 62 | 63 | if (!$hashid) { 64 | $hashidKey = 'DF_HASHID_' . $currIso . '_' . strtoupper(LanguageManager::getLanguageCode($lang->language_code)); 65 | $hashid = \Configuration::get($hashidKey); 66 | } 67 | 68 | return $hashid; 69 | } 70 | 71 | /** 72 | * Update the hashid of the search engines of the store in the configuration 73 | * 74 | * @param int|null $idShopGroup 75 | * @param int|null $idShop 76 | * 77 | * @return true 78 | */ 79 | public static function setSearchEnginesByConfig($idShopGroup = null, $idShop = null) 80 | { 81 | $context = \Context::getContext(); 82 | $idShopGroup = isset($idShopGroup) ? $idShopGroup : $context->shop->id_shop_group; 83 | $idShop = isset($idShop) ? $idShop : $context->shop->id; 84 | $installationID = \Configuration::get('DF_INSTALLATION_ID', null, $idShopGroup, $idShop); 85 | $apiKey = \Configuration::get('DF_API_KEY'); 86 | $region = \Configuration::get('DF_REGION'); 87 | 88 | $data = DoofinderLayerApi::getInstallationData($installationID, $apiKey, $region); 89 | 90 | foreach ($data['config']['search_engines'] as $lang => $currencies) { 91 | foreach ($currencies as $currency => $hashid) { 92 | $hashidKey = 'DF_HASHID_' . strtoupper($currency) . '_' . strtoupper($lang); 93 | \Configuration::updateValue($hashidKey, $hashid, false, $idShopGroup, $idShop); 94 | } 95 | } 96 | 97 | return true; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Feed/DfCmsBuild.php: -------------------------------------------------------------------------------- 1 | idShop = $idShop; 56 | $this->idLang = $idLang; 57 | } 58 | 59 | /** 60 | * Sets the CMS page IDs to be included in the payload. 61 | * 62 | * @param array $arrayCmsPages list of CMS page IDs 63 | * 64 | * @return void 65 | */ 66 | public function setCmsPages($arrayCmsPages) 67 | { 68 | $this->cmsPages = $arrayCmsPages; 69 | } 70 | 71 | /** 72 | * Builds the CMS pages payload. 73 | * 74 | * - Iterates over each provided CMS page ID. 75 | * - Retrieves and sanitizes CMS data. 76 | * - Generates an array or JSON structure containing page information. 77 | * 78 | * @param bool $json whether to return the payload as JSON (true) or array (false) 79 | * 80 | * @return string|array JSON string or array containing CMS page data 81 | */ 82 | public function build($json = true) 83 | { 84 | $this->assign(); 85 | 86 | $payload = []; 87 | 88 | foreach ($this->cmsPages as $cms) { 89 | $payload[] = $this->buildCms($cms); 90 | } 91 | 92 | return $json ? json_encode($payload) : $payload; 93 | } 94 | 95 | /** 96 | * Assigns required PrestaShop context properties (e.g., link builder). 97 | * 98 | * @return void 99 | */ 100 | private function assign() 101 | { 102 | $this->link = \Context::getContext()->link; 103 | } 104 | 105 | /** 106 | * Builds a single CMS page's data array. 107 | * 108 | * - Cleans text fields using DfTools. 109 | * - Retrieves CMS link and content. 110 | * 111 | * @param int $idCms CMS page ID 112 | * 113 | * @return array associative array with CMS page details 114 | */ 115 | private function buildCms($idCms) 116 | { 117 | $cms = new \CMS($idCms, $this->idLang, $this->idShop); 118 | 119 | $c = []; 120 | $c['id'] = (string) $cms->id; 121 | $c['title'] = DfTools::cleanString($cms->meta_title); 122 | $c['description'] = DfTools::cleanString($cms->meta_description); 123 | $c['meta_title'] = DfTools::cleanString($cms->meta_title); 124 | $c['meta_description'] = DfTools::cleanString($cms->meta_description); 125 | $c['content'] = DfTools::cleanString($cms->content); 126 | $c['link'] = $this->link->getCMSLink($cms); 127 | 128 | return $c; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Api/DoofinderApiItems.php: -------------------------------------------------------------------------------- 1 | hashid = $hashid; 64 | $this->apiKey = $apiKey; 65 | $this->apiUrl = UrlManager::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region); 66 | $this->type = $type; 67 | } 68 | 69 | /** 70 | * Make a request to the API to update the specified items 71 | * 72 | * @param array|string $payload Items data to update. This can be an associative array or a JSON string. 73 | * 74 | * @return array Response from the API 75 | */ 76 | public function updateBulk($payload) 77 | { 78 | $endpoint = '/item/' . $this->hashid . '/' . $this->type . '?platform=prestashop&action=update'; 79 | 80 | $url = $this->apiUrl . $endpoint; 81 | 82 | return $this->post($url, $payload); 83 | } 84 | 85 | /** 86 | * Make a request to the API to delete the specified items 87 | * 88 | * @param string|null $payload Items IDs to delete 89 | * 90 | * @return array Response from the API 91 | */ 92 | public function deleteBulk($payload) 93 | { 94 | $endpoint = '/item/' . $this->hashid . '/' . $this->type . '?platform=prestashop&action=delete'; 95 | 96 | $url = $this->apiUrl . $endpoint; 97 | 98 | return $this->post($url, $payload); 99 | } 100 | 101 | /** 102 | * Execute a POST request to the Doofinder API with a JSON payload. 103 | * 104 | * @param string $url Full API endpoint URL 105 | * @param mixed $payload Data to send in the request body 106 | * 107 | * @return array|null Decoded JSON response from the API, or null if the request fails 108 | */ 109 | private function post($url, $payload) 110 | { 111 | $client = new EasyREST(); 112 | 113 | $response = $client->post( 114 | $url, 115 | $payload, 116 | null, 117 | null, 118 | 'application/json', 119 | ['Authorization: Token ' . $this->apiKey] 120 | ); 121 | 122 | return json_decode($response->response, true); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Feed/DfCategoryBuild.php: -------------------------------------------------------------------------------- 1 | idShop = $idShop; 56 | $this->idLang = $idLang; 57 | } 58 | 59 | /** 60 | * Set the categories to be included in the payload 61 | * 62 | * @param array $arrayCategories categories ids 63 | */ 64 | public function setCategories($arrayCategories) 65 | { 66 | $this->categories = $arrayCategories; 67 | } 68 | 69 | /** 70 | * Sets the category IDs to be included in the payload. 71 | * 72 | * @param bool $json whether to return the payload as JSON (true) or array (false) 73 | * 74 | * @return string|array JSON string or array containing category data 75 | */ 76 | public function build($json = true) 77 | { 78 | $this->assign(); 79 | 80 | $payload = []; 81 | 82 | foreach ($this->categories as $category) { 83 | if (\Category::categoryExists($category)) { 84 | $payload[] = $this->buildCategory($category); 85 | } 86 | } 87 | 88 | return $json ? json_encode($payload) : $payload; 89 | } 90 | 91 | /** 92 | * Assigns required PrestaShop context properties (e.g., link builder). 93 | * 94 | * @return void 95 | */ 96 | private function assign() 97 | { 98 | $this->link = \Context::getContext()->link; 99 | } 100 | 101 | /** 102 | * Builds a single category's data array. 103 | * 104 | * - Cleans text fields using DfTools. 105 | * - Retrieves category link and image link. 106 | * 107 | * @param int $idCategory category ID 108 | * 109 | * @return array Associative array with category details 110 | */ 111 | private function buildCategory($idCategory) 112 | { 113 | $category = new \Category($idCategory, $this->idLang, $this->idShop); 114 | 115 | $c = []; 116 | 117 | $tableName = 'category'; 118 | $tableName = (method_exists(get_class(new \ImageType()), 'getFormattedName')) ? \ImageType::getFormattedName($tableName) : $tableName . '_default'; 119 | 120 | $c['id'] = (string) $category->id; 121 | $c['title'] = DfTools::cleanString($category->name); 122 | $c['description'] = DfTools::cleanString($category->description); 123 | $c['meta_title'] = DfTools::cleanString($category->meta_title); 124 | $c['meta_description'] = DfTools::cleanString($category->meta_description); 125 | $c['link'] = $this->link->getCategoryLink($category); 126 | $c['image_link'] = $category->id_image ? $this->link->getCatImageLink($category->link_rewrite, $category->id_image, $tableName) : ''; 127 | 128 | return $c; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'PrestaShop\\Module\\Doofinder\\' => 28, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'PrestaShop\\Module\\Doofinder\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/src', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | 'Doofinder' => __DIR__ . '/../..' . '/doofinder.php', 26 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApi' => __DIR__ . '/../..' . '/src/Api/DoofinderApi.php', 27 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiIndex' => __DIR__ . '/../..' . '/src/Api/DoofinderApiIndex.php', 28 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiItems' => __DIR__ . '/../..' . '/src/Api/DoofinderApiItems.php', 29 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderApiSingleScript' => __DIR__ . '/../..' . '/src/Api/DoofinderApiSingleScript.php', 30 | 'PrestaShop\\Module\\Doofinder\\Api\\DoofinderLayerApi' => __DIR__ . '/../..' . '/src/Api/DoofinderLayerApi.php', 31 | 'PrestaShop\\Module\\Doofinder\\Api\\EasyREST' => __DIR__ . '/../..' . '/src/Api/EasyREST.php', 32 | 'PrestaShop\\Module\\Doofinder\\Autoloader' => __DIR__ . '/../..' . '/src/Autoloader.php', 33 | 'PrestaShop\\Module\\Doofinder\\Configuration\\DoofinderConfig' => __DIR__ . '/../..' . '/src/Configuration/DoofinderConfig.php', 34 | 'PrestaShop\\Module\\Doofinder\\Core\\DoofinderConstants' => __DIR__ . '/../..' . '/src/Core/DoofinderConstants.php', 35 | 'PrestaShop\\Module\\Doofinder\\Core\\DoofinderScript' => __DIR__ . '/../..' . '/src/Core/DoofinderScript.php', 36 | 'PrestaShop\\Module\\Doofinder\\Core\\SearchEngine' => __DIR__ . '/../..' . '/src/Core/SearchEngine.php', 37 | 'PrestaShop\\Module\\Doofinder\\Core\\UpdateOnSave' => __DIR__ . '/../..' . '/src/Core/UpdateOnSave.php', 38 | 'PrestaShop\\Module\\Doofinder\\Exception\\DoofinderException' => __DIR__ . '/../..' . '/src/Exception/DoofinderException.php', 39 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfCategoryBuild' => __DIR__ . '/../..' . '/src/Feed/DfCategoryBuild.php', 40 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfCmsBuild' => __DIR__ . '/../..' . '/src/Feed/DfCmsBuild.php', 41 | 'PrestaShop\\Module\\Doofinder\\Feed\\DfProductBuild' => __DIR__ . '/../..' . '/src/Feed/DfProductBuild.php', 42 | 'PrestaShop\\Module\\Doofinder\\Installer\\DoofinderInstallation' => __DIR__ . '/../..' . '/src/Installer/DoofinderInstallation.php', 43 | 'PrestaShop\\Module\\Doofinder\\Manager\\FormManager' => __DIR__ . '/../..' . '/src/Manager/FormManager.php', 44 | 'PrestaShop\\Module\\Doofinder\\Manager\\HookManager' => __DIR__ . '/../..' . '/src/Manager/HookManager.php', 45 | 'PrestaShop\\Module\\Doofinder\\Manager\\LanguageManager' => __DIR__ . '/../..' . '/src/Manager/LanguageManager.php', 46 | 'PrestaShop\\Module\\Doofinder\\Manager\\UrlManager' => __DIR__ . '/../..' . '/src/Manager/UrlManager.php', 47 | 'PrestaShop\\Module\\Doofinder\\Utils\\DfDb' => __DIR__ . '/../..' . '/src/Utils/DfDb.php', 48 | 'PrestaShop\\Module\\Doofinder\\Utils\\DfTools' => __DIR__ . '/../..' . '/src/Utils/DfTools.php', 49 | 'PrestaShop\\Module\\Doofinder\\View\\DoofinderAdminPanelView' => __DIR__ . '/../..' . '/src/View/DoofinderAdminPanelView.php', 50 | ); 51 | 52 | public static function getInitializer(ClassLoader $loader) 53 | { 54 | return \Closure::bind(function () use ($loader) { 55 | $loader->prefixLengthsPsr4 = ComposerStaticInitddb45daa7f46128bfeb9153853763869::$prefixLengthsPsr4; 56 | $loader->prefixDirsPsr4 = ComposerStaticInitddb45daa7f46128bfeb9153853763869::$prefixDirsPsr4; 57 | $loader->classMap = ComposerStaticInitddb45daa7f46128bfeb9153853763869::$classMap; 58 | 59 | }, null, ClassLoader::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Utils/DfDb.php: -------------------------------------------------------------------------------- 1 | _DB_SERVER_, 'user' => _DB_USER_, 'password' => _DB_PASSWD_, 'database' => _DB_NAME_], /* MySQL Master server */ 64 | ]; 65 | } 66 | 67 | if (!$master) { 68 | self::loadSlaveServers(); 69 | } 70 | 71 | $totalServers = count(self::$_servers); 72 | if ($master || $totalServers == 1) { 73 | $idServer = 0; 74 | } else { 75 | ++$id; 76 | $idServer = ($totalServers > 2 && ($id % $totalServers) != 0) ? $id % $totalServers : 1; 77 | } 78 | 79 | $class = \Db::getClass(); 80 | $instance = new $class( 81 | self::$_servers[$idServer]['server'], 82 | self::$_servers[$idServer]['user'], 83 | self::$_servers[$idServer]['password'], 84 | self::$_servers[$idServer]['database'], 85 | false 86 | ); 87 | $link = $instance->connect(); 88 | if ('DbPDO' === $class && method_exists($link, 'setAttribute')) { 89 | // Set the PDO attribute to not use buffered queries 90 | // This is needed to avoid memory issues with large datasets 91 | 92 | $link->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); 93 | } 94 | 95 | return $instance; 96 | } 97 | 98 | /** 99 | * Loads configuration settings for slave servers if needed. 100 | * 101 | * @return void 102 | */ 103 | protected static function loadSlaveServers() 104 | { 105 | if (self::$_slave_servers_loaded) { 106 | return; 107 | } 108 | 109 | // Add here your slave(s) server(s) in this file 110 | if (file_exists(_PS_ROOT_DIR_ . '/config/db_slave_server.inc.php')) { 111 | self::$_servers = array_merge(self::$_servers, require (_PS_ROOT_DIR_ . '/config/db_slave_server.inc.php')); 112 | } 113 | 114 | self::$_slave_servers_loaded = true; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /upgrade/upgrade-4.12.0.php: -------------------------------------------------------------------------------- 1 | 22 | * @copyright 2007-2022 PrestaShop SA and Contributors 23 | * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) 24 | * International Registered Trademark & Property of PrestaShop SA 25 | */ 26 | 27 | use PrestaShop\Module\Doofinder\Configuration\DoofinderConfig; 28 | 29 | if (!defined('_PS_VERSION_')) { 30 | exit; 31 | } 32 | 33 | /** 34 | * Upgrades the Doofinder module to version 4.12.0. 35 | * 36 | * This upgrade step removes obsolete PHP files from previous versions 37 | * to prevent conflicts, dead code, or deprecated behavior. 38 | * Logs progress using DoofinderConfig::debug. 39 | * 40 | * @param Doofinder $module the Doofinder module instance being upgraded 41 | * 42 | * @return bool always returns true, regardless of whether files were successfully deleted 43 | */ 44 | function upgrade_module_4_12_0($module) 45 | { 46 | DoofinderConfig::debug('Initiating 4.12.0 upgrade'); 47 | 48 | // Delete old *.php files 49 | unlinkFiles(); 50 | DoofinderConfig::debug('Old files deleted successfully.'); 51 | 52 | return true; 53 | } 54 | 55 | /** 56 | * Deletes legacy files no longer needed by the Doofinder module. 57 | * 58 | * Iterates through a predefined list of obsolete files, checks their existence, 59 | * and attempts to remove them from the module directory. Each deletion attempt 60 | * is logged, including failures. 61 | * 62 | * @return void 63 | */ 64 | function unlinkFiles() 65 | { 66 | $files = [ 67 | 'autoloader.php', 68 | 'cache.php', 69 | 'config.php', 70 | 'doofinder-ajax.php', 71 | 'feed.php', 72 | 'landing.php', 73 | 'controllers/front/landingEntrypoint.php', 74 | /* Files from older versions. */ 75 | 'lib/doofinder_api.php', 76 | 'lib/doofinder_api_landing.php', 77 | 'views/templates/front/script.tpl', 78 | 'lib/doofinder_api_index.php', 79 | 'lib/doofinder_api_items.php', 80 | 'lib/doofinder_layer_api.php', 81 | 'lib/doofinder_api_unique_script.php', 82 | 'lib/doofinder_installation.php', 83 | 'lib/dfTools.class.php', 84 | 'lib/dfCategory_build.php', 85 | 'lib/dfCms_build.php', 86 | 'lib/dfProduct_build.php', 87 | 'lib/DfCategoryBuild.php', 88 | 'lib/DfCmsBuild.php', 89 | 'lib/DfProductBuild.php', 90 | 'lib/DfTools.php', 91 | 'lib/DoofinderAdminPanelView.php', 92 | 'lib/DoofinderApi.php', 93 | 'lib/DoofinderApiIndex.php', 94 | 'lib/DoofinderApiItems.php', 95 | 'lib/DoofinderApiLanding.php', 96 | 'lib/DoofinderApiUniqueScript.php', 97 | 'lib/DoofinderConfig.php', 98 | 'lib/DoofinderConstants.php', 99 | 'lib/DoofinderException.php', 100 | 'lib/DoofinderInstallation.php', 101 | 'lib/DoofinderLayerApi.php', 102 | 'lib/DoofinderResults.php', 103 | 'lib/DoofinderScript.php', 104 | 'lib/EasyREST.php', 105 | 'lib/FormManager.php', 106 | 'lib/HookManager.php', 107 | 'lib/LanguageManager.php', 108 | 'lib/SearchEngine.php', 109 | 'lib/UpdateOnSave.php', 110 | 'lib/UrlManager.php', 111 | ]; 112 | 113 | foreach ($files as $fileName) { 114 | $filePath = realpath(_PS_MODULE_DIR_ . 'doofinder' . DIRECTORY_SEPARATOR . $fileName); 115 | if (!file_exists($filePath) || is_dir($filePath)) { 116 | continue; 117 | } 118 | 119 | if (!unlink($filePath)) { 120 | DoofinderConfig::debug('Couldn\'t delete file: ' . $filePath); 121 | continue; 122 | } 123 | 124 | DoofinderConfig::debug('Deleted file: ' . $filePath); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Manager/UrlManager.php: -------------------------------------------------------------------------------- 1 | domain_ssl : 'http://' . $shop->domain; 45 | 46 | return $url . self::_getShopBaseURI($shop); 47 | } 48 | 49 | /** 50 | * Build feed urls 51 | * 52 | * @param int $shopId 53 | * @param int $language 54 | * 55 | * @return string 56 | */ 57 | public static function getFeedUrl($shopId, $language, $currency = null) 58 | { 59 | $shop = new \Shop($shopId); 60 | \Context::getContext()->shop = $shop; 61 | 62 | $params = [ 63 | 'language' => \Tools::strtoupper($language), 64 | 'dfsec_hash' => \Configuration::get('DF_API_KEY'), 65 | ]; 66 | 67 | if ($currency) { 68 | $params['currency'] = \Tools::strtoupper($currency); 69 | } 70 | 71 | $link = \Context::getContext()->link->getModuleLink(DoofinderConstants::NAME, 'feed', $params); 72 | 73 | /* 74 | * Cleans redundant parameters in URLs for PrestaShop 1.5.3 75 | * 76 | * In PrestaShop 1.5.3, 'module' and 'controller' parameters are maintained in URLs even when friendly URLs are 77 | * enabled, which causes navigation errors. 78 | * This function removes these parameters when they are not necessary, specifically when the 'fc=module' 79 | */ 80 | if (\Configuration::get('PS_REWRITING_SETTINGS')) { 81 | $link = preg_replace('/[&?](module|controller)=[^&]+/', '', $link); 82 | } 83 | 84 | return $link; 85 | } 86 | 87 | /** 88 | * Get Process Callback URL 89 | * 90 | * @return string 91 | */ 92 | public static function getProcessCallbackUrl($shopId) 93 | { 94 | $shop = new \Shop($shopId); 95 | \Context::getContext()->shop = $shop; 96 | 97 | return \Context::getContext()->link->getModuleLink(DoofinderConstants::NAME, 'callback', []); 98 | } 99 | 100 | /** 101 | * Get install endpoint URL 102 | * 103 | * @param string $region 104 | * 105 | * @return string 106 | */ 107 | public static function getInstallUrl($region) 108 | { 109 | return self::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region, '/install'); 110 | } 111 | 112 | /** 113 | * Get update feed endpoint URL 114 | * 115 | * @param string $region 116 | * 117 | * @return string 118 | */ 119 | public static function getUpdateFeedUrl($region) 120 | { 121 | return self::getRegionalUrl(DoofinderConstants::DOOPLUGINS_REGION_URL, $region, '/prestashop/feed-url-update'); 122 | } 123 | 124 | /** 125 | * Gets an URL with its region filled in. You can also append a path (optional). 126 | * If the region is provided as '' it will return a regionless URL. 127 | * 128 | * @return string 129 | */ 130 | public static function getRegionalUrl($url, $region, $pathToAppend = '') 131 | { 132 | if (empty($region)) { 133 | return sprintf($url, '') . $pathToAppend; 134 | } 135 | 136 | return sprintf($url, $region . '-') . $pathToAppend; 137 | } 138 | 139 | /** 140 | * Get shop base URI 141 | * 142 | * @param \Shop $shop 143 | * 144 | * @return string 145 | */ 146 | private static function _getShopBaseURI($shop) 147 | { 148 | return $shop->physical_uri . $shop->virtual_uri; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /views/js/doofinder-onboarding.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | */ 14 | 15 | const shopDomain = 16 | location.protocol + 17 | "//" + 18 | location.hostname + 19 | (location.port ? ":" + location.port : ""); 20 | 21 | $(document).ready(function () { 22 | window.addEventListener( 23 | "message", 24 | (event) => { 25 | const doofinder_regex = /.*\.doofinder\.com/gm; 26 | //Check that the sender is doofinder 27 | if (!doofinder_regex.test(event.origin)) return; 28 | if (event.data && 'string' === typeof event.data) { 29 | data = event.data.split("|"); 30 | event_name = data[0]; 31 | event_data = JSON.parse(atob(data[1])); 32 | processMessage(event_name, event_data); 33 | } 34 | }, 35 | false 36 | ); 37 | }); 38 | 39 | function popupDoofinder(type) { 40 | var params = 41 | "?" + 42 | paramsPopup + 43 | "&mktcod=PSHOP&utm_source=prestashop_module&utm_campaing=freetrial&utm_content=autoinstaller"; 44 | if ('undefined' === typeof doofinderAdminUrl) { 45 | doofinderAdminUrl = 'https://admin.doofinder.com'; 46 | } 47 | var domain = doofinderAdminUrl + "/plugins/" + type + "/prestashop"; 48 | popupCenter(domain + params, "Doofinder", 400, 850); 49 | } 50 | 51 | function initializeAutoinstallerMessages() { 52 | $(".choose-installer").hide(); 53 | $(".loading-installer").show(); 54 | var loop = setInterval(function () { 55 | if (!$(".loading-installer ul li.active").is(":last-child")) { 56 | $(".loading-installer ul li.active") 57 | .removeClass("active") 58 | .next() 59 | .addClass("active"); 60 | } else { 61 | clearInterval(loop); 62 | } 63 | }, 4000); 64 | } 65 | 66 | function launchAutoinstaller() { 67 | $("#installation-errors").empty(); 68 | initializeAutoinstallerMessages(); 69 | let post_data = { 70 | autoinstaller: 1, 71 | token: installerToken, 72 | }; 73 | 74 | if (typeof shop_id != "undefined") { 75 | post_data["shop_id"] = shop_id; 76 | } 77 | 78 | $.post(ajaxUrl, post_data, function (data) { 79 | if (data.success) { 80 | //reload without resending post data 81 | history.go(0); 82 | } else { 83 | if ("string" === typeof data.errors) { 84 | $(".loading-installer").hide(); 85 | $("#installation-errors").append("
  • " + data.errors + "
  • "); 86 | } else if (data.errors && data.errors.length > 0) { 87 | $(".loading-installer").hide(); 88 | for (const error in data.errors) { 89 | if (Object.hasOwnProperty.call(data.errors, error)) { 90 | $("#installation-errors").append("
  • " + data.errors[error] + "
  • "); 91 | } 92 | } 93 | } 94 | showErrorMessage(); 95 | } 96 | }); 97 | } 98 | 99 | function popupCenter(url, title, w, h) { 100 | const dualScreenLeft = 101 | window.screenLeft !== undefined ? window.screenLeft : window.screenX; 102 | const dualScreenTop = 103 | window.screenTop !== undefined ? window.screenTop : window.screenY; 104 | 105 | const width = window.innerWidth 106 | ? window.innerWidth 107 | : document.documentElement.clientWidth 108 | ? document.documentElement.clientWidth 109 | : screen.width; 110 | const height = window.innerHeight 111 | ? window.innerHeight 112 | : document.documentElement.clientHeight 113 | ? document.documentElement.clientHeight 114 | : screen.height; 115 | 116 | const systemZoom = width / window.screen.availWidth; 117 | const left = (width - w) / 2 / systemZoom + dualScreenLeft; 118 | const top = (height - h) / 2 / systemZoom + dualScreenTop; 119 | 120 | const newWindow = window.open( 121 | url, 122 | title, 123 | ` 124 | scrollbars=yes, 125 | width=${w / systemZoom}, 126 | height=${h / systemZoom}, 127 | top=${top}, 128 | left=${left}, 129 | status=0, 130 | toolbar=0, 131 | location=0 132 | ` 133 | ); 134 | 135 | if (window.focus) newWindow.focus(); 136 | return newWindow; 137 | } 138 | 139 | function processMessage(name, data) { 140 | if (name === "set_doofinder_data") send_connect_data(data); 141 | } 142 | 143 | function send_connect_data(data) { 144 | $.ajax({ 145 | type: "POST", 146 | dataType: "json", 147 | url: configUrl, 148 | data: data, 149 | success: function (response) { 150 | if (response.success) { 151 | launchAutoinstaller(); 152 | } else { 153 | showConnectionError(); 154 | } 155 | }, 156 | error: function (data) { 157 | showConnectionError(); 158 | }, 159 | }); 160 | } 161 | 162 | function showConnectionError() { 163 | $(".message-popup").show(); 164 | setTimeout(function () { 165 | $(".message-popup").hide(); 166 | }, 10000); 167 | } 168 | 169 | function showErrorMessage() { 170 | $(".loading-installer").hide(); 171 | $(".message-error").show(); 172 | } 173 | -------------------------------------------------------------------------------- /controllers/front/config.php: -------------------------------------------------------------------------------- 1 | ajax = true; 52 | 53 | header('Content-Type:application/json; charset=utf-8'); 54 | 55 | $module = Module::getInstanceByName('doofinder'); 56 | $autoinstallerToken = Tools::getValue('token'); 57 | if ($autoinstallerToken) { 58 | $link = Context::getContext()->link; 59 | $redirect = $link->getPageLink('module-doofinder-config'); 60 | $tmpToken = DfTools::encrypt($redirect); 61 | if ($tmpToken == $autoinstallerToken) { 62 | $apiToken = Tools::getValue('api_token'); 63 | $apiEndpoint = Tools::getValue('api_endpoint'); 64 | $adminEndpoint = Tools::getValue('admin_endpoint'); 65 | if ($apiToken) { 66 | DoofinderConfig::saveApiConfig($apiToken, $apiEndpoint, $adminEndpoint); 67 | } 68 | echo json_encode(['success' => true]); 69 | exit; 70 | } else { 71 | header('HTTP/1.1 403 Forbidden', true, 403); 72 | $msgError = 'Forbidden access.' 73 | . ' Token for autoinstaller invalid.'; 74 | exit($msgError); 75 | } 76 | } 77 | 78 | $languages = []; 79 | $configurations = []; 80 | $currencies = array_keys(DfTools::getAvailableCurrencies()); 81 | 82 | $display_prices = (bool) Configuration::get('DF_GS_DISPLAY_PRICES'); 83 | $prices_with_taxes = (bool) Configuration::get('DF_GS_PRICES_USE_TAX'); 84 | 85 | foreach (Language::getLanguages(true, $this->context->shop->id) as $lang) { 86 | $lang = Tools::strtoupper($lang['iso_code']); 87 | $currency = DfTools::getCurrencyForLanguage($lang); 88 | 89 | $languages[] = $lang; 90 | $configurations[$lang] = [ 91 | 'language' => $lang, 92 | 'currency' => Tools::strtoupper($currency->iso_code), 93 | 'prices' => $display_prices, 94 | 'taxes' => $prices_with_taxes, 95 | ]; 96 | } 97 | 98 | $force_ssl = (Configuration::get('PS_SSL_ENABLED') && Configuration::get('PS_SSL_ENABLED_EVERYWHERE')); 99 | $shop = $this->context->shop; 100 | $base = (($force_ssl) ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain); 101 | 102 | $cfg = [ 103 | 'platform' => [ 104 | 'name' => 'Prestashop', 105 | 'version' => _PS_VERSION_, 106 | ], 107 | 'module' => [ 108 | 'version' => DoofinderConstants::VERSION, 109 | 'feed' => $base . $shop->getBaseURI() . 'module/doofinder/feed', 110 | 'options' => [ 111 | 'language' => $languages, 112 | 'currency' => $currencies, 113 | ], 114 | 'configuration' => $configurations, 115 | ], 116 | ]; 117 | 118 | $jsonCfg = DfTools::jsonEncode($cfg); 119 | if (method_exists($this, 'ajaxRender')) { 120 | $this->ajaxRender($jsonCfg); 121 | exit; 122 | } elseif (method_exists($this, 'ajaxDie')) { 123 | // Workaround for PS 1.6 as ajaxRender is not available 124 | $this->ajaxDie($jsonCfg); 125 | } else { 126 | // Workaround for PS 1.5 127 | echo $jsonCfg; 128 | exit; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doofinder-prestashop 2 | 3 | Plugin that allows to configure the [Doofinder](https://www.doofinder.com) search service in a Prestashop 1.5 store with less effort than configuring it from scratch. 4 | 5 | > [!IMPORTANT] 6 | > If you experience any issue with the module, please [contact Doofinder Support](https://support.doofinder.com/pages/contact-us) from the Doofinder website. 7 | 8 | ## How to install and configure Doofinder 9 | 10 | Refer to [Doofinder Support Documentation for Prestashop](https://support.doofinder.com/plugins/prestashop/installation-guide/installation-steps-prestashop) for the latest and up to date instructions. 11 | 12 | ## Module Compatibility 13 | 14 | ### PHP 15 | 16 | The minimum php required for this module is php 5.4 17 | The maximum php version tested is 8.4 18 | 19 | ### PrestaShop 20 | 21 | From 1.5.0.17 to latest version 22 | 23 | For more compatibility details check the following documentation 24 | 25 | - [Prestashop 1.x](https://devdocs.prestashop-project.org/1.7/basics/installation/system-requirements/) system requirements. 26 | - [Prestashop 8](https://devdocs.prestashop-project.org/8/basics/installation/system-requirements/) system requirements. 27 | - [Prestashop 9](https://devdocs.prestashop-project.org/9/basics/installation/system-requirements/) system requirements. 28 | 29 | ## Docker Environment 30 | 31 | ### Configure ngrok 32 | 33 | In order to be able to create an account or login to an existing Doofinder account during the module initial setup, you will have to expose your local webserver to the internet (to receive a callback). 34 | 35 | To do so, you can use, for example, the utility ngrok: https://dashboard.ngrok.com/get-started/setup 36 | 37 | Once the external URL is created, simply set the `BASE_URL` environment variable (see [Environment Variables](#environment-variables)). 38 | 39 | So, when the installation process finished, instead of accessing to `https://localhost:4011` you will use your url, for example, `https://forcibly-ethical-apple.ngrok-free.app`). 40 | Notice that you'll need to specify the 4011 port when executing ngrok. 41 | 42 | ### Environment variables 43 | 44 | > [!TIP] 45 | > You can create an `.env.local` file to override the environment variables defined in `.env` such as PrestaShop installation data to fit your needs. 46 | 47 | For example, below is a base `.env.local` file: 48 | 49 | ```bash 50 | #PrestaShop setup configuration data 51 | BASE_URL=your-url.ngrok-free.app 52 | PS_ENV=dev 53 | 54 | ``` 55 | 56 | The `Makefile` automatically overrides `.env` vars with the ones found in `.env.local`. 57 | 58 | > [!IMPORTANT] 59 | > The `Makefile` internally appends `--env-file .env --env-file .env.local` to `docker compose` command for properly configuring container environment. So take it into account when interacting directly with `docker compose`. 60 | 61 | ### Initial setup 62 | 63 | You can set up a fresh PrestaShop installation using the provided `Makefile` target `init`. This command will: 64 | 65 | - Pulls and build a PrestaShop docker image with xdebug extension and maybe other tweaks. This build is configurable using the environment variables `PHP_VERSION` and `PS_VERSION` environment variables. 66 | - Starts the containers 67 | - Runs the installer script with the defined environment variables. 68 | 69 | Finally, PrestaShop is installed and will be running at `https://BASE_URL`. 70 | 71 | You can install the Doofinder module through the admin or execute `make doofinder-upgrade`. 72 | 73 | The admin panel will be available at `https://BASE_URL/PS_FOLDER_ADMIN`. Admin credentials are defined in the `.env`, if you used the `env.example` would be: 74 | 75 | - User: `test@example.com` 76 | - Pass: `admin123` 77 | 78 | > [!NOTE] 79 | > Keep in mind that for versions prior to 1.7 PrestaShop will ask you to delete the `install` folder and rename the `admin` folder located in the `html` directory. 80 | > For newer versions this is done automatically, using the value on the environment variable `PS_FOLDER_ADMIN` (by default, `/4dm1n`). 81 | 82 | ## Autoload 83 | 84 | Starting from version 6.0.0, this plugin uses the Composer autoloader, which is generated by running `composer dump-autoload` via the `make dump-autoload` command. This target is always executed after `make doofinder-configure`. If you create a new class, you must run `make dump-autoload` manually to make the class discoverable by PrestaShop. 85 | 86 | ## Version Upgrade 87 | 88 | To upgrade the package version, simply edit the `PLUGIN_VERSION` environment variable in the `.env` file and run `make doofinder-configure`. This will update all the necessary files. However, you must manually edit the `doofinder.php` file, as PrestaShop requires that `$this->version` be a hardcoded value. 89 | 90 | ## Xdebug ready to use 91 | 92 | If you wish to debug your new PrestaShop installation, the `XDEBUG_CONFIG` and `XDEBUG_MODE` environment variables are already configured in `docker-compose.yml`. Simply configure your IDE accordingly and have fun! 93 | 94 | ## Uninstall the module 95 | 96 | You can remove the Doofinder module using this straightforward method: 97 | 98 | ```sh 99 | make doofinder-uninstall 100 | ``` 101 | 102 | ## Test another versions of the module 103 | 104 | Change your branch to the tag that you want inside package directory 105 | 106 | ```sh 107 | make doofinder-upgrade 108 | ``` 109 | 110 | ## Backup and Restore Database 111 | 112 | During development, it is sometimes useful to create a data snapshot before performing an action. 113 | 114 | - To create a database dump, use: 115 | ```sh 116 | make db-backup [prefix=_some_state] 117 | ``` 118 | - To restore a previous state, run: 119 | ```sh 120 | make db-restore file=backup_file.sql.gz 121 | ``` 122 | 123 | ## Test other PrestaShop versions 124 | 125 | You can test different Prestashop versions along with different PHP versions. These are the latest combinations available gathered from [PrestaShop Docker Hub](https://hub.docker.com/r/prestashop/prestashop/tags) 126 | 127 | | PrestaShop | PHP | 128 | | ---------- | ----------------------- | 129 | | 8.2.1 | 8.1, 8.0, 7.4, 7.3, 7.2 | 130 | | 8.1.7 | 8.1, 8.0, 7.4, 7.3, 7.2 | 131 | | 8.0.5 | 8.1, 8.0, 7.4, 7.3, 7.2 | 132 | | 1.7.8.9 | 7.4, 7.3, 7.2, 7.1 | 133 | | 1.6 | 7.2, 7.1, 7.0, 5.6 | 134 | | 1.5[^ps15] | 7.2, 7.1, 7.0, 5.6, 5.5 | 135 | 136 | [^ps15]: Prestashop 1.5: This version is patched to allow auto installation (See Dockerfile). MySQL version must be 5.5. Must be used without SSL. 137 | -------------------------------------------------------------------------------- /views/js/add-to-cart/doofinder-add_to_cart_ps17.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | */ 14 | 15 | class DoofinderAddToCartError extends Error { 16 | constructor(reason, status = "") { 17 | const message = "Error adding an item to the cart. Reason: " + reason + ". Status code: " + status; 18 | super(message); 19 | this.name = "DoofinderAddToCartError"; 20 | } 21 | } 22 | 23 | //implementation of "add to cart" functionality for prestashop 1.7.x 24 | 25 | let dfAddToCart = (cartOptions) => { 26 | 27 | let IdProductCart; 28 | let IdCustomization; 29 | if (cartOptions.productID.includes('VAR-')) { 30 | IdProductCart = cartOptions.group_id; 31 | IdCustomization = cartOptions.productID.replace('VAR-', ''); 32 | } else { 33 | IdProductCart = cartOptions.productID; 34 | IdCustomization = cartOptions.customizationID; 35 | } 36 | 37 | /** 38 | * 1º create the necessary form to add to cart with the required inputs. 39 | * (the "name" attribute of the inputs is necessary to trigger the event that prestashop uses natively 40 | * for "add to cart"). 41 | */ 42 | let form = document.createElement("form"); 43 | Object.assign(form, { 44 | method: "post", 45 | action: cartOptions.cartURL, 46 | style: "display: none;" 47 | }); 48 | 49 | let quantityInput = document.createElement("input"); 50 | Object.assign(quantityInput, { 51 | type : "number", 52 | name : "qty", 53 | value : cartOptions.quantity, 54 | min : 1 55 | }); 56 | 57 | let productAttributeInput = document.createElement("input"); 58 | Object.assign(productAttributeInput, { 59 | type : "hidden", 60 | name : "id_product_attribute", 61 | value : IdCustomization 62 | }); 63 | 64 | let productInput = document.createElement("input"); 65 | Object.assign(productInput, { 66 | type : "hidden", 67 | name : "id_product", 68 | value : IdProductCart 69 | }); 70 | 71 | let tokenInput = document.createElement("input"); 72 | Object.assign(tokenInput, { 73 | type : "hidden", 74 | name : "token", 75 | value : cartOptions.cartToken 76 | }); 77 | 78 | let submit = document.createElement("input"); 79 | submit.setAttribute("type", "submit"); 80 | submit.setAttribute("data-button-action", "add-to-cart"); 81 | 82 | form.appendChild(quantityInput); 83 | form.appendChild(productAttributeInput); 84 | form.appendChild(productInput); 85 | form.appendChild(tokenInput); 86 | form.appendChild(submit); 87 | 88 | /** 89 | * 2º Add to DOM the form. 90 | */ 91 | document.body.appendChild(form); 92 | 93 | if (typeof prestashop !== 'undefined' && typeof cartOptions.statusPromise !== 'undefined') { 94 | 95 | // It's triggered everytime the cart is updated. 96 | prestashop.on('updateCart', function (event) { 97 | // This should be trigger only if the cart has been updated from the layer. Otherwise, the statusPromise will be undefined. 98 | if (typeof cartOptions.statusPromise === 'undefined' || 99 | !event || 100 | !event.reason || 101 | !event.resp) { 102 | return; 103 | } 104 | 105 | if (event.resp.success) { 106 | cartOptions.statusPromise.resolve("The item has been successfully added to the cart."); 107 | } else { 108 | // The event.resp.errors can be "" or an array 109 | errorList = Array.isArray(event.resp.errors) ? event.resp.errors.join(", ") : event.resp.errors; 110 | console.warn("Error adding to the cart: " + errorList); 111 | cartOptions.statusPromise.reject(new DoofinderAddToCartError(errorList)); 112 | /* Special case: the product cannot be added because is customizable. 113 | I don't know if it's the best way to detect it, but at least it's better than 114 | detecting it from the error message. In this case, the user should be redirected 115 | to the product detail page */ 116 | if (0 === event.resp.quantity && 'undefined' === typeof event.reason.cart) { 117 | window.location.href = cartOptions.itemLink; 118 | } 119 | } 120 | }); 121 | 122 | prestashop.on('handleError', function (event) { 123 | // This should be trigger only if the cart has been updated from the layer. Otherwise, the statusPromise will be undefined. 124 | if (typeof cartOptions.statusPromise === 'undefined') { 125 | return; 126 | } 127 | 128 | // Possible errors variations coming from Add to Cart 129 | if (event && event.eventType && ('updateProductInCart' === event.eventType || 130 | 'updateShoppingCart' === event.eventType || 131 | 'updateCart' === event.eventType || 132 | 'updateProductInCart' === event.eventType || 133 | 'updateProductQuantityInCart' === event.eventType || 134 | 'addProductToCart' === event.eventType)) { 135 | 136 | console.group(`Error of type '${event.eventType}' adding item to the cart.`); 137 | 138 | // If the event is addProductToCart, redirect to the item link. 139 | // This will prevent silent errors from both customizable products and products with minimal quantity. 140 | if (event.eventType === 'addProductToCart') { 141 | console.error("Redirecting user to the item page."); 142 | window.location.href = item_link; 143 | } 144 | 145 | /* Empirically I've have reproduced this error after loading another page in a specific moment */ 146 | if (0 === event.resp.readyState) { 147 | console.error("The connection was interrupted while adding to the cart"); 148 | } 149 | 150 | console.groupEnd(); 151 | cartOptions.statusPromise.reject(new DoofinderAddToCartError(message)); 152 | } 153 | }); 154 | } 155 | 156 | /** 157 | * 3º clicks on the submit (does not work if you invoke the submit event) 158 | * and removes the form from the DOM 159 | */ 160 | submit.click(); 161 | form.remove(); 162 | } 163 | 164 | let doofinderManageCart = (cartOptions) => { 165 | dfAddToCart(cartOptions); 166 | } 167 | -------------------------------------------------------------------------------- /views/templates/admin/onboarding_tab.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | * NOTICE OF LICENSE 3 | * 4 | * This file is licenced under the Software License Agreement. 5 | * With the purchase or the installation of the software in your application 6 | * you accept the licence agreement. 7 | * 8 | * You must not modify, adapt or create derivative works of this source code 9 | * 10 | * @author Doofinder 11 | * @copyright Doofinder 12 | * @license GPLv3 13 | *} 14 | 15 |
    16 |
    17 |
    18 |
    19 |

    20 | {if $checkConnection} 21 | {l s='Connection with Doofinder successful.' mod='doofinder'} 22 | {else} 23 | {l s='You cannot connect with Doofinder. Please contact your server provider to check your web server internet connection or firewall.' mod='doofinder'} 24 | {/if} 25 |

    26 |
    27 | 35 | 43 | 55 |
    56 | 57 |
    58 | 59 | {if $is_new_shop} 60 | 63 | {else} 64 | 68 | 72 |
    73 |

    74 | {* Go to DoofinderAdminPanelView class to see the details about the formation of the $skipurl *} 75 | {l s='I want to skip the autoinstaller, take me to the module manual configuration.' mod='doofinder'} 76 |

    77 |
    78 | {/if} 79 |
    80 |
    81 |
    82 |

    {l s='Add a smart search engine to your e-commerce in 5 minutes and with no programming' mod='doofinder'}

    83 |
    84 |
    85 |

    {l s='ABOUT DOOFINDER' mod='doofinder'}:

    86 |
    87 |

    {l s='Doofinder provides you with an instant search layer to display your products when your visitors use the Doofinder script' mod='doofinder'}.

    88 |
    89 |

    {l s='Your customers then have the opportunity to preview your products, filter them and choose the desired product. Upon hitting enter, doofinder will also power the results page' mod='doofinder'}.

    90 |
    91 |

    {l s='Among our features are' mod='doofinder'}:

    92 |
    93 |
    94 |
    · {l s='Detailed reports on visitor search behaviour' mod='doofinder'}.
    95 |
    · {l s='Faceted search option' mod='doofinder'}.
    96 |
    · {l s='Learning behaviour' mod='doofinder'}.
    97 |
    · {l s='Merchandising power to set a products positioning' mod='doofinder'}.
    98 |
    · {l s='Banner feature for advertising and promoting products, brands and events' mod='doofinder'}.
    99 |
    · {l s='Options to redirect users to content pages' mod='doofinder'}.
    100 |
    · {l s='Held on our servers for a faster page load time' mod='doofinder'}.
    101 |
    102 |
    103 |

    {l s='More than 5000 e-commerce sites over 35 countries are already using it' mod='doofinder'}. 104 |

    105 |
    106 |

    {l s='What are you waiting for?' mod='doofinder'}

    107 |
    108 |

    {l s='Test our solution for 30 days, for free!' mod='doofinder'}

    109 |
    110 |
    111 |
    112 |
    113 |
    114 | 172 | 183 | 184 | -------------------------------------------------------------------------------- /src/Manager/HookManager.php: -------------------------------------------------------------------------------- 1 | module = $module; 49 | } 50 | 51 | /** 52 | * Registers the hooks when the plugin is installed 53 | * 54 | * @return bool 55 | */ 56 | public function registerHooks() 57 | { 58 | $result = $this->module->registerHook('displayHeader') 59 | && $this->module->registerHook('moduleRoutes') 60 | && $this->module->registerHook('actionProductSave') 61 | && $this->module->registerHook('actionProductDelete') 62 | && $this->module->registerHook('actionObjectCmsAddAfter') 63 | && $this->module->registerHook('actionObjectCmsUpdateAfter') 64 | && $this->module->registerHook('actionObjectCmsDeleteAfter') 65 | && $this->module->registerHook('actionObjectCategoryAddAfter') 66 | && $this->module->registerHook('actionObjectCategoryUpdateAfter') 67 | && $this->module->registerHook('actionObjectCategoryDeleteAfter'); 68 | 69 | return $result; 70 | } 71 | 72 | /** 73 | * Retrieves common variables for assigning to Smarty templates in a hook context. 74 | * 75 | * This function prepares a set of key variables needed for rendering Doofinder-related data in a Smarty template. 76 | * These include language and currency codes, search engine hashid, region, script and CSS configurations, product links, etc. 77 | * 78 | * @param string $languageCode The language code (e.g., 'en-us', 'fr-fr') to be used in the Smarty template. 79 | * @param string $currencyCode The currency code (e.g., 'USD', 'EUR') to be used in the Smarty template. 80 | * @param array $productLinks array of product-related links to be used in the template 81 | * @param mixed $extraParams optional additional parameters that may be assigned to the template 82 | * 83 | * @return array returns an associative array of variables to be assigned to the Smarty template, 84 | * including language, script, product links, search engine ID, region, and more 85 | */ 86 | public static function getHookCommonSmartyAssigns($languageCode, $currencyCode, $productLinks, $extraParams = false) 87 | { 88 | $context = \Context::getContext(); 89 | $idShopGroup = $context->shop->id_shop_group; 90 | $idShop = $context->shop->id; 91 | 92 | $lang = \Tools::strtoupper($languageCode); 93 | $currency = \Tools::strtoupper($currencyCode); 94 | $searchEngineId = \Configuration::get('DF_HASHID_' . $currency . '_' . $lang, null, $idShopGroup, $idShop); 95 | $dfRegion = \Configuration::get('DF_REGION'); 96 | $script = \Configuration::get('DOOFINDER_SCRIPT_' . $lang, null, $idShopGroup, $idShop); 97 | $extraCss = \Configuration::get('DF_EXTRA_CSS', null, $idShopGroup, $idShop); 98 | $installationID = \Configuration::get('DF_INSTALLATION_ID', null, $idShopGroup, $idShop); 99 | $selfPath = dirname($_SERVER['SCRIPT_FILENAME']); 100 | if (!is_dir($selfPath)) { 101 | $selfPath = dirname(__FILE__); 102 | } 103 | 104 | $configScriptBaseUrl = sprintf(DoofinderConstants::CONFIG_REGION_URL, $dfRegion) . '/2.x'; 105 | $templateVars = [ 106 | 'ENT_QUOTES' => ENT_QUOTES, 107 | 'lang' => $languageCode, 108 | 'script_html' => DfTools::fixScriptTag($script), 109 | 'extra_css_html' => DfTools::fixStyleTag($extraCss), 110 | 'productLinks' => $productLinks, 111 | 'search_engine_id' => $searchEngineId, 112 | 'self' => $selfPath, 113 | 'df_another_params' => $extraParams, 114 | 'installation_ID' => $installationID, 115 | 'currency' => $currency, 116 | 'config_script_base_url' => $configScriptBaseUrl, 117 | 'customer' => (array) $context->customer, 118 | 'is_customer_logged' => $context->customer->isLogged(), 119 | 'is_customer_group_feature_active' => \Group::isFeatureActive(), 120 | 'customer_group_hide_prices' => 'false', 121 | ]; 122 | 123 | if ($context->customer->isLogged()) { 124 | $templateVars['customer_group_hide_prices'] = (DfTools::getCustomerGroupPriceVisibility($context->customer->id_default_group)) ? 'false' : 'true'; 125 | } 126 | 127 | return $templateVars; 128 | } 129 | 130 | /** 131 | * Returns an array of routes handled by this module. 132 | * 133 | * Defines custom URLs that map to specific controllers within the module. 134 | * 135 | * @return array module routes and their associated controllers, rules, keywords, and parameters 136 | */ 137 | public static function getHookModuleRoutes() 138 | { 139 | return [ 140 | 'module-doofinder-config' => [ 141 | 'controller' => 'config', 142 | 'rule' => 'module/doofinder/config', 143 | 'keywords' => [], 144 | 'params' => [ 145 | 'fc' => 'module', 146 | 'module' => 'doofinder', 147 | 'controller' => 'config', 148 | ], 149 | ], 150 | 'module-doofinder-ajax' => [ 151 | 'controller' => 'ajax', 152 | 'rule' => 'module/doofinder/ajax', 153 | 'keywords' => [], 154 | 'params' => [ 155 | 'fc' => 'module', 156 | 'module' => 'doofinder', 157 | 'controller' => 'ajax', 158 | ], 159 | ], 160 | 'module-doofinder-feed' => [ 161 | 'controller' => 'feed', 162 | 'rule' => 'module/doofinder/feed', 163 | 'keywords' => [], 164 | 'params' => [ 165 | 'fc' => 'module', 166 | 'module' => 'doofinder', 167 | 'controller' => 'feed', 168 | ], 169 | ], 170 | 'module-doofinder-callback' => [ 171 | 'controller' => 'callback', 172 | 'rule' => 'module/doofinder/callback', 173 | 'keywords' => [], 174 | 'params' => [ 175 | 'fc' => 'module', 176 | 'module' => 'doofinder', 177 | 'controller' => 'callback', 178 | ], 179 | ], 180 | ]; 181 | } 182 | 183 | /** 184 | * Queue the item for update on save 185 | * 186 | * @param string $object 187 | * @param int $idObject 188 | * @param int $shopId 189 | * @param string $action 190 | * 191 | * @return void 192 | */ 193 | public static function proccessHookUpdateOnSave($object, $idObject, $shopId, $action) 194 | { 195 | if (\Configuration::get('DF_UPDATE_ON_SAVE_DELAY')) { 196 | UpdateOnSave::addItemQueue($object, $idObject, $shopId, $action); 197 | 198 | if (UpdateOnSave::allowProcessItemsQueue()) { 199 | UpdateOnSave::processItemQueue($shopId); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Manager/FormManager.php: -------------------------------------------------------------------------------- 1 | module = $module; 53 | } 54 | 55 | /** 56 | * Process the backoffice configuration form 57 | * 58 | * Handles form submissions from different configuration tabs: 59 | * - Store information (installation ID, API key) 60 | * - Data feed settings 61 | * - Advanced parameters 62 | * - Reindexing requests 63 | * 64 | * Validates input data, updates configuration values, and provides user feedback. 65 | * 66 | * @return string HTML messages for user feedback (errors, warnings, confirmations) 67 | */ 68 | public function postProcess() 69 | { 70 | $formValues = []; 71 | $formUpdated = ''; 72 | $messages = ''; 73 | $context = \Context::getContext(); 74 | $idShop = $context->shop->id; 75 | 76 | $isFirstTime = (bool) \Tools::getValue('first_time', 0); 77 | $isAdvParamPresent = (bool) \Tools::getValue('adv', 0); 78 | 79 | if ($isFirstTime) { 80 | $shops = \Shop::getShops(); 81 | foreach ($shops as $shop) { 82 | $shopGroupId = $shop['id_shop_group']; 83 | $shopId = $shop['id_shop']; 84 | DoofinderConfig::setSharedDefaultConfig($shopGroupId, $shopId); 85 | } 86 | \Configuration::updateGlobalValue('DF_FEED_INDEXED', true); 87 | DoofinderConfig::setSharedGlobalDefaultConfig(); 88 | } 89 | 90 | $multipriceEnabled = \Configuration::get('DF_MULTIPRICE_ENABLED'); 91 | 92 | if ((bool) \Tools::isSubmit('submitDoofinderModuleLaunchReindexing')) { 93 | UpdateOnSave::indexApiInvokeReindexing(); 94 | } 95 | if (((bool) \Tools::isSubmit('submitDoofinderModuleDataFeed')) == true) { 96 | $formValues = array_merge($formValues, DoofinderConfig::getConfigFormValuesDataFeed($idShop)); 97 | $formUpdated = 'data_feed_tab'; 98 | } 99 | 100 | if (((bool) \Tools::isSubmit('submitDoofinderModuleAdvanced')) == true) { 101 | $formValues = array_merge($formValues, DoofinderConfig::getConfigFormValuesAdvanced($idShop)); 102 | $formUpdated = 'advanced_tab'; 103 | $context->smarty->assign('adv', 1); 104 | } 105 | 106 | $adminPanelView = new DoofinderAdminPanelView($this->module); 107 | if (((bool) \Tools::isSubmit('submitDoofinderModuleStoreInfo')) == true) { 108 | $storeSubmissionErrors = false; 109 | 110 | $installationId = trim(\Tools::getValue('DF_INSTALLATION_ID')); 111 | if (DfTools::validateInstallationId($installationId)) { 112 | $formValues['DF_INSTALLATION_ID'] = $installationId; 113 | } else { 114 | $messages .= $adminPanelView->displayErrorCtm($this->module->l('The Store ID must be in UUID format, for example: 3c49f881-5988-4e32-a581-1d577d7d55c0', 'formmanager')); 115 | $storeSubmissionErrors = true; 116 | } 117 | 118 | $apiKey = trim(\Tools::getValue('DF_API_KEY')); 119 | if (DfTools::validateApiKey($apiKey)) { 120 | $formValues['DF_API_KEY'] = $apiKey; 121 | } else { 122 | $messages .= $adminPanelView->displayErrorCtm($this->module->l('The API Key must be in the correct format, for example: eu1-29c05dbdab18cbbb65c95305bb767bab1240a528', 'formmanager')); 123 | $storeSubmissionErrors = true; 124 | } 125 | 126 | if (!$storeSubmissionErrors) { 127 | $formValues = array_merge($formValues, DoofinderConfig::getConfigFormValuesStoreInfo($idShop)); 128 | $formUpdated = 'store_info_tab'; 129 | } 130 | } 131 | 132 | foreach (array_keys($formValues) as $key) { 133 | $postKey = str_replace(['[', ']'], '', $key); 134 | $value = \Tools::getValue($postKey); 135 | 136 | if (isset($formValues[$key]['real_config'])) { 137 | $postKey = $formValues[$key]['real_config']; 138 | } 139 | if (is_array($value)) { 140 | $value = implode(',', $value); 141 | } 142 | if ($postKey === 'DF_FEED_FULL_PATH') { 143 | \Configuration::updateValue('DF_FEED_MAINCATEGORY_PATH', 0); 144 | } 145 | $value = trim($value); 146 | // Special case for Hashids due to the Multiprice 147 | if ($isAdvParamPresent && $multipriceEnabled && DfTools::str_contains($postKey, 'DF_HASHID')) { 148 | self::updateHashIds($postKey, $value); 149 | continue; 150 | } 151 | \Configuration::updateValue($postKey, $value); 152 | } 153 | 154 | if ($formUpdated == 'data_feed_tab') { 155 | if ((bool) \Configuration::get('DF_UPDATE_ON_SAVE_DELAY')) { 156 | SearchEngine::setSearchEnginesByConfig(); 157 | } 158 | if (\Tools::getValue('DF_UPDATE_ON_SAVE_DELAY') && (int) \Tools::getValue('DF_UPDATE_ON_SAVE_DELAY') < 5) { 159 | \Configuration::updateValue('DF_UPDATE_ON_SAVE_DELAY', 5); 160 | } 161 | 162 | $context->smarty->assign('text_data_changed', $this->module->l('You\'ve just changed a data feed option. It may be necessary to reprocess the index to apply these changes effectively.', 'formmanager')); 163 | $context->smarty->assign('text_reindex', $this->module->l('Launch reindexing', 'formmanager')); 164 | $msg = $context->smarty->fetch(DoofinderAdminPanelView::getLocalPath() . 'views/templates/admin/reindex.tpl'); 165 | $messages .= $adminPanelView->displayWarningCtm($msg, false, true); 166 | } 167 | 168 | // Check connection 169 | if ($formUpdated == 'store_info_tab') { 170 | $hashid = SearchEngine::getHashId($context->language->id, $context->currency->id); 171 | $apiKey = \Configuration::get('DF_API_KEY'); 172 | $dfApi = new DoofinderApi($hashid, $apiKey); 173 | $messages .= $dfApi->checkConnection($this->module); 174 | } 175 | 176 | if (!empty($formUpdated)) { 177 | $messages .= $adminPanelView->displayConfirmationCtm($this->module->l('Settings updated!', 'formmanager')); 178 | $context->smarty->assign('formUpdatedToClick', $formUpdated); 179 | } 180 | 181 | return $messages; 182 | } 183 | 184 | /** 185 | * Update Hashids configuration for multiprice scenarios 186 | * 187 | * When advanced parameters are present and multiprice is enabled, 188 | * this method updates all related Hashids configuration keys. 189 | * 190 | * @param string $postKey The configuration key being updated 191 | * @param string $value The new value to set 192 | * 193 | * @return void 194 | */ 195 | private static function updateHashIds($postKey, $value) 196 | { 197 | $hashidKeys = DfTools::getHashidKeys(); 198 | $hashidKeys = array_filter($hashidKeys, function ($hashidKey) use ($postKey) { 199 | return $hashidKey['keyMultiprice'] === $postKey; 200 | }); 201 | foreach ($hashidKeys as $hashidKey) { 202 | \Configuration::updateValue($hashidKey['key'], $value); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /feeds/product.php: -------------------------------------------------------------------------------- 1 | 43 | * Merge multidemensionnal array by value on each row 44 | * https://stackoverflow.com/questions/7973915/php-merge-arrays-by-value 45 | */ 46 | function arrayMergeByIdProduct($array1 = [], $array2 = []) 47 | { 48 | $sub_key = 'id_product'; 49 | $result = []; 50 | $result_row = []; 51 | if (empty($array1)) { 52 | return $array2; 53 | } 54 | if (empty($array2)) { 55 | return $array1; 56 | } 57 | foreach ($array1 as $item1) { 58 | $result_row = []; 59 | // Merge data 60 | foreach ($array2 as $item2) { 61 | if ($item1[$sub_key] == $item2[$sub_key]) { 62 | $result_row = array_merge($item1, $item2); 63 | break; 64 | } 65 | } 66 | // If no array merged 67 | if (empty($result_row)) { 68 | $result_row = $item1; 69 | } 70 | $result[] = $result_row; 71 | } 72 | 73 | return $result; 74 | } 75 | 76 | $context = Context::getContext(); 77 | 78 | $shop = new Shop((int) $context->shop->id); 79 | if (!$shop->id) { 80 | exit('NOT PROPERLY CONFIGURED'); 81 | } 82 | 83 | // GENERAL PURPOSE VARIABLES + CONTEXT 84 | $lang = DfTools::getLanguageFromRequest(); 85 | $context->language = $lang; 86 | $country = (int) DfTools::cfg($shop->id, 'PS_COUNTRY_DEFAULT'); 87 | $context->country = new Country($country); 88 | $currency = DfTools::getCurrencyForLanguageFromRequest($lang); 89 | 90 | $dfProductBuild = new DfProductBuild($shop->id, $lang->id, $currency->id); 91 | 92 | /* ---------- START SHARED CONFIG ---------- */ 93 | $currencies = $dfProductBuild->getCurrencies(); 94 | $shouldDisplayPrices = $dfProductBuild->shouldDisplayPrices(); 95 | $shouldPricesUseTaxes = $dfProductBuild->shouldUseTaxes(); 96 | $isMultipriceEnabled = $dfProductBuild->isMultipriceEnabled(); 97 | $shouldShowProductVariations = $dfProductBuild->shouldShowProductVariations(); 98 | $shouldShowProductFeatures = $dfProductBuild->shouldShowProductFeatures(); 99 | $featuresShownArray = $dfProductBuild->getFeaturesShown(); 100 | $attributesShownArray = array_filter(explode(',', $dfProductBuild->getAttributesShown()), function ($a) { return strlen(trim($a)) > 0; }); 101 | /* ---------- END SHARED CONFIG ---------- */ 102 | 103 | /* ---------- START CSV-SPECIFIC CONFIG ---------- */ 104 | $shouldLimitGroupAttributes = false; 105 | $isDebugEnabled = DfTools::cfg($shop->id, 'DF_DEBUG'); 106 | $debug = DfTools::getBooleanFromRequest('debug'); 107 | $limit = Tools::getValue('limit', false); 108 | $offset = Tools::getValue('offset', false); 109 | /* ---------- END CSV-SPECIFIC CONFIG ---------- */ 110 | 111 | // To prevent printing errors or warnings that may corrupt the feed. 112 | if ($debug) { 113 | error_reporting(E_ALL); 114 | ini_set('display_errors', 1); 115 | } else { 116 | error_reporting(0); 117 | ini_set('display_errors', 0); 118 | } 119 | 120 | $groupAttributesSlug = []; 121 | if (count($attributesShownArray) > 0) { 122 | $groupAttributes = AttributeGroup::getAttributesGroups($lang->id); 123 | foreach ($groupAttributes as $g) { 124 | if (in_array($g['id_attribute_group'], $attributesShownArray)) { 125 | $groupAttributesSlug[] = DfTools::slugify($g['name']); 126 | } 127 | } 128 | $shouldLimitGroupAttributes = true; 129 | } 130 | 131 | if ($isDebugEnabled) { 132 | error_log("Starting feed.\n", 3, 'doofinder.log'); 133 | } 134 | 135 | // OUTPUT 136 | if (isset($_SERVER['HTTPS'])) { 137 | header('Strict-Transport-Security: max-age=500'); 138 | } 139 | 140 | header('Content-Type:text/plain; charset=utf-8'); 141 | 142 | // HEADER 143 | $header = ['id']; 144 | if ($shouldShowProductVariations) { 145 | $header[] = 'item_group_id'; 146 | } 147 | $header = array_merge($header, [ 148 | 'title', 'link', 'description', 'alternate_description', 'meta_title', 'meta_description', 'image_link', 'images_links', 'main_category', 149 | 'categories', 'category_merchandising', 'availability', 'brand', 'mpn', 'ean13', 'upc', 'reference', 150 | 'supplier_reference', 'supplier_name', 'extra_title_1', 'extra_title_2', 'tags', 'minimum_quantity', 'creation_date', 151 | ]); 152 | 153 | if (DfTools::versionGte('1.7.0.0')) { 154 | $header = array_merge($header, ['isbn']); 155 | } 156 | 157 | $header[] = 'stock_quantity'; 158 | 159 | if ($shouldDisplayPrices) { 160 | $header[] = 'price'; 161 | $header[] = 'sale_price'; 162 | 163 | if ($isMultipriceEnabled) { 164 | $header[] = 'df_multiprice'; 165 | } 166 | } 167 | 168 | $additionalAttributesHeaders = []; 169 | 170 | if ($shouldShowProductVariations) { 171 | $header[] = 'variation_reference'; 172 | $header[] = 'variation_supplier_reference'; 173 | $header[] = 'variation_mpn'; 174 | $header[] = 'variation_ean13'; 175 | $header[] = 'variation_upc'; 176 | $header[] = 'df_group_leader'; 177 | $header[] = 'df_variants_information'; 178 | $attributeKeys = DfTools::getAttributeKeysForShopAndLang($shop->id, $lang->id); 179 | $altAttributeKeys = []; 180 | 181 | foreach ($attributeKeys as $key) { 182 | $headerValue = DfTools::slugify($key); 183 | if ($shouldLimitGroupAttributes && !in_array($headerValue, $groupAttributesSlug)) { 184 | continue; 185 | } 186 | $altAttributeKeys[] = $key; 187 | $additionalAttributesHeaders[] = $headerValue; 188 | } 189 | $attributeKeys = $altAttributeKeys; 190 | } 191 | 192 | if ($shouldShowProductFeatures) { 193 | $allFeatureKeys = DfTools::getFeatureKeysForShopAndLang($shop->id, $lang->id); 194 | 195 | if ( 196 | is_array($featuresShownArray) 197 | && count($featuresShownArray) > 0 198 | && $featuresShownArray[0] !== '' 199 | ) { 200 | $featurekeys = DfTools::getSelectedFeatures($allFeatureKeys, $featuresShownArray); 201 | } else { 202 | $featurekeys = $allFeatureKeys; 203 | } 204 | $additionalAttributesHeaders[] = 'attributes'; 205 | } 206 | 207 | $header = array_merge($header, $additionalAttributesHeaders); 208 | 209 | /** 210 | * @author camlafit 211 | * Allows users to extend Doofinder feed by adding extra headers and extra rows. 212 | * The hook can be defined in a different custom module or directly in the theme PHP files. 213 | * Don't forget to register the hook via registerHook method: 214 | * 215 | * `$this->registerHook('actionDoofinderExtendFeed');` 216 | * 217 | * Example of the function: 218 | * 219 | * public function hookActionDoofinderExtendFeed($params) 220 | * { 221 | * $params['extra_header'] = ['header_custom1', 'header_custom2']; 222 | * $params['extra_rows'] = [ 223 | * [ 224 | * 'id_product' => 1, 225 | * 'header_custom1' => 'value1' 226 | * ], 227 | * [ 228 | * 'id_product' => 2, 229 | * 'header_custom2' => 'value2' 230 | * ] 231 | * ]; 232 | * } 233 | * 234 | * To add an new header, module can do an array_merge on $extraHeader 235 | * To add an new data to a product, the module must create a multidimensional array as this : 236 | * array( 237 | * index => array( 238 | * 'id_product' => value, 239 | * 'new_header_column_name' => 'value related to the new column' 240 | * ), 241 | * [...] 242 | * ) 243 | * As each module can extend $extraHeader and $extraRows don't forget to merge them 244 | */ 245 | $extraHeader = []; 246 | $extraRows = []; 247 | Hook::exec('actionDoofinderExtendFeed', [ 248 | 'extra_header' => &$extraHeader, 249 | 'extra_rows' => &$extraRows, 250 | 'id_lang' => $lang->id, 251 | 'id_shop' => $shop->id, 252 | 'limit' => $limit, 253 | 'offset' => $offset, 254 | ]); 255 | 256 | $header = array_merge($header, $extraHeader); 257 | // To avoid indexation failures 258 | $header = array_unique($header); 259 | $additionalHeaders = array_merge($additionalAttributesHeaders, $extraHeader); 260 | 261 | $csv = fopen('php://output', 'w'); 262 | if (!$limit || (false !== $offset && 0 === (int) $offset)) { 263 | fputcsv($csv, $header, DfTools::TXT_SEPARATOR); 264 | } 265 | 266 | $products = DfTools::getAvailableProducts($lang->id, $shouldShowProductVariations, $limit, $offset); 267 | $products = arrayMergeByIdProduct($products, $extraRows); 268 | 269 | foreach ($products as $product) { 270 | $minPriceVariant = null; 271 | if ($shouldShowProductVariations && $product['variant_count'] > 0) { 272 | $variations = DfTools::getProductVariations($product['id_product']); 273 | foreach ($variations as $variation) { 274 | $minPriceVariant = $dfProductBuild->getMinPrice($minPriceVariant, $variation); 275 | $builtVariation = $dfProductBuild->buildVariation($product, $variation); 276 | $csvVariation = $dfProductBuild->applySpecificTransformationsForCsv($builtVariation, $extraHeader, $header); 277 | fputcsv($csv, $csvVariation, DfTools::TXT_SEPARATOR); 278 | } 279 | $product = $dfProductBuild->buildProduct($product, $minPriceVariant, $additionalAttributesHeaders, $additionalHeaders); 280 | } else { 281 | $product = $dfProductBuild->buildProduct($product, null, $additionalAttributesHeaders, $additionalHeaders); 282 | } 283 | 284 | $product = $dfProductBuild->applySpecificTransformationsForCsv($product, $extraHeader, $header); 285 | fputcsv($csv, $product, DfTools::TXT_SEPARATOR); 286 | } 287 | fclose($csv); 288 | --------------------------------------------------------------------------------