In our blog we explained how PrestaShop search works and instantly received a comment from one of our readers.
Bill: This is good, but the search function is entirely about products. What about when customers want to search something from CMS pages?
Alas, there are no standard tools to do that. The only way to achieve this goal is to extend the standard functionality with the help of a new module. I’ll guide you the process of its development. Readers may also download the module archive at the end of this article.
PrestaShop stores all modules in the YOUR_STORE/modules/ folder. It includes at least one file – yourmodulename.php, which contains the core module functions. If you want to set an icon for your module, copy the logo.gif image into the folder with your module. This icon will be displayed in backend -> modules -> yourModule.
Let’s call our module belvg_cmssearchfree and create a folder with the same name in the modules directory. Belvg_cmssearchfree.php with the following content should be placed there:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
if (!defined('_PS_VERSION_')) exit; /* checks for the existence of a PHP constant, and if it doesn't exist, it quits. The sole purpose of this is to prevent visitors to load this file directly. */ class Belvg_CmsSearchFree extends Module { public function __construct() { $this->name = 'belvg_cmssearchfree';// technical name of your module $this->tab = 'front_office_features'; // back office tab where module is displayed $this->version = '1.0.0'; //module version $this->author = 'BelVG'; //module author $this->need_instance = 0; //if you want the module to display a warning message in the “Modules” page, set this attribute to 1 $this->module_key = ''; //unique key, issued by addons.prestqashop.com upon module upload parent::__construct(); $this->displayName = $this->l('CMS Search Free'); // your module name that will be recognized by users and may differ from $this->name. $this->description = $this->l('Useful if the user wants to search in CMS'); // your module description } public function install() { include(dirname(__FILE__).'/install.php'); //this file contains instructions for automatic controller replacement (we’ll take a closer look at it below) if ( !parent::install() ) return false; return true; } public function uninstall() { include(dirname(__FILE__).'/uninstall.php'); // this file contains instructions on the automatic controller change (we’ll take a closer look at it below) if (!parent::uninstall()) return false; return true; } } |
We need to modify the SearchController work logic, so let’s create the same file in override/controllers folder. We add the function to enable search both within products and CMS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
class SearchController extends SearchControllerCore //we inherit the standard controller { public function preProcess() { global $smarty; $status = $this->getStatus(); // true will be returned if our module is active if ( empty($status) ){ parent::preProcess(); return true; } $query = urldecode(Tools::getValue('q')); if ($this->ajaxSearch) { self::$link = new Link(); $searchResults = Search::find((int)(Tools::getValue('id_lang')), $query, 1, 10, 'position', 'desc', true); foreach ($searchResults AS &$product) $product['product_link'] = self::$link->getProductLink($product['id_product'], $product['prewrite'], $product['crewrite']); die(Tools::jsonEncode($searchResults)); } if ($this->instantSearch && !is_array($query)) { $this->productSort(); $this->n = abs((int)(Tools::getValue('n', Configuration::get('PS_PRODUCTS_PER_PAGE')))); $this->p = abs((int)(Tools::getValue('p', 1))); $search = Search::find((int)(self::$cookie->id_lang), $query, $this->p, $this->n, $this->orderBy, $this->orderWay); Module::hookExec('search', array('expr' => $query, 'total' => $search['total'])); $nbProducts = $search['total']; $articles = $this->getCmsArticles((int)(self::$cookie->id_lang), $query); // we get articles that satisfy the search criteria $this->pagination($nbProducts); self::$smarty->assign(array( 'products' => $search['result'], // DEPRECATED (since to 1.4), do not use this: there is a conflict with block_cart module 'articles' => $articles, //we pass articles array into smarty 'nbArticles' => count($articles), //we pass articles quantity into smarty 'search_products' => $search['result'], 'nbProducts' => $search['total'], 'search_query' => $query, 'instantSearch' => $this->instantSearch, 'homeSize' => Image::getSize('home'))); } elseif ($query = Tools::getValue('search_query', Tools::getValue('ref')) AND !is_array($query)) { //-------------------------------------------// $this->productSort(); $this->n = abs((int)(Tools::getValue('n', Configuration::get('PS_PRODUCTS_PER_PAGE')))); $this->p = abs((int)(Tools::getValue('p', 1))); $search = Search::find((int)(self::$cookie->id_lang), $query, $this->p, $this->n, $this->orderBy, $this->orderWay); Module::hookExec('search', array('expr' => $query, 'total' => $search['total'])); $nbProducts = $search['total']; $articles = $this->getCmsArticles((int)(self::$cookie->id_lang), $query); // we get articles that satisfy the search criteria $this->pagination($nbProducts); self::$smarty->assign(array( 'products' => $search['result'], // DEPRECATED (since to 1.4), do not use this: there is a conflict with block_cart module 'articles' => $articles, //we pass articles array into smarty 'nbArticles' => count($articles), //we pass articles quantity into smarty 'search_products' => $search['result'], 'nbProducts' => $search['total'], 'search_query' => $query, 'homeSize' => Image::getSize('home') )); //-------------------------------------------// } elseif ($tag = urldecode(Tools::getValue('tag')) AND !is_array($tag)) { $nbProducts = (int)(Search::searchTag((int)(self::$cookie->id_lang), $tag, true)); $this->pagination($nbProducts); $result = Search::searchTag((int)(self::$cookie->id_lang), $tag, false, $this->p, $this->n, $this->orderBy, $this->orderWay); Module::hookExec('search', array('expr' => $tag, 'total' => sizeof($result))); self::$smarty->assign(array( 'search_tag' => $tag, 'products' => $result, // DEPRECATED (since to 1.4), do not use this: there is a conflict with block_cart module 'search_products' => $result, 'nbProducts' => $nbProducts, 'homeSize' => Image::getSize('home'))); } else { self::$smarty->assign(array( 'products' => array(), 'search_products' => array(), 'pages_nb' => 1, 'nbProducts' => 0)); } self::$smarty->assign('add_prod_display', Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY')); } public function displayContent() { //if the module is active, our template will be displayed; otherwise the standard template will appear $status = $this->getStatus(); if ( empty($status) ){ parent::displayContent(); }else{ self::$smarty->display(_PS_MODULE_DIR_.'belvg_cmssearchfree/tpl/search.tpl'); } } public function setMedia() { //if the module is active, load css styles $status = $this->getStatus(); parent::setMedia(); if ( !empty($status) ){ Tools::addCSS(_THEME_CSS_DIR_.'../../../modules/belvg_cmssearchfree/css/styles.css', 'all'); //css styles are stored in the folder with our module } } /* * It checks whether the module is active */ public function getStatus($moduleName = 'belvg_cmssearchfree'){ Db::getInstance()->ExecuteS('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($moduleName).'\' AND active = 1'); $status = ((bool)Db::getInstance()->NumRows()); return $status; } /* * It gets articles that satisfy the search criteria */ public function getCmsArticles($id_lang, $query) { $sql = 'SELECT * FROM `'._DB_PREFIX_.'cms_lang` cl JOIN `'._DB_PREFIX_.'cms` c ON (cl.`id_cms` = c.`id_cms`) WHERE id_lang = '.$id_lang.' AND (`meta_title` LIKE \'%'.Search::sanitize($query, (int)$id_lang).'%\' OR `meta_description` LIKE \'%'.Search::sanitize($query, (int)$id_lang).'%\' OR `meta_keywords` LIKE \'%'.Search::sanitize($query, (int)$id_lang).'%\' OR `content` LIKE \'%'.Search::sanitize($query, (int)$id_lang).'%\')'; $articles = Db::getInstance()->ExecuteS($sql); foreach($articles as &$article){ $article['content'] = strip_tags($article['content']); } return $articles; } } |
Search.tpl template will be extended with a section, responsible for displaying articles that satisfy the search criteria.
1 2 3 4 5 6 7 8 9 10 11 |
{if !empty($articles)} <h3><span class="big">{$nbArticles|intval}</span> {if $nbArticles == 1}{l s=' cms result has been found.' mod='belvg_cmssearchfree'}{else}{l s='cms results have been found.' mod='belvg_cmssearchfree'}{/if}</h3> <ul id="articles_list" class="clear"> {foreach from=$articles item=article} <li> <h4><a href="{$link->getCMSLink($article['id_cms'], $article['link_rewrite'])}">{$article['meta_title']|escape:'htmlall':'UTF-8'}</a></h4> <div>{$article['content']|escape:'htmlall':'UTF-8'|truncate:140}</div> </li> {/foreach} </ul> {/if} |
Please, notice that if you are planning to implement the multilanguage option in your module, you need to use the following construction for the templates: {l s=’ cms result has been found.’ mod=’belvg_cmssearchfree’}, where the mod=yourmodulename parameter is specified. Thereby translations will be available at backend ->Tools -> Translations -> Modify translations ->Module Translaions ->Choose Language -> Module: yourmodulename. When translations are saved, the file with multiple languages (e.g. en.php, fr.php, etc) will appear in the folder with your module.
PrestaShop Modules
Take your online store to the next level with BelVG PrestaShop Modules
Visit the storeThe last thing to do is to take a look at install.php and uninstall.php files, where file replacement logic is contained. You could place the SearchController file in override folder manually, but since we create a product for addons.prestashop.com, manual approach for file transfer is a bad idea.
Install.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function _install_make_bak($file_name_for_backup){ $file_for_backup = $file_name_for_backup; if (file_exists($file_for_backup)) { // backup if(!file_exists($file_for_backup.".belvg_bak")) rename($file_for_backup, $file_for_backup.".belvg_bak"); } } function _install_copy($source,$destination){ copy($source,$destination); } $files_dir = dirname(__FILE__); //."/override/classes/SearchController.php"; _install_make_bak($files_dir."/../../override/controllers/SearchController.php"); _install_copy($files_dir."/override/controllers/SearchController.php", $files_dir."/../../override/controllers/SearchController.php"); |
Uninstall.php:
1 2 3 4 5 6 7 8 9 |
function _uninstall_bak($file_for_uninstall){ if (file_exists($file_for_uninstall)) { rename($file_for_uninstall, $file_for_uninstall.".belvg_bak_uninstall"); rename($file_for_uninstall.".belvg_bak", $file_for_uninstall); } } $files_dir = dirname(__FILE__); _uninstall_bak($files_dir."/../../override/controllers/SearchController.php"); |
As a result we get a module that enables search both within products and CMS.
Experienced developers won’t consider this article extremely helpful, because it is largely oriented on beginners.
If anything is left unclear or questionable, let’s discuss it in comments below. Feel free to suggest ideas for developer tips, and we’ll examine the case in our future posts.
Expect to read how to create more flexible Prestashop CMS search and configure module settings the next Monday, August 6.
PrestaShop Development
Take your online store to the next level with BelVG PrestaShop Development
Visit the page
Cyril,
It is avaliable as a module https://addons.prestashop.com/en/search-filters/8775-cms-search.html
Excellent job! Is it available for Prestashop 1.6?
Ashsih,
This module is not displayed in hooks, because it just modifies the behavior of Prestashop. If you want to change the position of search results, then you need to edit directly the tpl file of the theme or module.
Hello Alex,
Thanks for the great module.
I have installed it in my localhost But it is not displaying in the front page.Please let me know how to setup the position of this module in the header.Because in backend module >position>not displaying there your module.
Thanks in advance
Super solution. It may be useful for different purposes.
We have updated the CMS Search module for Prestashop 1.5. Head to our store to get it – http://module-presta.com/cms-search.html.
But that’s not all – we will be offering the new version of the module for free to several blog readers who were interested in it’s appearance enough to ask us about it. If you wrote a comment to this post – please contact us at [email protected] and we will give you a special download code.
Hi Martin, it would be extremely helpful if you could forward that file to me.
Hi Alex, have you managed to upgrade this module to 1.5? Thanks for your excellent article!
I want to please have these files for Prestashop 1.5 also.
hi Martin, can you pl send the files for version 1.5.
Hello Martin,
1) This is correct; you no longer need this in 1.5 since all files which can be overridden are already
located in the necessary folder. Here it is described in more details: http://blog.belvg.com/how-to-make-an-override-in-your-module.html
2-3) Thanks for the info, this may help our readers.
Hi,
I have upgraded (hacked?) the SearchController override to be compatible with 1.5. There are a couple of issues:
1) The install script tried to backup the file and copy to/from “override/Controllers/SearchController.php”, where the correct path should now include the folder “Front, (i.e. “override/Controllers/Front/SearchController.php”). I just manully copied the files and changed the install method in the module php file to return true.
2) The name of the method to override is now initContent(). Actually I copied the base class (SearchControllerCore), changed the name and extends statement, and added the method of getting CMS content and the smarty assign variables.
3) Context is now used to get language id, ie. $this->context->language->id
I’m happy to forward the file, if you’re interested.
Thanks for a great article!
Best regards,
Martin
Hi Jörg,
Thanks for installing our module. Actually, we are planning to upgrade all our PrestaShop modules to 1.5 version by the end of this year.
Besides, we will post a new article on how to write modules in PrestaShop 1.5 shortly, so don’t forget to subscribe to our blog.
Hi great Job. I used your module in prestashop 1.4 and it worked like charm BUT NOW IN PRESTASHOP 1.5, it doesn’t work anymore (not even install !). Do you have any idea why ? Have you planned to make an update ? If no could you please give me a hint ?
Kind regards,
Jörg