1

Тема: Улучшаем поиск.

На одном из последних проектов, я перепилил логику поиска для автокомплита и полного поиска.
Это позволяет совершать поиск по нескольким словам-ключам.


/application/modules/shop/search.php

<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

/**
 * Search Controller
 *
 * @uses ShopController
 * @package Shop
 * @version 0.1
 * @copyright 2013 ImageCMS
 * @author <dev@imagecms.net>
 */
class Search extends \Search\BaseSearch {

    public function __construct() {
        parent::__construct();
    }

    /**
     * Display products list.
     *
     * @access public
     */
    public function index() {
        // Begin pagination
        $this->load->library('Pagination');
        $this->pagination = new SPagination();
        $searchPagination['base_url'] = shop_url('search/' . $this->_getQueryString());
        $searchPagination['total_rows'] = $this->data['totalProducts'];
        $searchPagination['per_page'] = $this->perPage;
        $searchPagination['last_link'] = ceil($this->data['totalProducts'] / $this->perPage);
        $searchPagination['page_query_string'] = TRUE;
        include_once "./templates/{$this->config->item('template')}/paginations.php";

        $this->pagination->initialize($searchPagination);
        $this->data['pagination'] = $this->pagination->create_links();
        // End pagination

        $this->template->registerMeta("ROBOTS", "NOINDEX, NOFOLLOW");

        /** Register event 'search:load' */
        \CMSFactory\Events::create()->registerEvent($this->data, 'search:load');
        \CMSFactory\Events::runFactory();

        $this->render('search', $this->data);
        exit;
    }

    /**
     * Autocomplete for search
     * @return jsone
     */
    public function ac($locale = NULL) {
        $NextCS = $this->template->get_var('NextCS');
        $NextCSId = $this->template->get_var('NextCSId');
        $locale = $locale ? $locale : \MY_Controller::getCurrentLocale();

        /** Register event 'search:AC' */
        \CMSFactory\Events::create()->registerEvent(array('search_text' => $this->input->post('queryString')), 'search:AC');

        if (mb_strlen($this->input->post('queryString')) >= 3) {
            
            $this->db
                            ->select('DISTINCT shop_product_variants.product_id as product_id, name, shop_products.url, shop_product_variants.mainImage, shop_product_variants.price as price, shop_product_variants.id')
                            ->join('shop_products_i18n', "shop_products.id = shop_products_i18n.id AND shop_products_i18n.locale='{$locale}'")
                            ->join('shop_product_variants', 'shop_products.id = shop_product_variants.product_id')
                            ->join('shop_category', 'shop_products.category_id = shop_category.id')
                            ->where('shop_products.active', 1)
                            ->where('shop_category.active', 1)
                            ->order_by('shop_product_variants.position')
                            ->group_by('shop_products.id')
                            ->limit(5)
                            ->distinct();
            
            $stringSearch = $this->input->post('queryString');
            $arraySearch = explode(' ', $stringSearch);
            $i = 0;
            foreach($arraySearch as $word){
                    if($i){
                        if(mb_strlen($word)>=3){
                            $this->_wherelike($word);
                        }
                    }else{
                        $this->_wherelike($word);
                        $i++;
                }
            }
            $res =  $this->db->get('shop_products')->result_array();

            foreach ($res as $key => $val) {

                $product = \SProductsQuery::create()->findPk($val['product_id']);

                if ($product) {
                    $res[$key]['price'] = $product->firstVariant->toCurrency();
                    $res[$key]['mainImage'] = $product->firstVariant->getMainPhoto();
                    $res[$key]['smallImage'] = $product->firstVariant->getSmallPhoto();
                    if ($NextCS != null) {
                        $res[$key]['nextCurrency'] = $product->firstVariant->toCurrency('Price', $NextCSId);
                    }
                } else {
                    $res[$key]['price'] = 0;
                }
            }

            $res['queryString'] .= $this->input->post('queryString');

            return json_encode($res);
        } else {
            $this->core->error_404();
        }
    }
    
    private function _wherelike($word){
        $word = $this->db->escape('%'.$word.'%');
        $this->db->where("(`name` LIKE $word OR `number` LIKE $word OR `product_id` LIKE $word)");
        return;
    }

}

