Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- class DB
- {
- /**
- * @var \mysqli
- */
- static public $handle = false;
- static public function connect($db, $login, $passw)
- {
- if (self::$handle = new mysqli('localhost', $login, $passw))
- {
- self::$handle->select_db($db);
- self::$handle->set_charset('UTF8');
- return true;
- }
- return false;
- }
- /**
- * Выполняет запрос к БД.
- *
- * @param string $sql
- * @return \mysqli_result|bool
- */
- public static function query($sql)
- {
- return self::$handle->query($sql);
- }
- /**
- * Возвращает строку в стиле mysql
- *
- * @param string $value
- * @param boolean $q
- * @return string
- */
- public static function str($value, $q = true)
- {
- if ($value === null)
- return 'null';
- else
- return ($q ? '"' : '').self::$handle->real_escape_string($value).($q ? '"' : '');
- }
- /**
- * Выполняет запрос к БД и получает одну запись
- *
- * @param string $sql
- * @return bool|array
- */
- public static function get($sql)
- {
- if (!$query = DB::query($sql))
- return false;
- return $query->fetch_assoc();
- }
- }
- if (!function_exists('stem')) {
- function stem($word, $stemmer) {
- /** @var \Lingua_Stem_Ru $stemmer */
- return $stemmer->stem_word($word);
- }
- }
- class ISearch
- {
- public static $RUSSIAN;
- const tbl_search= 'search';
- const tbl_category= 'search_category';
- const tbl_pages = 'search_pages';
- const tbl_page = 'search_page';
- const tbl_results = 'search_results';
- const tbl_words = 'search_words';
- const tbl_stopwords = 'search_stopwords';
- const id = 'id';
- const active = 'active';
- const position = 'position';
- const title = 'title';
- const path = 'path';
- public static $storage_engine = 'MyISAM'; //'InnoDB';
- /**
- * язык подключаемого стеммера (например 'Ru').
- * соответствующий класс (Lingua_Stem_Ru) должен находиться в файле "search.stem-ru.php"
- *
- * или константа подключаемого стеммера (например STEM_RUSSIAN_UNICODE),
- * если у вас установлено расширение php_stem
- *
- * @var string
- */
- public static $stemmer;
- private static $stemmers = [];
- public static $words_mask = false;
- public static $categories = array();
- private static function html2text($text)
- {
- $text = preg_replace(array(
- '~<script[^>]*?>.*?<\/script>~si', // Strip out javascript
- '~<style[^>]*?>.*?<\/style>~si', // Strip style tags properly
- '~<[/\!]*?[^<>]*?>~si', // Strip out HTML tags
- '~<![\s\S]*?--[ \t\n\r]*>~', // Strip multi-line comments including CDATA
- '~{{.+?}}~',
- ), ' ', $text);
- $text = htmlspecialchars_decode($text);
- $text = preg_replace('~\W~u', ' ', $text); //удаляем все символы кроме букв и цифр
- $text = preg_replace('~\x20\w\x20~u', ' ', $text); //удаляем все одиночные буквы
- $text = preg_replace('~\x20{2,}~', ' ', $text);
- $text = trim($text);
- return $text;
- }
- /**
- * Добавить страницу к поиску
- * @param string $category группа, к которой принадлежит страница
- * @param int $page_id id страницы в вашем каталоге
- * @param bool $active только активные будут участвовать в поиске
- * @param string $title заголовок страницы в поиске
- * @param string $path путь к странице в результатах поиска
- * @param string $text
- * @return bool
- */
- public static function add($category, $page_id, $active, $title, $path, $text)
- {
- $text = self::html2text($text); //очищаем текст от html, !@#$^:<>......
- if (empty($text)) return false;
- // $text = explode(' ', $text); //строку в массив
- // $text = array_unique($text); //убираем все повторения слов
- // if (empty($text)) return false;
- //добавляем информацию о новой странице
- if (DB::query(strtr('
- INSERT IGNORE INTO :tbl_page SET
- category_id = :category_id,
- page_id = :page_id,
- active = :active,
- path = :path,
- title = :title
- ', array(
- ':tbl_page' => self::tbl_page,
- ':category_id' => self::$categories[$category],
- ':page_id' => $page_id,
- ':active'=>$active,
- ':path' => DB::str($path),
- ':title' => DB::str($title),
- ))))
- {
- $search_page_id = DB::$handle->insert_id;
- }
- else
- {
- //если не удалось добавить, то, скорей всего, страница уже существует,
- //получаем её id
- if (!$search_page_id = DB::get(strtr('
- SELECT id FROM :page
- WHERE category_id = :category_id
- AND page_id = :page_id
- ', array(
- ':page'=>self::tbl_page,
- ':category_id'=>self::$categories[$category],
- ':page_id'=>$page_id,
- ))))
- return false;
- $search_page_id = $search_page_id['id'];
- }
- $text = explode(' ', $text);
- //если стеммер включён
- if ($stemmer = self::$stemmer)
- {
- //если у вас установлено php extension php_stem, то этот блок вам не нужен
- if (is_string($stemmer))
- {
- if (isset(self::$stemmers[$stemmer]))
- {
- $stemmer = self::$stemmers[self::$stemmer];
- }
- else
- {
- $stemmer = self::getStemmer();
- //закешируем созданный stemmer, чтобы не создавать новые
- //в случае многократного вызова функции
- self::$stemmers[self::$stemmer] = $stemmer;
- }
- }
- //надо разобрать каждое слово
- foreach ($text as &$word)
- {
- $word = stem($word, $stemmer);
- unset($word);
- }
- }
- DB::query(strtr('
- CREATE TEMPORARY TABLE IF NOT EXISTS temp_words (
- main_id MEDIUMINT UNSIGNED DEFAULT "0",
- word VARCHAR(25),
- UNIQUE KEY word (word)
- ) ENGINE=:engine DEFAULT CHARSET=utf8
- ', array(':engine'=>self::$storage_engine)));
- //заполняем временную таблицу слов словами из текста
- DB::query(strtr('
- INSERT IGNORE INTO temp_words (word)
- VALUES :words
- ', array(':words'=>'("'.implode('"),("', $text).'")')));
- //убрать из списка новых слов стоп-слова
- // DB::query(strtr('
- // DELETE temp_words FROM temp_words
- // INNER JOIN :tbl_stopwords ON (temp_words.word = :tbl_stopwords.word)
- // ', array(
- // ':tbl_stopwords'=>self::tbl_stopwords,
- // )));
- //передаём слова в основную таблицу, т.к. в основной таблице
- //уникальный индекс, то попадут в неё только новые слова.
- DB::query(strtr('
- INSERT INTO :tbl_words (word)
- SELECT word FROM temp_words
- ON DUPLICATE KEY UPDATE count = count + 1
- ', array(':tbl_words'=>self::tbl_words)));
- DB::query(strtr('
- UPDATE :tbl_words SET
- main_id = id
- WHERE NOT main_id
- ', array(':tbl_words'=>self::tbl_words)));
- //забираем из основной таблицы идентификаторы слов или их синонимов
- DB::query(strtr('
- UPDATE temp_words
- INNER JOIN :tbl_words words ON temp_words.word = words.word
- SET temp_words.main_id = words.main_id
- ', array(':tbl_words'=>self::tbl_words)));
- //добавляем в таблицу список идентификаторов слов встретившихся в тексте
- DB::query(strtr('
- INSERT IGNORE INTO :tbl_pages (page_id, word_id)
- SELECT :page_id, main_id FROM temp_words
- ', array(
- ':tbl_pages'=>self::tbl_pages,
- ':page_id' => $search_page_id,
- )));
- //очищаем временную таблицу слов
- DB::query('TRUNCATE TABLE temp_words');
- return true;
- }
- /**
- * Обновить страницу в поиске
- * @param string $category группа, к которой принадлежит страница
- * @param int $page_id id страницы в вашем каталоге
- * @param bool $active только активные будут участвовать в поиске
- * @param string|null $title заголовок страницы в поиске
- * @param string|null $path путь к странице в результатах поиска
- * @param string $text
- * @return bool
- */
- public static function update($category, $page_id, $active, $title, $path, $text)
- {
- if (!$search_page_id = DB::get(strtr('
- SELECT SQL_NO_CACHE id FROM :tbl_page
- WHERE category_id = :category_id
- AND page_id = :page_id
- ', array(
- ':tbl_page'=>self::tbl_page,
- ':category_id'=>self::$categories[$category],
- ':page_id'=>$page_id,
- ))))
- return false;
- $search_page_id = $search_page_id['id'];
- DB::query(strtr('
- UPDATE :tbl_page SET
- active = :active,
- path = :path,
- title = :title
- WHERE
- category_id = :category_id
- AND page_id = :page_id
- ', array(
- ':tbl_page'=>self::tbl_page,
- ':active'=>$active,
- ':path' => $path ? DB::str($path) : '`path`',
- ':title' => $title ? DB::str($title) : '`title`',
- ':category_id' => self::$categories[$category],
- ':page_id' => $page_id,
- )));
- DB::query(strtr('
- TRUNCATE TABLE :tbl_results
- ', array(':tbl_results'=>self::tbl_results)));
- DB::query(strtr('
- DELETE FROM :tbl_pages
- WHERE page_id = :page_id
- ', array(
- ':tbl_pages'=>self::tbl_pages,
- ':page_id' => $search_page_id,
- )));
- return self::add($category, $page_id, $active, $title, $path, $text);
- }
- /**
- * Удалить страницу из поиска
- * @param string $category группа, к которой принадлежит страница
- * @param int $page_id id страницы в вашем каталоге
- * @return bool
- */
- public static function delete($category, $page_id)
- {
- if (!$page_id = (int)$page_id)
- return false;
- if (!$page_id = DB::get(strtr('
- SELECT SQL_NO_CACHE page_id FROM :tbl_page
- WHERE category_id = :category_id
- AND page_id = :page_id
- ', array(
- ':tbl_page'=>self::tbl_page,
- ':category_id'=>self::$categories[$category],
- ':page_id'=>$page_id,
- ))))
- return false;
- $page_id = $page_id['page_id'];
- DB::query(strtr('
- DELETE FROM :tbl_results
- WHERE page_id = :page_id
- ', array(
- ':tbl_results'=>self::tbl_results,
- ':page_id'=>$page_id,
- )));
- DB::query(strtr('
- DELETE FROM :tbl_pages
- WHERE page_id = :page_id
- ', array(
- ':tbl_pages'=>self::tbl_pages,
- ':page_id'=>$page_id,
- )));
- DB::query(strtr('
- DELETE FROM :tbl_page
- WHERE id = :page_id
- ', array(
- ':tbl_page'=>self::tbl_page,
- ':page_id'=>$page_id,
- )));
- return true;
- }
- /**
- * заполняем таблицу словами близкими по значению
- * пример: synonym('подгузник', 'pampers')
- * synonym('pampers', 'памперс')
- * в данном примере слова 'pampers' и 'памперс' будут приравнены к 'подгузник'
- * и в случае поиска по одному из этих трёх слов, страница будет найдена, если в ней
- * встречается любое и этих слов.
- *
- * так же можно добавить слова имеющие приблизительно одинаковое значение
- * пример: synonym('телевизор', 'sony') ..ничего другого не придумал
- * synonym('конструктор', 'lego')
- *
- * слова в написанные в транскрипции
- * пример: synonym('mercedes', 'мерседес')
- *
- * слова в различных падежах
- * пример: synonym('цветок', 'цветком')
- * synonym('цветок', 'цветка')
- * synonym('цветок', 'цветах')
- * ........
- *
- * @param string $word
- * @param string|null $synonym
- * @return bool
- */
- public static function synonym($word, $synonym = null)
- {
- if ($word === $synonym) $synonym = null;
- //находим слово
- if (!$word_id = DB::get(strtr('
- SELECT SQL_NO_CACHE id, main_id FROM :tbl_words
- WHERE word = :word
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':word'=>DB::str($word),
- )))) {
- //если не нашли, то пытаемся его добавить
- if (DB::query(strtr('
- INSERT INTO :tbl_words SET
- word = :word
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':word'=>DB::str($word),
- )))) {
- //если синоним не задан, уходим
- if (is_null($synonym))
- return true;
- $word_id = new \stdClass;
- $word_id->{'id'} = DB::$handle->insert_id;
- $word_id->{'main_id'} = $word_id->{'id'};
- }
- else {
- //если не получилось добавить, уходим
- return false;
- }
- }
- //если $synonym === null значит $word больше не должно быть синонимом другого слова
- if (is_null($synonym))
- {
- //отвязываем синоним от слова
- DB::query(strtr('
- UPDATE :tbl_words SET
- main_id = id
- WHERE id = :id
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':id'=>$word_id['id'],
- )));
- return true;
- }
- //если $synonym !== null значит $synonym надо привязать к $word
- //если "главное слово" это не синоним другого слова, то берём его id, иначе id более главного слова
- $word_id = $word_id->{'id'} === $word_id->{'main_id'} ? $word_id->{'id'} : $word_id->{'main_id'};
- //находим слово, которое надо превратить в синоним
- if ($synonym_id = DB::get(strtr('
- SELECT SQL_NO_CACHE id FROM :tbl_words
- WHERE word = :synonym
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':synonym'=>DB::str($synonym)
- )))) {
- $synonym_id = $synonym_id['id'];
- }
- else {
- //если не нашли, то пытаемся его добавить
- if (DB::query(strtr('
- INSERT INTO :tbl_words SET word = :word
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':word'=>DB::str($synonym),
- )))) {
- $synonym_id = DB::$handle->insert_id;
- }
- else {
- //если не получилось добавить, уходим
- return false;
- }
- }
- //делаем его синонимом
- DB::query(strtr('
- UPDATE :tbl_words SET main_id = :word_id
- WHERE id = :id
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':word_id'=>$word_id,
- ':id'=>$synonym_id,
- )));
- //все другие слова, которые были синонимами этого слова, присваиваем главному слову
- DB::query(strtr('
- UPDATE :tbl_words SET main_id = :word_id
- WHERE main_id = :id
- ', array(
- ':tbl_words'=>self::tbl_words,
- ':word_id'=>$word_id,
- ':id'=>$synonym_id,
- )));
- return true;
- }
- const STOPWORD_CHECK = 0;
- const STOPWORD_ADD = 1;
- const STOPWORD_REMOVE = -1;
- /**
- * Работа со стоп-словами
- * @param string $word
- * @param int $flag STOPWORD_CHECK - проверить принадлежит ли слово к группе стоп-слов,
- * STOPWORD_ADD - добавить слово в стоп-словарь,
- * STOPWORD_REMOVE - удалить из словаря
- * @return bool
- */
- public static function stopword($word, $flag = self::STOPWORD_ADD)
- {
- if ($flag === self::STOPWORD_CHECK) {
- if ($count = DB::get(strtr('
- SELECT SQL_NO_CACHE COUNT(*) AS count FROM :tbl_stopwords
- WHERE word = :word
- ', array(
- ':tbl_stopwords'=>self::tbl_stopwords,
- ':word'=>DB::str($word),
- ))))
- {
- return (int)$count['count'] > 0;
- }
- return false;
- }
- if ($flag === self::STOPWORD_REMOVE) {
- return DB::query(strtr('
- DELETE FROM :tbl_stopwords
- WHERE word = :word
- ', array(
- ':tbl_stopwords'=>self::tbl_stopwords,
- ':word'=>DB::str($word),
- )));
- }
- if ($flag === self::STOPWORD_ADD) {
- return DB::query(strtr('
- INSERT INTO :tbl_stopwords SET word = :word
- ', array(
- ':tbl_stopwords'=>self::tbl_stopwords,
- ':word'=>DB::str($word),
- )));
- }
- return false;
- }
- /**
- * @param string $text
- * @return array|bool
- */
- private static function internal_clean_query($text)
- {
- $text = preg_replace('~[^\w\-*]~u', ' ', $text); //удаляем все символы кроме букв и цифр
- //if (preg_match_all('~(\w+[’*]\w*|\w*[’*]\w+|\w+|\s-)~u', $text, $out)) //удаляем все символы кроме букв и цифр и разрешённых символов
- // $text = implode(' ', $out[1]);
- $text = preg_replace('~\x20\w{1,2}\x20~u', ' ', $text); //удаляем все одиночные буквы
- $text = trim($text);
- if (empty($text)) return false;
- $search = array('inc'=>'', 'exc'=>'');
- //если в запросе встречаются слова исключения
- if (($pos = strpos($text, ' -')) !== false)
- {
- $search['inc'] = substr($text, 0, $pos);
- $search['exc'] = substr($text, $pos+2);
- }
- else
- {
- $search['inc'] = $text;
- }
- //если слово написано через дефис, разделяем его на два слова
- $search['inc'] = str_replace('-', ' ', $search['inc']);
- $search['exc'] = str_replace('-', ' ', $search['exc']);
- //заменяем любое кол-во пробелов на один пробел
- $search['inc'] = preg_replace('~\x20{2,}~', ' ', $search['inc']);
- $search['exc'] = preg_replace('~\x20{2,}~', ' ', $search['exc']);
- //убираем пробелы в начале и в конце
- $search['inc'] = trim($search['inc']);
- $search['exc'] = trim($search['exc']);
- //начинаем охоту за стоп-словами
- //берём весь очищенный текст
- $text = $search['inc'].(empty($search['exc']) ? '' : ' '.$search['exc']);
- $text = explode(' ', $text);
- //запрашиваем пересечение слов из запроса и из стоп-словаря
- if ($query = DB::query(strtr('
- SELECT word FROM :tbl_stopwords
- WHERE word IN (:words)
- ', array(
- ':tbl_stopwords' => self::tbl_stopwords,
- ':words' => '"'.implode('","', $text).'"',
- )))) {
- while ($word = $query->fetch_assoc())
- $stopwords[] = $word['word'];
- }
- //если пересечения найдены
- if (!empty($stopwords)) {
- //избавляемся от стоп-слов в запросе
- foreach ($search as $key=>$words)
- {
- $words = explode(' ', $words);
- $words = array_flip($words);
- foreach ($stopwords as $word)
- {
- if (isset($words[$word]))
- unset($words[$word]);
- }
- $words = array_flip($words);
- $words = implode(' ', $words);
- $search[$key] = $words;
- }
- }
- return $search;
- }
- /**
- * Возвращает запрос очищенный от лишних символов, стоп-слов, ...
- * @param string $text
- * @return string
- */
- public static function clean_query($text)
- {
- $search = self::internal_clean_query($text);
- //очищенный от всего мусора запрос
- return $search['inc'].(empty($search['exc']) ? '' : ' - '.$search['exc']);
- }
- protected static function getStemmer()
- {
- include_once(strtr('search.stem-:lang.php', array(':lang'=>strtolower(self::$stemmer))));
- $stemmer = 'Lingua_Stem_'.self::$stemmer;
- return new $stemmer;
- }
- private static function internal_search($text)
- {
- $search = self::internal_clean_query($text);
- //очищенный от всего мусора запрос
- $text = $search['inc'].(empty($search['exc']) ? '' : ' - '.$search['exc']);
- if ($stemmer = self::$stemmer)
- {
- if (is_string($stemmer))
- {
- $stemmer = self::getStemmer();
- }
- }
- //проверяем были ли похожие запросы
- if (!$search_id = DB::get(strtr('
- SELECT id FROM :tbl_search
- WHERE text=:text
- ', array(
- ':tbl_search'=>self::tbl_search,
- ':text'=>DB::str($text),
- ))))
- {
- if (DB::query(strtr('
- INSERT INTO :tbl_search SET
- count=1,
- text = :text
- ', array(
- ':tbl_search'=>self::tbl_search,
- ':text'=>DB::str($text),
- )))){
- $search_id = DB::$handle->insert_id;
- }
- }
- else {
- $search_id = $search_id['id'];
- //увеличиваем счётчик запросов
- DB::query(strtr('
- UPDATE :tbl_search SET
- count = count+1
- WHERE id = :id
- ', array(
- ':tbl_search'=>self::tbl_search,
- ':id'=>$search_id,
- )));
- //проверяем были ли результаты подобного поиска
- if ($count = DB::get(strtr('
- SELECT COUNT(*) AS count
- FROM :tbl_results
- WHERE search_id=:id
- ', array(
- ':tbl_results'=>self::tbl_results,
- ':id'=>$search_id,
- )))) {
- if ($count['count'])
- return $search_id;
- };
- }
- if (!$search_id) return 0;
- //таблица промежуточных результатов поиска
- DB::query(strtr('
- CREATE TEMPORARY TABLE search_tmp (
- page_id MEDIUMINT UNSIGNED NOT NULL,
- status TINYINT(2) UNSIGNED NOT NULL,
- UNIQUE KEY page_id (page_id)
- ) ENGINE=:engine ROW_FORMAT=FIXED DEFAULT CHARSET=utf8;
- ', array(':engine'=>self::$storage_engine)));
- $real_words = false;
- /**
- * @param array $words
- * @return array
- */
- $get_real_words = function($words) use($stemmer, &$real_words)
- {
- $real_words = [];
- foreach ($words as $word)
- {
- //если встретилось слово с маской и маска разрешена
- //прекращщаем операцию
- if ((strpos($word, '*') !== false) && self::$words_mask) {
- $real_words = false;
- break;
- }
- //если stemmer включен
- elseif ($stemmer)
- {
- $real_words[] = stem($word, $stemmer);
- }
- else {
- $real_words[] = $word;
- }
- }
- if ($real_words)
- {
- $words = array();
- if ($query = DB::query(strtr('
- SELECT word FROM search_words
- WHERE word IN (:words)
- ORDER BY count
- ', array(
- ':words'=>'"'.implode('","', $real_words).'"'
- )))) {
- $real_words = true;
- while ($word = $query->fetch_assoc())
- {
- $words[] = "word = '{$word['word']}'";
- }
- }
- }
- return $words;
- };
- /**
- * @param array $words
- * @return array
- */
- $get_words = function($words) use($stemmer)
- {
- foreach ($words as &$word)
- {
- if (empty($word)) {
- unset($word);
- continue;
- }
- if ((strpos($word, '*') !== false) && self::$words_mask)
- {
- $word = str_replace('*', '%', $word);
- $word = "word LIKE '$word'";
- }
- elseif ($stemmer)
- {
- $word = stem($word, $stemmer);
- //если индексирование проводилось с учётом стеммера,
- //то ищем точное совпадение, иначе по части слова
- $word = "word = '$word'";
- }
- else
- {
- $word = "word = '$word'";
- }
- unset($word);
- }
- };
- $status = 0;
- foreach ($search as $key => $words)
- {
- $words = explode(' ', $words);
- $words = array_unique($words);
- //если находимся в ветке искомых слов,
- //попытаемся отсортировать слова по частоте использования
- if ($key === 'inc')
- {
- $words = $get_real_words($words);
- }
- //если на предыдущем шаге не удалось обработать слова
- //обрабатываем их другим способом
- if (!$real_words)
- {
- $words = $get_words($words);
- }
- foreach ($words as $word)
- {
- if (empty($word)) continue;
- if ($key === 'inc')
- {
- if ($status === 0) {
- DB::query(strtr('
- INSERT IGNORE INTO search_tmp
- SELECT page.id, 1 FROM :tbl_page AS page
- LEFT JOIN :tbl_pages AS pages ON (
- page.id = pages.page_id
- AND page.active
- )
- INNER JOIN :tbl_words AS words ON (
- pages.word_id = words.main_id
- AND :word
- )
- ', array(
- ':tbl_page' => self::tbl_page,
- ':tbl_pages' => self::tbl_pages,
- ':tbl_words' => self::tbl_words,
- ':word' => $word,
- )));
- }
- else {
- //в оставшихся спроках помечаем те, которые подходят под следующее условие
- DB::query(strtr('
- UPDATE search_tmp
- INNER JOIN :tbl_pages pages ON search_tmp.page_id = pages.page_id
- INNER JOIN :tbl_words words ON pages.word_id = words.main_id
- SET search_tmp.status = :status + 1
- WHERE search_tmp.status = :status
- AND (:word)
- ', array(
- ':tbl_pages' => self::tbl_pages,
- ':tbl_words' => self::tbl_words,
- ':status' => $status,
- ':word' => $word,
- )));
- }
- $status++;
- }
- else {
- DB::query(strtr('
- UPDATE search_tmp
- INNER JOIN :tbl_pages pages ON search_tmp.page_id = pages.page_id
- INNER JOIN :tbl_words words ON pages.word_id = words.main_id
- SET search_tmp.status = 0
- WHERE (:word)
- ', array(
- ':tbl_pages' => self::tbl_pages,
- ':tbl_words' => self::tbl_words,
- ':word' => $word,
- )));
- }
- }
- }
- DB::query('SET @pos=0');
- DB::query(strtr('
- INSERT INTO :tbl_results
- SELECT :id, @pos:=@pos+1, search_tmp.page_id
- FROM search_tmp
- INNER JOIN :tbl_page page ON search_tmp.page_id = page.id
- WHERE search_tmp.status = :status
- ORDER BY page.category_id, page.title
- ', array(
- ':tbl_results' => self::tbl_results,
- ':tbl_page' => self::tbl_page,
- ':id' => $search_id,
- ':status' => $status,
- )));
- return $search_id;
- }
- /**
- * @param string $text
- * @param int $page_num номер выводимой страницы поиска
- * @param int $limit кол-во элементов на странице
- * @param string $category группа страниц по которым осуществляется поиск
- * @return array ['id'=>'', 'active'=>'', 'category_id'=>'', 'page_id'=>'', 'path'=>'', 'title'=>'']
- */
- public static function search($text, $page_num = 0, $limit = 25, $category = '*')
- {
- $pages = array();
- if (!$search_id = self::internal_search($text))
- return $pages;
- if ($query = DB::query(strtr('
- SELECT page.*, results.position
- FROM :tbl_results results
- LEFT JOIN :tbl_page page ON (
- results.page_id = page.id
- AND page.active
- )
- WHERE (:category OR page.category_id = :category_id)
- AND results.search_id = :search_id
- AND (results.position > :from AND results.position <= :to)
- ORDER BY results.position
- ', array(
- ':tbl_page'=>self::tbl_page,
- ':tbl_results'=>self::tbl_results,
- ':category'=>$category === '*' ? 1 : 0,
- ':category_id'=>$category === '*' ? 0 : self::$categories[$category],
- ':search_id'=>$search_id,
- ':from'=>$page_num * $limit,
- ':to'=>$page_num * $limit + $limit,
- )))) {
- $pages = $query->fetch_all(MYSQLI_ASSOC);
- }
- return $pages;
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////
- //для подключения к базе не забудьте вписать корректные параметры
- DB::connect('my_db', 'root', '');
- /////////////////////////////////////////////////////////////////////////////////////
- ISearch::$RUSSIAN = defined('STEM_RUSSIAN_UNICODE') ? STEM_RUSSIAN_UNICODE : 'Ru';
- //при первом обращении к скрипту загружаются группы (что такое группы написано ниже)
- if ($query = DB::query(strtr('SELECT * FROM :category',
- array(':category'=>ISearch::tbl_category))))
- {
- while ($category = $query->fetch_assoc())
- {
- ISearch::$categories[$category['category']] = $category['id'];
- }
- }
- /*
- -- Обратите внимание на ENGINE=MyISAM, и выберите подходящую (MyISAM или InnoDB)
- -- Также в ISearch::$storage_engine выберите нужный вариант (нужно для временных таблиц)
- CREATE TABLE IF NOT EXISTS `search` (
- `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
- `count` mediumint(8) unsigned NOT NULL DEFAULT '0',
- `text` varchar(255) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_stopwords` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `word` varchar(25) NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `word` (`word`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_page` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `active` tinyint(1) unsigned NOT NULL DEFAULT '0',
- `category_id` smallint(5) unsigned NOT NULL,
- `page_id` int(10) unsigned NOT NULL,
- `path` varchar(255) DEFAULT NULL,
- `title` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `category_id` (`category_id`,`page_id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_pages` (
- `page_id` int(10) unsigned NOT NULL,
- `word_id` mediumint(8) unsigned NOT NULL,
- UNIQUE KEY `page_id_word_id` (`page_id`,`word_id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_results` (
- `search_id` mediumint(8) unsigned NOT NULL,
- `position` mediumint(8) unsigned NOT NULL DEFAULT '0',
- `page_id` int(10) unsigned NOT NULL
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_words` (
- `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
- `main_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
- `count` mediumint(8) unsigned NOT NULL DEFAULT '1',
- `word` varchar(25) NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `word` (`word`),
- KEY `main_id` (`main_id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
- -- --------------------------------------------------------
- CREATE TABLE IF NOT EXISTS `search_category` (
- `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
- `category` varchar(15) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
- INSERT INTO `search_category` (`id`, `category`) VALUES
- (1, 'page'),
- (2, 'catalog'),
- (3, 'product'),
- (4, 'news');
- */
- /* ----- ПЛАН ДЕЙСТВИЙ (пример есть далее) ------------------------------
- //очищаем таблицы перед индексированием
- DB::query(strtr('
- TRUNCATE TABLE :tbl_page
- ', array(':tbl_page' => ISearch::tbl_page)));
- DB::query(strtr('
- TRUNCATE TABLE :tbl_pages
- ', array(':tbl_pages' => ISearch::tbl_pages)));
- DB::query(strtr('
- TRUNCATE TABLE :tbl_results
- ', array(':tbl_results' => ISearch::tbl_results)));
- //таблицу слов можно не очищать перед индексированием, тогда
- //не придётся её заново заполнять
- //DB::query(strtr('
- // TRUNCATE TABLE :tbl_words
- //', array(':tbl_words' => ISearch::tbl_words)));
- //таблицу слов можно заполнить вручную
- ISearch::synonym('слово');
- ISearch::synonym(...);
- ISearch::synonym(...);
- //можно задать синонимы
- ISearch::synonym('цветок', 'цветка');
- ISearch::synonym('цветок', 'цветком');
- ISearch::synonym('мерседес', 'mercedes');
- //и приступаем непосредственно к индексированию
- //select * from какой-то_таблицы
- //foreach(по записям) {
- ISearch::add(
- 'page', //группа страниц (см. ниже)
- $id, //идентификатор вашей страницы (см. ниже)
- true, //указываем активна ли в данный момент страница
- //если не активна, то она проиндексируется, но не будет отражена
- //в результатах поиска
- $title, //заголовок, который будет выведен в результатах поиска
- $path, //ссылка на страницу
- $text //любой текст для индекса, актуальный для этой страницы:
- //заголовок, описание страницы, товара, сообщений с форума...
- );
- //} //end foreach
- //select * from другой_таблицы
- //foreach(по записям) {
- ISearch::add(
- 'news',
- ....
- //} //end foreach
- //что такое "группа страниц" - это разделение вашего индекса на логические группы.
- //можно конечно свалить весь индекс в кучу, но тогда не возможно будет ограничить
- //какой либо группой товара, или ограничить поиск страницами форума, новостями, ...
- //"идентификатор вашей страницы" - обычно у каждой страницы (новости, товара, ветки каталога)
- //есть уникальный идентификатор, по нему же и разделяются страницы в индексе в рамках одной группы.
- // и сам поиск
- ISearch::search(
- 'найди меня',
- 0,
- 25
- );
- */
- /* -------------------- ТЕСТ ------------------------ */
- //после того как вы создали таблицы и вписали корректные параметры подключения
- //раскомментируйте первый шаг, проиндексируйте вашу таблицу (запустив скрипт http://localhost/search.php)
- //закомментируйте 1шаг
- //раскомментируйти шаг 2, и попробуйте что-нибудь найти (http://localhost/search.php?search=hello)
- //1. шаг
- //false - пропустить этот шаг (индексирование)
- //true - выполнить
- if (false) {
- set_time_limit(0); //индексация может занять много времени,
- //если у вас большой базы
- DB::query(strtr('
- TRUNCATE TABLE :tbl_page
- ', array(':tbl_page' => ISearch::tbl_page)));
- DB::query(strtr('
- TRUNCATE TABLE :tbl_pages
- ', array(':tbl_pages' => ISearch::tbl_pages)));
- DB::query(strtr('
- TRUNCATE TABLE :tbl_results
- ', array(':tbl_results' => ISearch::tbl_results)));
- //добавляем слова "синонимы"
- ISearch::synonym('велосипед', 'велописед');
- ISearch::synonym('велосипед', 'велик');
- //можно добавить проскланённые слова
- //ISearch::synonym('велосипед', 'велосипедов');
- //ISearch::synonym('велосипед', 'велосипедами');
- //ISearch::synonym('велосипед', 'велосипеде');
- //добавляем слова которые не надо учитывать в поиске
- ISearch::stopword('все');
- ISearch::stopword('очень');
- //базу можно проиндексировать стеммером Портера
- //индексация, возможно, займёт больше времени, т.к. надо обработать каждое слово в тексте
- //можно не индексировать стеммером Портера, а включить стеммер только для поиска
- //для включения стеммера эти два параметра обязательны.
- //не забудьте скачать файл search.stem-ru.php ( http://pastebin.com/PvJL9d7F ), если
- //у вас не установлено extension php_stem
- ISearch::$stemmer = ISearch::$RUSSIAN; //указываем язык стеммера или
- //если оставить пустым - индексация будет проводиться обычным способом
- if ($query = DB::query('
- SELECT * FROM shop_products
- -- LiMiT 1000
- ')) {
- while ($rec = $query->fetch_assoc())
- {
- //'id', 'active', 'title', 'text' - это поля в вашей таблице
- ISearch::add(
- 'product',
- $rec['id'],
- $rec['active'],
- $rec['title'], //заголовок, который будет выведен в результатах поиска
- '/products/'.$rec['id'], //ссылка на страницу
- $rec['title'].' '.$rec['text'] //индексируем по двум полям
- );
- }
- }
- return;
- }
- //2. шаг
- //false - прогпустить этот шаг (поиск)
- //true - выполнить
- if (false)
- {
- //если что база была проиндексирована стеммером Портера
- ISearch::$stemmer = ISearch::$RUSSIAN;
- $pages = ISearch::search($_GET['search'], 0, 150);
- ?>
- <ul>
- <?
- foreach ($pages as $page)
- {
- ?>
- <li><a href="<?=$page['path'] ?>"><?=htmlspecialchars($page['title']) ?></a>
- <?
- }
- ?>
- </ul>
- <?
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement