Тема: Улучшаем поиск.
На одном из последних проектов, я перепилил логику поиска для автокомплита и полного поиска.
Это позволяет совершать поиск по нескольким словам-ключам.
/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__().