/* End of file search.php */

Добавлена служебная функция для сборки запроса _wherelike().
Незначительно исправлена основная функция автокомплита ac()

/application/modules/shop/classes/Search/BaseSearch.php

<?php

namespace Search;

(defined('BASEPATH')) OR exit('No direct script access allowed');

/**
 * Shop Controller
 *
 * @uses \ShopController
 * @package Shop
 * @copyright 2013 ImageCMS
 * @property products SProducts
 */
class BaseSearch extends \ShopController {

    protected $perPage = 10;
    protected $data = array();

    public function __construct() {
        parent::__construct();
        $this->load->helper('string');

        // Load per page param
        $this->perPage = $this->input->get('user_per_page')? : \ShopCore::app()->SSettings->frontProductsPerPage;

        $this->load->module('core');
        $this->core->set_meta_tags(\ShopCore::t('Поиск'));

        $this->REQUEST_URI = $_SERVER['REQUEST_URI'];

        if ($this->uri->segment(3) != 'ac') {
            $this->__CMSCore__();
            $this->index();
        }
    }

    /**
     * Display products list.
     *
     * @access public
     */
    public function __CMSCore__() {

        $this->perPage = (intval(\ShopCore::$_GET['user_per_page'])) ? intval(\ShopCore::$_GET['user_per_page']) : $this->perPage = \ShopCore::app()->SSettings->frontProductsPerPage;

        $search_str = trim(\ShopCore::$_GET['text']);

        /** Convert to string * */
        if (!is_string($search_str))
            $search_str = (string) $search_str;
        
        \CMSFactory\Events::create()->registerEvent(array('search_text' => $search_str), 'ShopBaseSearch:preSearch');

        $products = \SProductsQuery::create()
                ->joinWithI18n(\MY_Controller::getCurrentLocale(), \Criteria::RIGHT_JOIN)
                ->leftJoin('ProductVariant')
                ->joinMainCategory()
                ->where('MainCategory.Active = ?', 1)
                ->filterByActive(true);

        $brandsInSearchResult = \SProductsQuery::create()
                ->distinct()
                ->filterByActive(true)
                ->useI18nQuery(\MY_Controller::getCurrentLocale())
                ->filterByName('%' . $search_str . '%')
                ->endUse()
                ->select(array('BrandId'));

        if (!empty($search_str)) {
            $products = $products
                    ->useI18nQuery(\MY_Controller::getCurrentLocale(), null, \Criteria::RIGHT_JOIN)
                    ->filterByName('%' . $search_str . '%')
                    ->endUse();
                    
            $arraySearch = explode(' ', $search_str);
            if(count($arraySearch)>1){
                $ind = 0; 
                foreach($arraySearch as $keyword){                    
                    if($ind){
                        $products = $products->where('( SProductsI18n.Name LIKE  ?  or  ProductVariant.Number LIKE ? )', array('%'.$keyword.'%', '%'.$keyword.'%'));                        
                    }else{
                        $products = $products->orWhere('( SProductsI18n.Name LIKE  ?  or  ProductVariant.Number LIKE ? )', array('%'.$keyword.'%', '%'.$keyword.'%'));    
                        $ind++;
                    }
                }
            }else{
                $products = $products->orWhere('ProductVariant.Number LIKE ?', '%' . $search_str . '%');
                $products = $products->orWhere('SProducts.Id LIKE ?', $search_str);
            }

            $subCategories = clone($products);

            $subCategories = $subCategories
                    ->select('CategoryId')
                    ->find()
                    ->toArray();

            if (!empty(\ShopCore::$_GET['category']) && \ShopCore::$_GET['category'] > 0) {
                $brandsInSearchResult = $brandsInSearchResult->filterByCategoryId((int) \ShopCore::$_GET['category']);
                $products = $products->filterByCategoryId((int) \ShopCore::$_GET['category']);
            }

//              // Filter by brand
            if (!empty(\ShopCore::$_GET['brand']) && \ShopCore::$_GET['brand'] > 0)
                $products = $products->filterByBrandId((int) \ShopCore::$_GET['brand']);

            $brandsInSearchResult = $brandsInSearchResult
                    ->find()
                    ->toArray();

            if (sizeof($brandsInSearchResult) > 0) {
                $brandsInSearchResult = \SBrandsQuery::create()
                        ->findPks($brandsInSearchResult);
            }
        }

        $count_cats = array_count_values($subCategories);

        /**
         * Prepare category tree of Main catagory and sub-categories
         */
        $categories = array();
        $count = 0;

        foreach ($count_cats as $key => $value) {

            $category = \SCategoryQuery::create()
                    ->joinWithI18n(\MY_Controller::getCurrentLocale())
                    //->filterByActive(TRUE)
                    ->findOneById($key);

            if (!$category instanceof \SCategory) {
                break;
            }

            foreach ($category->buildCategoryPath(\Criteria::ASC, TRUE) as $cat) {
                $parentCategory = $cat;
                break;
            }

            if ($parentCategory) {

                $categories[$parentCategory->getId()][$parentCategory->getName()][] = array(
                    'id' => $category->getId(),
                    'name' => $category->getName(),
                    'count' => $count_cats[$category->getId()]
                );
//                    $categories[$parentCategory->getId()][$parentCategory->getName()]['count'] += $count_cats[$category->getId()];
            }
        }
        /** Set userPerPage Products Count */
        //choode order method (default or get)
        if (!\ShopCore::$_GET['order']) {
            $order_method = \Category\BaseCategory::getDefaultSort();
            // $order_method = $order_method->get;
        } elseif (!empty(\ShopCore::$_GET['order'])) {
            $order_method = \ShopCore::$_GET['order'];
        }

        //for order method by get order
        $products
                ->distinct()
                ->withColumn('IF(shop_product_variants.stock > 0, 1, 0)', 'allstock')
                ->orderBy('allstock', \Criteria::DESC)
                ->globalSort($order_method);


        $totalProducts = $this->_count($products);

        $products = $products
                ->offset((int) \ShopCore::$_GET['per_page'])
                ->limit((int) $this->perPage)
                ->find();

        // Load product variants
        $products->populateRelation('ProductVariant');

        /** Setting core data */
        $this->core->core_data['data_type'] = 'search';

        $this->data = array(
            'products' => $products,
            'cart_data' => \ShopCore::app()->SCart->getData(),
            'totalProducts' => $totalProducts,
            'brandsInSearchResult' => $brandsInSearchResult,
            //'tree' => \ShopCore::app()->SCategoryTree->getTree(0),
            //'tree' => \ShopProductCategoriesQuery::create()->find(),
            'categories' => $categories,
            'order_method' => $order_method
        );
//        }

        \ShopCore::app()->SCategoryTree->setLoadUnactive(false);
        $this->data['tree'] = \ShopCore::app()->SCategoryTree->getTree(0);
        if (mb_strlen($_POST['queryString']) >= 1) {
            /**
             * @deprecated since version number
             */
            if ($_POST['cat'] != 2) {
                $category = \SCategoryQuery::create()->filterByFullPathIds('%' . $_POST['cat'] . '%')->select(array('Id'))->find()->toArray();
                array_push($category, $cat);
            }

            $products = \SProductsQuery::create()
                    ->addSelectModifier('SQL_CALC_FOUND_ROWS')
                    ->leftJoin('ProductVariant')
                    ->joinWithI18n(\MY_Controller::getCurrentLocale())
                    ->distinct()
                    ->filterByActive(true)
                    ->useI18nQuery(\MY_Controller::getCurrentLocale())
                    ->filterByName('%' . $_POST['queryString'] . '%')
                    ->endUse();

            $products = $products->orWhere('ProductVariant.Number LIKE ?', $_POST['queryString']);
            $products = $products->orWhere('ProductVariant.Id LIKE ?', $search_str);

            if ($_POST['cat'] != 2) {
                $products = $products->filterByCategoryId($category);
            }

            $totalProducts = $this->getTotalRow();

            $products = $products->offset((int) \ShopCore::$_GET['per_page'])
                    ->limit((int) $this->perPage)
                    ->find();

            // Load product variants
            $products->populateRelation('ProductVariant');
            if (sizeof($products) > 0) {
                $result = '<div class="search_drop">
                                 <ul>';
                foreach ($products as $product) {
                    $prices = currency_convert($product->firstvariant->getPrice(), $product->firstvariant->getCurrency());
                    $result .= '
                                    <li class="smallest_item">
                                        <a class="photo_block" href="' . shop_url('product/' . $product->getUrl()) . '" class="photo">
                                            <img src="' . productImageUrl($product->getSmallModImage()) . '"/>
                                        </a>
                                    <div class="func_description">
                                        <a class="title" href="' . shop_url('product/' . $product->getUrl()) . '">' . $product->getName() . '</a>
                                        <div class="buy">
                                        <div class="price f-s_14">
                                            <span>' . $prices['main']['price'] . $prices['main']['symbol'] . '</span>
                                        </div>
                                        </div>
                                    </div>
                                    </li>';
                }
                if ($totalProducts > 5)
                    $result .= '</ul>  <a class="all_result" style="bottom:0;" href="/shop/search?text=' . $_POST['queryString'] . '" >' . lang('All result') . '</a>
                       </div>  </div></div>
                    ';
                echo $result;
            }
            exit();
        }
    }

    /**
     *
     * @return type
     */
    private function getTotalRow() {
        $connection = \Propel::getConnection();
        $statement = $connection->prepare('SELECT FOUND_ROWS() as `number`');
        $statement->execute();
        $resultset = $statement->fetchAll();
        return $resultset[0]['number'];
    }

    /**
     * Convert price to main currency
     * @param type $sum
     * @return
     */
    protected function convertToMainCurrency($sum) {
        return ShopCore::app()->SCurrencyHelper->convertToMain($sum, ShopCore::app()->SCurrencyHelper->current->getId());
    }

    /**
     * Count total products in category
     *
     * @param SProductsQuery $object
     * @return int */
    protected function _count(\SProductsQuery $object) {
        $object = clone $object;
        return $object->count();
    }

    protected function _getQueryString() {
        $data = array();

        $need = array('text', 'f', 'lp', 'rp', 'brand', 'order', 'category', 'user_per_page');

        foreach ($need as $key => $value) {
            if (isset(\ShopCore::$_GET[$value])) {
                $data[$value] = \ShopCore::$_GET[$value];
            }
        }

        return '?' . http_build_query($data);
    }

}

/* End of file search.php */

немного исправлена основная функция поиска  __CMSCore__().

Thumbs up +3 Thumbs down

2

Re: Улучшаем поиск.

вы просто волшебник!!!! спасибо!
я как раз искал как этот поправить... http://forum.imagecms.net/viewtopic.php … 095#p18095

Thumbs up Thumbs down

3

Re: Улучшаем поиск.

vladlen пишет:

вы просто волшебник!!!! спасибо!
я как раз искал как этот поправить... http://forum.imagecms.net/viewtopic.php … 095#p18095

Рад помочь! Я об этой недоработке уже третий месяц им говорю. По-хорошему, такие вещи нужно вообще переводить на полнотекстовый поиск. Хотя переделанный мной "поиск" и работает лучше - это по прежнему фильтр по словам а не полноценный fulltext search (индекс релевантности и прочие плюшки).

Thumbs up Thumbs down

4

Re: Улучшаем поиск.

Супер! Спасибо Вам!

Thumbs up Thumbs down

5

Re: Улучшаем поиск.

Подскажите, где подредактировать запрос в БД, по каким полям искать? В моей версии CMS не ищет по Артикулу. И вообще такого класса как BaseSearch.php нет. Вот мой файл search.php

<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

/**
 * Search Controller
 *
 * @uses ShopController
 * @package Shop
 * @version 0.1
 * @copyright 2010 Siteimage
 * @author <dev@imagecms.net>
 */
class Search extends ShopController {

    protected $perPage = 10;

    public function __construct() {
        parent::__construct();
        $this->load->helper('string');

        // Load per page param
        $this->perPage = ShopCore::app()->SSettings->frontProductsPerPage;

        $this->load->module('core');
        $this->core->set_meta_tags(ShopCore::t('Поиск'));

        $this->index();
        exit;
    }

    /**
     * Display products list.
     *
     * @access public
     */
    public function index() {

        // Start search from one char.
        if (!is_string(ShopCore::$_GET['text']))
            ShopCore::$_GET['text'] = (string) ShopCore::$_GET['text'];

        $get = str_replace(' ', '%', trim(ShopCore::$_GET['text']));

        $query = "SELECT  SP.url, SP.id, S.name, SP.hit, SP.hot, SP.action, SP.id, SP.smallimage, SP.mainmodimage,
            (SELECT stock FROM shop_product_variants WHERE shop_product_variants.product_id = S.id AND stock > 0 LIMIT 1) as stock,
            (SELECT price FROM shop_product_variants WHERE shop_product_variants.product_id = S.id
            OR shop_product_variants.product_id = S.id AND stock = 0 LIMIT 1) as price,
            (SELECT id FROM shop_product_variants WHERE shop_product_variants.product_id = S.id
            OR shop_product_variants.product_id = S.id AND stock = 0 LIMIT 1) as v_id,
            (SELECT old_price FROM shop_product_variants WHERE shop_product_variants.product_id = S.id AND stock > 0 LIMIT 1) as old_price
                    FROM shop_products_i18n S
                    INNER JOIN shop_products SP ON SP.id = S.id
                    WHERE SP.active = 1 AND S.name LIKE '%" . $get . "%'
                    GROUP BY S.id
                    ORDER BY stock DESC ";

        $products = $this->db->query($query)->result();
        $totalProducts = count($products);

        $query .= " LIMIT " . (int) $this->perPage . " OFFSET " . (int) ShopCore::$_GET['per_page'];

        $products = $this->db->query($query)->result();


        if (mb_strlen(ShopCore::$_GET['term']) >= 1) {

        $get = str_replace(' ', '%', trim(ShopCore::$_GET['term']));

        $query = "SELECT  SP.url, S.name, SP.smallimage,
            (SELECT stock FROM shop_product_variants WHERE shop_product_variants.product_id = S.id AND stock > 0 LIMIT 1) as stock,
            (SELECT price FROM shop_product_variants WHERE shop_product_variants.product_id = S.id
            OR shop_product_variants.product_id = S.id AND stock = 0 LIMIT 1) as price,
            (SELECT id FROM shop_product_variants WHERE shop_product_variants.product_id = S.id
            OR shop_product_variants.product_id = S.id AND stock = 0 LIMIT 1) as v_id,
            (SELECT old_price FROM shop_product_variants WHERE shop_product_variants.product_id = S.id AND stock > 0 LIMIT 1) as old_price
                    FROM shop_products_i18n S
                    INNER JOIN shop_products SP ON SP.id = S.id
                    WHERE SP.active = 1 AND S.name LIKE '%" . $get . "%'
                    GROUP BY S.id
                    ORDER BY stock DESC LIMIT 5";

        $products = $this->db->query($query)->result();

        if (sizeof($products) > 0) {
            $temp = array();
            foreach ($products as $product) {
                $temp[] = array(
                    "label" => $product->name,
                    "image" => $product->smallimage,
                    "price" => round($product->price, 2),
                    "url" => shop_url('product/' . $product->url),
                    "term" => $get
                );
            }
            echo json_encode($temp);
            exit;
        }

        }
        // Begin pagination
        $this->load->library('Pagination');
        $this->pagination = new SPagination();
        $config['base_url'] = shop_url('search/' . $this->_getQueryString());
        $config['page_query_string'] = true;
        $config['total_rows'] = $totalProducts;
        $config['per_page'] = $this->perPage;

        $config['next_tag_open'] = '<li class="rightarr">';
        $config['next_tag_close'] = '</li>';
        $config['next_link'] = '';
        $config['last_link'] = false;
        $config['prev_link'] = '';
        $config['first_link'] = false;
        $config['prev_tag_open'] = '<li class="leftarr">';
        $config['prev_tag_close'] = '</li>';
        $config['page_query_string'] = true;
        $config['num_tag_open'] = '<li>';
        $config['num_tag_close'] = '</li>';
        $config['cur_tag_open'] = '<li class="active_page">';
        $config['cur_tag_close'] = '</li>';
        $this->pagination->num_links = 6;
        $this->pagination->initialize($config);
        // End pagination
        //for canonical
        if (isset(ShopCore::$_GET['text']) or ShopCore::$_GET['category'])
            $canonical = "<link href='" . site_url($this->uri->uri_string()) . "' rel='canonical'>";


$other_tags = '<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW" />';
        $this->render('search', array(
            'products' => $products,
            'cart_data' => ShopCore::app()->SCart->getData(),
            'totalProducts' => $totalProducts,
            'brandsInSearchResult' => $brandsInSearchResult,
            'pagination' => $this->pagination->create_links(),
            'tree' => ShopCore::app()->SCategoryTree->getTree(),
            'categorys' => $count_cats,
            'canonical' => $canonical,
            'other_tags' => $other_tags
        ));
    }

    /**
     * Convert price to main currency
     * @param type $sum
     * @return
     */
    protected function convertToMainCurrency($sum) {
        return ShopCore::app()->SCurrencyHelper->convertToMain($sum, ShopCore::app()->SCurrencyHelper->current->getId());
    }

    /**
     * Count total products in category
     *
     * @param SProductsQuery $object
     * @return int */
    protected function _count(SProductsQuery $object) {
        $object = clone $object;
        return $object->count();
    }

    protected function _getQueryString() {
        $data = array();

        $need = array('text', 'f', 'lp', 'rp', 'brand', 'order', 'category');

        foreach ($need as $key => $value) {
            if (isset(ShopCore::$_GET[$value])) {
                $data[$value] = ShopCore::$_GET[$value];
            }
        }

        return '?' . http_build_query($data);
    }

}

/* End of file search.php */

Thumbs up Thumbs down

6

Re: Улучшаем поиск.

Заменил код.
Подскажите как убрать кодировку при поиске?
http://pixs.ru/showimage/Noviytoche_556 … 284816.jpg

Thumbs up Thumbs down

7

Re: Улучшаем поиск.

Greabock, подскажите, пожалуйста, для версии 4.7 случайно еще нет исправлений?  smile
Буду очень рад помощи!
Спасибо!

Thumbs up Thumbs down

8

Re: Улучшаем поиск.

Добрый день, друзья.
Просьба помочь в решении улучшения поиска по сайту!
Если название товара вот такое "Nokia Lumia 630" - то поиск не находит товары, если мы вводим искомую фразу "Nokia 630"...
то есть, чтобы найти, нам нужно искать: или "Nokia Lumia 630" или "Nokia Lumia" или же "Lumia 630" или "Nokia" или "Lumia" ну или же "630"...  почему нельзя это исправить?
на мой вопрос про писк, мне ответили вот как:

"В даному випадку така є логіка та функціональність пошуку по магазину. Нами було оцінено, біля 100 магазинів за останній рік та проаналізували пошук, який був налаштований в Google Analytics. Більш складна логіка пошуку потрібна в тому випадку, якщо трафік на сайт більший ніж 5 тис/день. Інакше не доцільно робити, тому що цільовий трафік на магазин йде відразу на відповідні сторінки по тематиці (категорія або товар). Так і варто починати просування магазину, як по SEO так і по контекстній рекламі".

честно говоря, мне данная логика абсолютно не понятна!
вы опросите людей, как бы они искали мобильный телефон - целиком? ("Nokia Lumia 630") или же "Nokia 630"? - я больше чем уверен что большинство ищет как "Nokia 630"!
если вы оценивали более 100 магазинов, наверняка вы обрабатывали и наших гигантов интернета?! давайте посмотрим:
http://rozetka.com.ua/search/?section=% … =nokia+630
http://allo.ua/catalogsearch/result/?q=nokia+630
http://fotos.ua/shop/mobilnye-telefony/?qr=nokia%20630
и т.д. и везде ищет в любой комбинации! то есть, что мы видим, что у rozetka, allo, fotos и т.д. не правильная логика? ))))) 

Решение указанное в первом посте к сожалению не подходит для версий 4.7 - 4.8 - интересует именно для этих версий!
Спасибо всем отозвавшимся за помощь!

Thumbs up Thumbs down

9

Re: Улучшаем поиск.

Актуально! Прошу помощи!

Thumbs up Thumbs down