Перейти к содержанию

YetiSearch Pro

Важно

Продуктам Premium нужен бесплатный плагин License Manager. Установите его и добавьте лицензию продукта перед установкой этого продукта.

Установка

Сначала убедитесь, что у вас последняя версия Grav 1.7 (-f принудительно обновляет индекс GPM).

$ bin/gpm selfupgrade -f

Установите YetiSearch Pro через GPM:

$ bin/gpm install yetisearch-pro

Плагин также можно установить через раздел Plugins в плагине админки.

После установки запустите первую индексацию, чтобы заполнить базу поиска:

$ bin/plugin yetisearch-pro index --flush

Требования

  • PHP 7.4 или выше
  • SQLite 3.24.0 или выше (для лучшей производительности рекомендуется 3.35.0+)
  • Включённое расширение SQLite FTS5 — YetiSearch использует виртуальные таблицы FTS5 для полнотекстового поиска и ранжирования BM25. Ваша сборка PHP должна быть связана с библиотекой SQLite, скомпилированной с FTS5 (ENABLE_FTS5).
  • Расширение PHP PDO SQLite
  • Расширение PHP Mbstring
  • Расширение PHP JSON
  • Индексация PDF (необязательно, 2.2.0+) требует PHP 8.3 или выше и расширения PHP zlib. На более старых версиях PHP всё остальное работает, а PDF пропускаются.

Проверить версию SQLite и поддержку FTS5 можно встроенным скриптом из корня Grav:

$ php user/plugins/yetisearch-pro/vendor/yetidevworks/yetisearch/scripts/check_sqlite_features.php

Или посмотреть версию SQLite напрямую:

$ php -r "echo 'SQLite: ' . \SQLite3::version()['versionString'] . PHP_EOL;"

На macOS PHP из Homebrew обычно включает свежий SQLite с FTS5. Некоторые дистрибутивы Linux и хостинги с общим доступом могут поставлять старые версии SQLite — см. Устранение неполадок ниже.

Фичи

  • 100% локальный поиск — поиск по файлам без внешних зависимостей (необязательный семантический поиск использует API эмбеддингов)
  • Два дизайна модального окна — выберите «simple» (лёгкое автодополнение) или «full» (две панели с предпросмотром)
  • Vanilla JS — без зависимостей от фреймворков, работает с любой темой
  • CSS-переменные — простое оформление через пользовательские свойства CSS
  • Умная индексация — обнаружение изменений по отпечатку для эффективных обновлений
  • Очистка осиротевших записей — автоматически удаляет устаревшие документы удалённых страниц
  • Обновления в реальном времени — обновление индекса при сохранении или удалении страницы (необязательно)
  • Многоязычность — стратегии на язык или единого индекса
  • Нечёткий поиск — находит результаты, несмотря на опечатки
  • Стемминг — необязательно. Поиск также находит другие формы слова (runs находит «running») в английском, французском, немецком и испанском (2.3.0+)
  • Индексация фрагментами — точные результаты поиска внутри длинного содержимого
  • Аналитика — отслеживание поисков, переходов и популярных запросов
  • Геопоиск — задайте страницам местоположение и ищите по расстоянию: радиус, сначала ближайшие и кнопка «Near me» в полном модальном окне (2.3.0+)
  • Права — детальные права администратора (просмотр, изменение, администрирование)
  • Планировщик — автоматическая переиндексация и обслуживание через планировщик Grav
  • Команды CLI — полный интерфейс командной строки для индексации, запросов и обслуживания
  • Индексация PDF — необязательный поиск внутри PDF с результатами, ведущими на нужную страницу PDF (2.2.0+)
  • Семантический поиск — необязательный поиск по смыслу, а не только по ключевым словам, через OpenAI, OpenRouter, Ollama или любой совместимый с OpenAI API эмбеддингов (2.1.0+)

Конфигурация

YetiSearch Pro настраивается через user/plugins/yetisearch-pro/yetisearch-pro.yaml или в интерфейсе плагина админки.

Базовая конфигурация

enabled: true

engine:
  storage_dir: 'user://data/yetisearch-pro'
  index_prefix: 'grav_'
  timeout: 5
  max_batch_size: 10
  search:
    snippet_length: 75   # длина окна фрагмента по умолчанию
    stem_weight: 0.5     # при включённом стемминге: насколько учитывается совпадение со стеммом слова (от 0 до 1)

indexes:
  pages:
    strategy: single  # или 'per_language'
    languages: []           # пусто: определить по языкам и версиям Grav
    stemming: false         # также находить другие формы слова; см. раздел «Стемминг» ниже
    geo:                    # откуда берётся местоположение страницы (точечные пути в заголовке страницы)
      lat: geo.lat
      lng: geo.lng
    options:
      fuzzy: true
    query_defaults:
      per_page: 10
      geo:
        units: km           # km | mi | m для радиуса и расстояний, если запрос их не задаёт
  • Пакетная обработка управляется engine.max_batch_size.
  • Длина фрагмента по умолчанию — 75. Задайте её через engine.search.snippet_length или параметр CLI --highlight-length.
  • stemming описан в разделе Стемминг, а geo — в разделе Геопоиск.
  • Настройки поиска (engine.search и блок search верхнего уровня, который побеждает, если оба задают один ключ) применяются к каждому индексу. Настройки индексатора берутся из engine.indexer и могут быть переопределены для индекса через indexes.<key>.options.indexer. Переопределения поиска для отдельного индекса нет.

Примечание

В более ранних версиях перечислялись настройки индекса для индексируемых полей, фасетов, географических полей и допуска опечаток. Их никто не читал, поэтому в 2.3.0 они удалены. Значения, оставшиеся в вашей конфигурации, игнорируются, как и раньше.

Стратегии индекса

YetiSearch Pro поддерживает две стратегии индексации:

  • single (по умолчанию) — одна база для всех языков. Язык хранится в каждом документе.
  • Файл: user/data/yetisearch-pro/<prefix><index>.db (например, grav_pages.db)
  • Запрос с --index pages --lang fr, чтобы использовать обработку с учётом языка.

  • per_language — отдельный файл базы для каждого языка/версии.

  • Файл: user/data/yetisearch-pro/<prefix><index>_<lang>.db (например, grav_pages_17.db)
  • Запрос с --index pages_17 или --index pages --lang 17.

Значения запроса по умолчанию и усиление полей

Настройте поля поиска по умолчанию и веса усиления для каждого индекса:

indexes:
  pages:
    query_defaults:
      per_page: 10
      fields: [title, headers_h1, headers_h2, headers_h3, headers, content, excerpt, tags]
      boost:
        title: 5.0
        headers_h1: 4.0
        headers_h2: 3.0
        headers_h3: 2.0
        headers: 1.5
        tags: 2.5
        excerpt: 2.0
        content: 1.0
      rerank:
        title_exact: 10.0    # бонус за точное совпадение слова в заголовке
        title_prefix: 8.0    # бонус за совпадение с началом заголовка
        title_word: 5.0      # бонус за совпадение слова в заголовке
        headers_exact: 7.0   # бонус за точное совпадение слова в заголовках
        headers_prefix: 6.0  # бонус за совпадение с началом в заголовках
        headers_word: 3.0    # бонус за совпадение слова в заголовках
        chunk: 1.0           # небольшое предпочтение совпадениям во фрагментах
        fuzzy_factor: 1.2    # масштабировать бонусы переранжирования при включённом нечётком поиске
        demote_prefixes: ['/api']
        demote_penalty: 10.0

Настройки rerank позволяют переранжировать результаты на стороне клиента после оценки движком, давая точный контроль над порядком.

Режимы окружения

Плагин поддерживает конфигурацию с учётом окружения, чтобы индексы не конфликтовали:

environment:
  mode: auto          # auto | production | staging | development
  append_environment_to_index: true   # добавлять имя окружения к префиксу индекса
  realtime_in_development: true       # включить индексацию в реальном времени в режиме разработки

Когда mode равен auto, плагин следует определению окружения Grav. Параметр append_environment_to_index не даёт индексам staging и development конфликтовать с production.

Стемминг

Доступен с YetiSearch Pro 2.3.0. Необязателен и по умолчанию выключен.

Стемминг позволяет поиску находить другие формы слова: runs находит «running», configured находит «configuration», utilisateur находит «utilisateurs».

Включение

Включите его для индекса параметром Stemming (Pages) в настройках плагина или в конфигурации:

indexes:
  pages:
    stemming: true

Языки

Для английского, французского, немецкого и испанского есть стеммеры. Индекс на любом другом языке не проходит стемминг, и его слова совпадают так, как введены. Выбор языка зависит от стратегии индекса:

  • per_language — каждый индекс проходит стемминг на своём языке, поэтому pages_fr использует французский, а pages_de — немецкий. Поиск по нему тоже проходит стемминг на этом языке.
  • single — каждая страница проходит стемминг на языке, на котором была проиндексирована, поэтому французская страница использует французский. Собственный язык индекса — язык сайта по умолчанию: язык Grav по умолчанию или site.default_lang на сайте без языков. Эндпоинт /ys ищет по всем языкам и не передаёт ни одного, поэтому его поиски используют стемминг языка сайта по умолчанию, а каждая страница сохраняет стеммы своего языка.

На многоязычном сайте per_language даёт максимальную пользу от стемминга.

Ранжирование и подсветка

Совпадение со словом в том виде, как оно введено, ранжируется выше совпадения со стеммом. stem_weight (от 0 до 1, по умолчанию 0.5) задаёт, насколько учитывается совпадение со стеммом: меньшее значение оставляет точные совпадения впереди, а 0 всё ещё находит стеммы, но ставит их в конце. Задайте его в engine.search или в блоке search верхнего уровня, который побеждает, если задано и там, и там. Подсветка включает совпадения со стеммами, поэтому поиск runs отмечает «running».

Стоимость

Каждая страница проходит стемминг при индексации, поэтому индексация занимает больше времени, а индекс растёт. На демо-сайте Helios (507 документов) запись индекса заняла 0,15 с со стеммингом против 0,09 с без него, а база была примерно на треть больше. Измерения библиотеки на 32 000 документов: индексация примерно в 2,5 раза медленнее, база примерно на 50% больше, а медиана поиска — 1,7 мс против 0,9 мс.

Включение и выключение для существующего индекса

Изменение настройки один раз перестраивает полнотекстовый индекс каждого существующего индекса по хранящимся в нём документам, без повторной индексации страниц.

  • Когда это происходит. При сохранении настроек плагина в любой админке с настройками, с которыми сайт работает после сохранения (включая переопределения окружения). Иначе — при следующем запуске индексации (bin/plugin yetisearch-pro index, переиндексации из админки или планировщика), сохранении или удалении страницы.
  • Настройки другого окружения. Сохранение настроек другого окружения (например, user/env/staging/config/plugins/yetisearch-pro.yaml с production-сайта) здесь ничего не меняет. Это окружение переключится при своём следующем запуске индексации или сохранении страницы.
  • Переменные GRAV_CONFIG__. Сохранение также ничего не меняет, пока задана любая переменная окружения GRAV_CONFIG__ (например, GRAV_CONFIG__plugins__yetisearch-pro__engine__storage_dir), для каких бы настроек она ни была. Такая переменная может переопределить настройки плагина, а сохранённые файлы не содержат этого значения. Следующий запуск индексации, сохранение или удаление страницы переключат индексы с настройками, с которыми работает сайт.
  • Что вы увидите. bin/plugin yetisearch-pro index печатает строку для каждого перестроенного индекса, и это записывается в журнал Grav.
  • Сколько времени занимает. Несколько сотых секунды на индекс на демо Helios. Библиотека измерила 1,7 с на 32 000 документов.
  • С --flush. Переиндексация с --flush создаёт индексы с этой настройкой.

Доступен с YetiSearch Pro 2.1.0. Необязателен и по умолчанию выключен.

Поиск по ключевым словам находит страницы, содержащие введённые посетителем слова. Семантический поиск также находит страницы, которые означают то же самое. Кто-то вводит «can't log in» и получает вашу страницу сброса пароля. «Hire an advisor» находит вашу страницу консультаций, даже если ни одного из этих слов на ней нет. YetiSearch Pro выполняет оба поиска и объединяет результаты, поэтому страница, совпавшая и по ключевым словам, и по смыслу, оказывается первой, а страница, найденная только по смыслу, всё равно попадает в список.

Чтобы понимать смысл, YetiSearch Pro превращает каждую страницу в список чисел, называемый эмбеддингом, с помощью модели эмбеддингов. Страницы со схожим смыслом получают похожие числа. Эмбеддинги хранятся в существующем файле индекса, а при поиске запрос посетителя обрабатывается так же и сравнивается с ними.

Модель должна где-то работать. Запускать её внутри PHP непрактично на обычном хостинге, поэтому YetiSearch Pro обращается к API эмбеддингов. Это может быть размещённый сервис вроде OpenAI или OpenRouter либо ваш собственный сервер Ollama, если хотите держать всё у себя. Поиск по ключевым словам в любом случае остаётся на 100% локальным и продолжает работать, если API медленный или недоступен.

Настройка

Откройте настройки плагина, разверните Semantic Search, включите его и заполните данные подключения к вашему сервису:

Сервис Базовый URL API Модель эмбеддингов Размерность
OpenAI https://api.openai.com/v1 text-embedding-3-small 512
OpenRouter https://openrouter.ai/api/v1 openai/text-embedding-3-small 512
Ollama http://localhost:11434/v1 nomic-embed-text 0

Подойдёт и любой другой сервис с API эмбеддингов OpenAI (Voyage, Mistral, Together, LM Studio). Оставьте API Key пустым для локального сервера вроде Ollama. Если не хотите хранить ключ в конфигурации, введите env:OPENAI_API_KEY (или любое имя переменной), и YetiSearch Pro прочитает его из окружения.

Для Ollama с nomic-embed-text также задайте Query Prefix как search_query:, а Document Prefix как search_document:. Эта модель этого ожидает.

Затем проверьте подключение:

bin/plugin yetisearch-pro embed --test

Та же проверка доступна кнопкой Test connection на странице YetiSearch Pro в Admin Next.

Эмбеддинг содержимого

После включения страницы нужно один раз превратить в эмбеддинги. Либо переиндексируйте, либо выполните:

bin/plugin yetisearch-pro embed

В API отправляются только страницы, новые или изменённые с момента последнего эмбеддинга. Повторная индексация неизменённого содержимого ничего не стоит, а редактирование одной страницы отправляет только её. После этого изменённые страницы подхватываются автоматически:

  • После сохранения страницы или переиндексации — когда ответ уже ушёл в браузер, поэтому медленный API никогда не задерживает сохранение
  • Заданием планировщика Semantic Search Embedding, по умолчанию каждые 15 минут, которое добирает всё оставшееся
  • Кнопкой Embed на странице YetiSearch Pro в Admin Next, которая показывает, какая часть сайта уже обработана, и полосу выполнения во время работы

Страницы, ещё не получившие эмбеддинги, всё равно находятся поиском по ключевым словам. Просто они не ранжируются по смыслу, пока обработка не завершится.

С text-embedding-3-small от OpenAI эмбеддинг стоит пару центов за миллион слов, поэтому типичный сайт обходится значительно меньше доллара за полную обработку. Каждый новый поисковый запрос стоит одного небольшого вызова API, а повторные запросы кэшируются и ничего не стоят.

Как ведут себя поиски

  • Модальное окно поиска остаётся мгновенным. Результаты по ключевым словам появляются по мере ввода. Когда посетитель делает паузу, второй запрос ранжирует по смыслу и заменяет результаты. Ни одно нажатие клавиши не ждёт API.
  • Короткие запросы (меньше Minimum Query Length, по умолчанию 3 символа) используют только ключевые слова.
  • Бессмыслица не совпадает. Запрос вроде «asdf» возвращает ничего, а не случайную ближайшую страницу. С версии 2.1.1 YetiSearch Pro измеряет, где должен быть этот порог, для вашего содержимого: после каждого полного прохода эмбеддингов он превращает несколько бессмысленных тестовых фраз в эмбеддинги, смотрит, насколько близко они совпадают с вашими страницами, и ставит порог чуть выше этого. Страница YetiSearch Pro в Admin Next и embed --status показывают текущий порог.
  • Если API не работает или отвечает дольше Search Timeout (5 секунд), поиск возвращается к ключевым словам.
  • JSON-эндпоинт принимает semantic=0, чтобы запросить только ранжирование по ключевым словам, и сообщает "semantic": true, когда смысл участвовал в ранжировании.

Тонкая настройка

Настройка По умолчанию Что делает
Meaning vs Keywords 0.5 Какая доля ранжирования приходится на смысл. 0 — только ключевые слова, 1 — только смысл.
Minimum Similarity 0.25 Страницы с меньшим сходством никогда не появляются только по смыслу. Повысьте, если показываются не связанные страницы.
Noise Calibration Automatic Измеряет порог бессмыслицы для каждого индекса после эмбеддинга. Off использует фиксированное значение по умолчанию.
Calibration Strictness 2.0 Насколько порог выше типичной бессмыслицы. Понизьте (например, до 1.5), если хорошие совпадения теряются; повысьте, если бессмыслица проходит. Действует сразу, без повторного эмбеддинга.
Minimum Query Length 3 Более короткие запросы используют только ключевые слова.
Search Timeout 5 Сколько секунд поиск ждёт API, прежде чем вернуться к ключевым словам.
Embed After Indexing On Превращать изменённые страницы в эмбеддинги после сохранений и переиндексаций.
Documents per Run 200 Сколько документов обрабатывает каждый фоновый запуск.
Dimensions 512 Размер каждого эмбеддинга. Меньше — поиск быстрее. Значение принимают только модели text-embedding-3 от OpenAI; для остальных используйте 0.

У каждого индекса также есть собственный переключатель Semantic Search в разделе Index Definitions, поэтому его можно оставить выключенным для индекса, где он не помогает.

Примечание

Изменение модели, её размерности или префиксов означает, что каждую страницу нужно снова превратить в эмбеддинги. Пока это не сделано, поиски по этому индексу используют только ключевые слова. Re-embed all в Admin Next (или embed --reset) начинает заново с нуля.

Ограничения

Семантический поиск сравнивает запрос с каждым фрагментом содержимого, имеющим эмбеддинг. На современном сервере это занимает около 0,1 секунды для 10 000 фрагментов размерности 512 и растёт вместе с объёмом содержимого. Этого достаточно для большинства сайтов. Для очень большого сайта используйте размерность 256 или держите семантический поиск выключенным для самого большого индекса.

Индексация PDF

Примечание

Индексация PDF добавлена в YetiSearch Pro 2.2.0 и требует PHP 8.3 или выше.

Включите PDF Indexing в настройках плагина (или задайте pdf.enabled: true) и переиндексируйте. Каждый PDF в папке индексируемой страницы становится отдельным результатом поиска с заголовком из собственного названия PDF или имени файла. Каждая страница PDF индексируется отдельно, поэтому результат ведёт на file.pdf#page=12, и браузер открывает PDF на странице, где найдены слова.

pdf:
  enabled: true
  max_file_size: 50   # МБ; более крупные PDF пропускаются, 0 = без ограничения
  max_pages: 1000     # страниц индексируется на PDF, 0 = все

Требования

PHP 8.3 или новее с расширением zlib. Извлечение текста выполняется в PHP встроенной библиотекой YetiPDF, поэтому на сервере ничего устанавливать не нужно. На PHP 8.2 и старше настройка игнорируется, в журнал Grav пишется уведомление, а страницы индексируются как обычно.

Что индексируется

PDF, лежащие в папке страницы, которая сама индексируется. PDF наследует язык, таксономию и версию своей страницы, поэтому фильтры и фасеты применяются и к нему. Страница, исключённая из индекса, забирает с собой и свои PDF.

Чтобы оставить страницу в индексе, но исключить её PDF, выключите Index this page's PDFs на вкладке YetiSearch страницы или добавьте в её frontmatter:

yetisearch-pro:
  index-pdfs: false

Поддержание актуальности

Извлечённый текст кэшируется в cache/yetisearch-pro/pdf с привязкой к размеру и времени изменения каждого файла, поэтому переиндексация читает только изменённые PDF. Удалите эту папку, чтобы все PDF были прочитаны заново. При включённой индексации в реальном времени загрузка или удаление PDF в админке сразу обновляет индекс, как и сохранение страницы.

Что пропускается

Сканированные PDF без текстового слоя (OCR нет), PDF, для открытия которых нужен пароль, и PDF больше лимита размера. Каждый случай отмечается в журнале Grav. PDF, который не удалось прочитать, будет повторён при следующем запуске индексации.

В шаблонах

У результата по PDF есть doc_type: pdf в метаданных (result._meta в Twig и в JSON /ys), а также pdf_file, pdf_page, pdf_pages, page_route и page_title (страница, к которой прикреплён PDF). Его route — путь, по которому отдаётся файл, а url добавляет #page=N.

Примечание

Геопоиск добавлен в YetiSearch Pro 2.3.0.

Задайте страницам местоположение, и поиск сможет перечислять их по расстоянию, оставлять только попавшие в радиус и показывать, насколько далеко находится каждый результат. Поиски и модальные окна работают как раньше для страниц без местоположения, а страница без него никогда не появляется в поиске по местоположению.

Это работает без модуля R-Tree в SQLite. С YetiSearch 2.5.4 библиотека переходит на обычную таблицу с теми же результатами, а с 2.5.5 это верно и без математических функций SQLite. См. Without R-Tree в README библиотеки, чтобы узнать отличия.

Задание местоположения страницы

Добавьте geo во frontmatter страницы или заполните группу Location на вкладке YetiSearch редактора страницы, которая записывает те же два ключа:

geo:
  lat: 51.5074
  lng: -0.1278

Широта должна быть от -90 до 90, а долгота — от -180 до 180. Местоположение задаётся странице, только когда оба значения — числа в допустимом диапазоне. Отсутствующее, пустое или недопустимое значение никогда не превращается в 0,0: страница индексируется без местоположения. PDF в папке страницы находятся по местоположению своей страницы, как и фрагменты страницы. Изменение местоположения перезаписывает документ при следующей индексации, а удаление убирает страницу из поиска по местоположению. В Admin Next числовое поле нельзя очистить, если в нём есть значение, поэтому удалите местоположение из frontmatter страницы (режим Expert).

Собственные пути

Если ваши страницы уже хранят координаты под другими ключами, укажите на них индекс через точечные пути в заголовке страницы:

indexes:
  pages:
    geo:
      lat: venue.latitude
      lng: venue.longitude

Те же настройки есть в конфигурации плагина как Page Latitude Field и Page Longitude Field. Задайте geo: false, чтобы не использовать frontmatter страницы. Поле Location в редакторе страницы всегда записывает geo.lat и geo.lng, поэтому оно подходит только для путей по умолчанию. После изменения путей переиндексируйте.

Местоположения из другого источника, например из базы данных или значения в другом формате, задаются слушателем события (см. Задание местоположения из слушателя события).

Поиск

Эндпоинт query_route (по умолчанию /ys), функция Twig yetisearch() и options события onYetisearchProBeforeSearch принимают следующее:

Параметр Что делает
lat, lng Точка, вокруг которой искать. Нужны оба. Значения, не являющиеся допустимыми координатами, игнорируются, и поиск выполняется как обычный.
radius Необязательно. С ним возвращаются только документы в пределах этого расстояния. Без него возвращается каждый документ, у которого есть местоположение. Значение, не являющееся положительным числом, игнорируется.
units km, mi или m — для радиуса и расстояний. По умолчанию берётся из query_defaults.geo.units индекса, затем km.
sort distance перечисляет ближайшие первыми, в том числе с запросом. Поиск с запросом без него сохраняет порядок релевантности.

С местоположением и пустым q поиск перечисляет ближайшие документы, как если бы был задан sort=distance. Пустой q без допустимого местоположения всё равно возвращает пустой результат. Это правило применяется после события onYetisearchProBeforeSearch, поэтому слушатель может задать geo в options события для пустого запроса, а слушатель, который его удаляет, завершает поиск пустым результатом. То же в CLI: query с --lat и --lng без текста перечисляет ближайшие первыми.

/ys?lat=51.5074&lng=-0.1278&radius=5&units=km&sort=distance&ajax=1
/ys?q=coffee&lat=51.5074&lng=-0.1278&radius=25&ajax=1

У каждого результата поиска по местоположению есть distance (в метрах) и distance_text — расстояние в единицах поиска, например 1.3 km или 0.8 mi. Строки берутся из PLUGIN_YETISEARCH_PRO.DISTANCE_KM, DISTANCE_MI и DISTANCE_M, поэтому их можно переводить. В конверте результата есть запись geo с местоположением, которое использовал поиск (lat, lng, radius, units, sort), чтобы страница могла понять, что результаты содержат расстояния.

В Twig:

{% set results = yetisearch('coffee', {'lat': 51.5074, 'lng': -0.1278, 'radius': 5, 'units': 'mi', 'sort': 'distance'}) %}
{% for hit in results.results %}
    {{ hit.title }}, {{ hit.distance_text }}
{% endfor %}

Простое модальное окно показывает distance_text для каждого результата, когда его запросы содержат местоположение. Передайте его через data-ys-extra-params (см. Extra Query Parameters в разделе Настройка шаблонов модальных окон), например data-ys-extra-params="lat=51.5074&lng=-0.1278&units=km".

Near Me

Задайте modal.near_me: true, чтобы добавить кнопку Near me в полное модальное окно. По умолчанию она выключена. Когда посетитель её включает, браузер спрашивает его местоположение. Затем модальное окно добавляет lat, lng, radius, units и sort=distance к своим запросам, перечисляет ближайшие результаты даже при пустом поле поиска и предлагает меню радиуса. Если в местоположении отказано или его не удаётся определить, модальное окно сообщает об этом.

modal:
  type: full
  near_me: true
  near_me_radii: [1, 5, 10, 25, 50]   # варианты радиуса в единицах query_defaults.geo.units индекса
  near_me_radius: 10                  # радиус, выбранный первым; 0 = любое расстояние. Добавляется в меню, если отсутствует в near_me_radii

Кнопка находится в собственном блоке near_me шаблона полного модального окна, поэтому тема может её переместить или заменить. Собственный filters.html.twig темы не затрагивается.

Конфиденциальность

Координаты, которыми делится посетитель, остаются в браузере, пока открыта страница. Они отправляются только как параметры поискового запроса, никогда не сохраняются в браузере, и плагин их не хранит: аналитика записывает текст поиска, как и раньше, плюс то, использовалось ли местоположение, и никогда сами координаты. Поиски с пустым q вообще не записываются.

Поиск по местоположению в CLI

bin/plugin yetisearch-pro query "coffee" --lat=51.5074 --lng=-0.1278 --radius=5 --units=km --sort=distance
bin/plugin yetisearch-pro query --lat=51.5074 --lng=-0.1278 --radius=25

Нужны и --lat, и --lng; отрицательное число пишите как --lng=-0.1278, со знаком равенства. --sort distance требует местоположения. Запрос можно не указывать, когда есть местоположение. В таблице результатов появляется столбец расстояния. Все параметры см. в разделе Команда Query.

Задание местоположения из слушателя события

onYetisearchBuildDocument может задать местоположение документа из любого источника. Присвойте документ обратно событию: изменение $event['doc']['geo'] на месте не доходит до индексатора, поэтому слушатель должен прочитать $event['doc'], изменить его копию и присвоить копию обратно в $event['doc']. Этот пример, проверенный с bin/plugin yetisearch-pro index и с сохранением страницы в админке, читает ключ заголовка location: "48.8584, 2.2945":

use Grav\Common\Plugin;
use RocketTheme\Toolbox\Event\Event;

class MyPlugin extends Plugin
{
    public static function getSubscribedEvents(): array
    {
        return ['onYetisearchBuildDocument' => ['onYetisearchBuildDocument', 0]];
    }

    public function onYetisearchBuildDocument(Event $event): void
    {
        $location = (string)($event['subject']->header()->location ?? '');
        if (strpos($location, ',') === false) {
            return;
        }
        [$lat, $lng] = array_map('trim', explode(',', $location, 2));

        $doc = $event['doc'];
        $doc['geo'] = ['lat' => $lat, 'lng' => $lng];
        $event['doc'] = $doc;
    }
}

Индексатор проверяет местоположение и превращает два значения в числа; значение, не являющееся допустимой координатой, оставляет документ без местоположения. Документ также может нести geo_bounds (north, south, east, west) вместо точки. Событие срабатывает и для PDF страницы, с самой страницей в subject, поэтому тот же код задаёт местоположение и им.

YetiSearch Pro включает два дизайна модального окна, которые могут использовать темы.

  • full — двухпанельное модальное окно с результатами слева и живым предпросмотром справа. Включает пагинацию, элементы фильтрации, хлебные крошки и разделы «On This Page». Лучше всего подходит для сайтов документации с длинным содержимым.

  • simple (по умолчанию) — лёгкое модальное окно автодополнения со сгруппированными результатами. Одна панель, без предпросмотра. Лучше всего подходит для обычных сайтов, которым нужен чистый и быстрый поиск.

Конфигурация

modal:
  type: simple    # 'full' или 'simple'
  branding: true  # показывать «Powered by YetiSearch» в подвале
  near_me: false  # только полное модальное окно (2.3.0+): добавить кнопку «Near me»
  near_me_radii: [1, 5, 10, 25, 50]   # варианты радиуса в единицах query_defaults.geo.units индекса
  near_me_radius: 10                  # радиус, выбранный первым; 0 = любое расстояние

near_me добавляет кнопку, которая ищет вокруг местоположения посетителя. По умолчанию она выключена. near_me_radii перечисляет варианты радиуса, а near_me_radius — выбранный первым; он добавляется в меню, если его нет в списке. Как это работает, см. в разделе Near Me.

Интеграция с темой

Добавление модального окна поиска

Включите модальное окно в базовый шаблон темы:

{# В partials/base.html.twig вашей темы или похожем файле #}
{% if config.plugins['yetisearch-pro'].enabled %}
    {% include 'partials/yetisearch-pro/modal.html.twig' %}
{% endif %}

Модальное окно автоматически выбирает правильный тип по вашей конфигурации.

Модальные окна отправляют запросы на query_route (по умолчанию /ys) с базовым URL сайта впереди, поэтому достаточно изменить query_route. Темы, которые строят собственное модальное окно, должны сами читать query_route.

Чтобы включить конкретное модальное окно напрямую:

{% include 'partials/yetisearch-pro/modal-simple.html.twig' %}
{# или #}
{% include 'partials/yetisearch-pro/modal-full.html.twig' %}

Кнопка запуска поиска

Включите готовую кнопку поиска:

{% include 'partials/yetisearch-pro/search-trigger.html.twig' %}

{# С пользовательскими параметрами #}
{% include 'partials/yetisearch-pro/search-trigger.html.twig' with {
    placeholder: 'Search docs...',
    show_shortcut: true,
    class: 'my-custom-class'
} %}

Программное открытие модального окна

Добавьте data-ys-open любому элементу, чтобы он открывал модальное окно поиска:

<button data-ys-open>Search</button>
<input type="text" data-ys-open placeholder="Search...">

Горячие клавиши: Cmd/Ctrl+K или / (когда фокус не в текстовом поле).

Настройка через CSS-переменные

Оба модальных окна используют CSS-переменные для простого оформления:

/* Переменные простого модального окна */
:root {
  --ys-simple-dialog-bg: #ffffff;
  --ys-simple-text: #111827;
  --ys-simple-accent: #3b82f6;
  --ys-simple-highlight-bg: #fef08a;
}

/* Переменные полноэкранного модального окна */
:root {
  --ys-panel-bg: #181a1f;
  --ys-text: #d3d7df;
  --ys-accent: #4ea1ff;
}

Переменные Twig

Плагин передаёт тип модального окна в Twig:

{{ yetisearch_modal_type }} {# 'full' или 'simple' #}

Встроенная страница поиска

Примечание

Встроенная страница поиска работает с YetiSearch Pro 2.3.0. В более ранних версиях её шаблон вызывал функции Twig, которые никогда не регистрировались, поэтому страница не отрисовывалась.

Задайте built_in_search_page: true, чтобы отдавать готовую страницу результатов по адресу search_route (по умолчанию /search). Она использует partials/base.html.twig темы. Если на сайте уже есть страница по этому маршруту, даже неопубликованная, используется она, а встроенная не добавляется.

built_in_search_page: true
search_route: /search

Страница строится из двух функций Twig, которые может использовать любой шаблон:

  • yetisearch(query, options) выполняет тот же поиск, что и эндпоинт query_route, включая обработку языков, событие onYetisearchProBeforeSearch и аналитику, и возвращает тот же конверт результата: results, total, page, pages, limit, time и time_ms (в миллисекундах), suggestion и semantic. В options могут быть page, per_page, type (content, api или both), filter (см. DSL фильтров), version, fuzzy, semantic, unique_by_route, max_results, а также lat, lng, radius, units и sort для поиска по местоположению (см. Геопоиск).
  • yetisearch_form(options) возвращает форму поиска, которая отправляет q на страницу поиска. В options могут быть placeholder, button_text, action, value и class.

У каждого результата есть title, url, route, excerpt, tags (строка через запятую) и _highlights, а в _meta — date, anchor и heading. В поиске по местоположению у него также есть distance (в метрах) и distance_text.

Подсветка содержит теги <mark> вокруг совпадений, и ничего в ней не экранируется: заголовки хранятся так, как введены, а текст страницы сохраняет сущности вроде &lt;. Прежде чем выводить её как HTML, превратите каждый <, который не начинает <mark> или </mark>, в &lt;, как это делает templates/search.html.twig через {{ text|regex_replace('~<(?!/?mark>)~i', '&lt;')|raw }}, или выводите только обычные поля. Не экранируйте весь текст снова, иначе примеры кода на ваших страницах покажутся как &lt;img вместо <img.

Встроенная страница превращается в поиск по местоположению, когда в её URL есть lat и lng, например /search?q=coffee&lat=51.5074&lng=-0.1278&radius=5&units=km&sort=distance. Тогда каждый результат показывает расстояние, а пустой q перечисляет ближайшие страницы. Местоположение остаётся в ссылках страницы, поэтому пагинация его сохраняет.

Настройка шаблонов модальных окон

Оба модальных окна используют шаблон базовый шаблон + extends, который позволяет темам переопределять отдельные части, не переписывая всё модальное окно.

Наследование шаблонов

Plugin:  partials/yetisearch-pro/base/modal-simple.html.twig   ← canonical ({% block %} tags)
Plugin:  partials/yetisearch-pro/modal-simple.html.twig         ← thin {% extends %} wrapper
Theme:   partials/yetisearch-pro/modal-simple.html.twig         ← theme override (extends base)

Файл темы затеняет обёртку плагина через разрешение шаблонов Grav. Путь в {% extends %} указывает на base/..., который есть только в плагине, поэтому циклического поиска нет.

Тот же шаблон применяется к modal-full.html.twig.

Доступные блоки — простое модальное окно

Блок Описание
modal Весь контейнер модального окна
modal_data_attrs Пустой слот для дополнительных атрибутов data-* на корневом <div>
backdrop Затемняющая подложка
search_input Обёртка поля ввода (значок + поле + бейдж ESC)
results Весь раздел результатов
results_header Заголовок с числом результатов
loading Индикатор загрузки
empty Сообщение о пустом состоянии
type_more Подсказка «Type more characters»
results_list Контейнер сгруппированных результатов
footer Раздел подвала
keyboard_hints Подсказки о горячих клавишах
branding Ссылка «Powered by YetiSearch»

Доступные блоки — полное модальное окно

Блок Описание
config_script Встроенный <script> для определения эндпоинта и темы
modal Весь контейнер модального окна
modal_data_attrs Пустой слот для дополнительных атрибутов data-* на корневом <div>
backdrop Затемняющая подложка
search_input Строка ввода (значок + поле + фильтры + кнопка отмены)
near_me Строка Near me под полем ввода (пуста, если modal.near_me выключен)
body Контейнер тела (левая и правая панели)
results_panel Левая панель (метаданные + список + пагинация)
preview_panel Правая панель (хлебные крошки + заголовок + фрагмент)
item_template Элемент <template> для клонирования результата

Пример: переопределение темы

Минимальное переопределение темы, добавляющее собственный атрибут данных:

{# themes/my-theme/templates/partials/yetisearch-pro/modal-full.html.twig #}
{% extends 'partials/yetisearch-pro/base/modal-full.html.twig' %}

{% block modal_data_attrs %}
data-ys-extra-params="filter[version][eqor]={{ some_version|url_encode }}"
{% endblock %}

Это передаёт дополнительные параметры запроса эндпоинту поиска, не затрагивая другие части модального окна.

Дополнительные параметры запроса (data-ys-extra-params)

Добавьте атрибут data-ys-extra-params корневому элементу модального окна, чтобы добавлять произвольные параметры к каждому поисковому запросу. Значение добавляется как есть к URL запроса:

<!-- Rendered output -->
<div id="ys-modal" data-ys-extra-params="filter[version][eqor]=17">

JS плагина читает этот атрибут при инициализации и добавляет его к каждому URL поиска:

/ys?q=twig&page=1&type=content&ajax=1&filter[version][eqor]=17

Блок modal_data_attrs — рекомендуемый способ задать это из Twig.

Пример: замена панели предпросмотра

{% extends 'partials/yetisearch-pro/base/modal-full.html.twig' %}

{% block preview_panel %}
<div class="ys-right custom-preview">
    {# Ваш собственный макет предпросмотра #}
</div>
{% endblock %}

Пример: добавление содержимого после результатов

{% extends 'partials/yetisearch-pro/base/modal-simple.html.twig' %}

{% block footer %}
    <div class="my-custom-footer">
        Custom footer content
    </div>
    {{ parent() }}
{% endblock %}

Используйте {{ parent() }}, чтобы включить исходное содержимое блока вместе с вашими дополнениями.

Управление на уровне страницы

Параметры frontmatter

Управляйте индексацией каждой страницы через frontmatter или те же настройки на вкладке YetiSearch редактора страницы:

yetisearch-pro:
  index-page: false      # исключить эту страницу из индекса
  ignore: true           # то же, что index-page: false
  index-children: false  # исключить дочерние страницы из индекса
  index-pdfs: false      # оставить страницу, но исключить её PDF (2.2.0+)
  fields:                # дополнительные поля frontmatter для индексации вместе со страницей
    - seo.description

Примечание

До 2.2.0 читался только старый ключ yetisearch:, поэтому настройки на вкладке YetiSearch редактора страницы (которые сохраняются под yetisearch-pro:) не действовали. С 2.2.0 читаются оба ключа. Использовать нужно yetisearch-pro:, и он побеждает, если страница задаёт один параметр под обоими ключами.

Шорткод игнорирования

Исключите определённые блоки содержимого из индексации, оставив их видимыми на странице:

[yetisearch=ignore]
This content will be visible but not indexed for search.
[/yetisearch]

Поля документов по умолчанию

Каждый индексируемый документ содержит:

  • Поля содержимого: title, content, excerpt, url, route, tags и category, полученные из таксономии
  • Метаданные: taxonomy, meta, breadcrumbs, date, updated_at
  • Язык: language (задаётся для каждой страницы при стратегии единого индекса)
  • Местоположение: geo с lat и lng, только для страниц с местоположением (2.3.0+)
  • Стабильный ID: <lang>:<route>

Панель управления

YetiSearch Pro включает полноценную панель управления, доступную из бокового меню плагина админки.

Панель индекса

Следите за состоянием индекса и запускайте обслуживание:

  • Просмотр общего числа документов, размеров индексов и времени последнего обновления
  • Переиндексация всего содержимого с отслеживанием хода
  • Очистка и сброс индексов

Аналитика поиска

Отслеживайте вовлечённость в поиск и производительность:

  • Общее число и число уникальных запросов
  • Переходы по результатам и поиски без результатов
  • Среднее время ответа
  • Популярные поиски и самые часто открываемые результаты
  • Частые запросы без результатов (чтобы находить пробелы в содержимом)

Браузер индекса

Просматривайте и управляйте индексируемыми документами:

  • Просмотр всех документов с пагинацией
  • Поиск внутри индекса
  • Просмотр отдельного документа с полным JSON
  • Прямое редактирование данных документа
  • Отладка статистики индекса и частых терминов
  • Оптимизация индексов

Права

YetiSearch Pro регистрирует детальные права администратора:

  • yetisearch-pro.view — просмотр панели поиска и статистики
  • yetisearch-pro.modify — запуск переиндексации и управление индексами
  • yetisearch-pro.admin — полный доступ, включая изменение конфигурации

Команды CLI

Все команды выполняются из корня сайта Grav.

Команда Index

bin/plugin yetisearch-pro index [options]

Options:
  -x, --index=INDEX       Index key (e.g., pages) [default: pages]
  -l, --lang=LANG         Language/Version (e.g., 17)
  -f, --flush             Drop and recreate the target index before indexing
  -q, --quiet             Minimal output
  -r, --raw               Raw JSON status
      --no-embed          Skip embedding for semantic search afterwards

Примеры:

# полная перестройка для одного языка
bin/plugin yetisearch-pro index --index pages --lang 17 --flush

# полная перестройка для всех языков (per_language)
bin/plugin yetisearch-pro index --index pages --flush

# индексация конкретного индекса с суффиксом
bin/plugin yetisearch-pro index --index pages_17 --flush

Когда индекс даёт сбой или запуск не смог удалить устаревший документ (тот, чья страница исчезла), команда называет индекс и причину и завершается со статусом 1, в том числе с --raw, чей JSON тогда содержит "success": false. Устаревший документ, который не удалось удалить, остаётся записанным, поэтому следующий запуск его удалит. Переиндексация в любой админке, API и планировщик также сообщают о таком сбое (2.3.0+).

Команда Query

bin/plugin yetisearch-pro query [QUERY] [options]

Core:
  -x,  --index=INDEX             Index key or suffixed key
  -l,  --lang=LANG               Language/Version
  -p,  --page=PAGE               Page number (default: 1)
       --per-page=N              Results per page (default: 10)
       --compact                 Compact output
  -r,  --raw                     Raw JSON output

Location (2.3.0+, see Geo Search):
       --lat=LAT                 Latitude of the search location (needs --lng)
       --lng=LNG                 Longitude of the search location (needs --lat)
       --radius=R                Only documents within R of the location, in --units
       --units=km|mi|m           Units for the radius and distances (default: the index's setting, else km)
  -s,  --sort=distance           Nearest first (needs a location)

Search behavior:
       --fuzzy                   Enable fuzzy search
       --fuzziness=F             Fuzziness score (0..1, default: 0.8)
       --fields=LIST             Comma-separated fields to search
  -s,  --sort=SPEC               Sort spec, e.g. "title:asc,updated_at:desc"
  -F,  --filter=EXPR             Repeatable filter: field<op>value, e.g. type=content or price>=10 (op: =,!=,>,>=,<,<=)
       --no-highlight            Disable highlight markup
       --highlight-length=N      Snippet window length (default: 75)
  -d,  --distinct[=BOOL]         Distinct per route (default: true)
       --suggestions             Show suggestions after results
       --suggestions-limit=N     Suggestion count (default: 10)

Текст запроса необязателен, когда есть местоположение: query с --lat и --lng без текста перечисляет ближайшие документы первыми. Нужны и --lat, и --lng, а отрицательное число пишется со знаком равенства, как в --lng=-0.1278. --sort distance требует местоположения. С местоположением в таблице результатов появляется столбец расстояния.

Фильтр пишется без скобок: type=content, price>=10, taxonomy.category=plugins. Поле состоит из букв, цифр, _ и . и начинается с буквы или _. С 2.3.0 фильтр, который команда не может прочитать, например type[=]content, печатает предупреждение с его именем в stderr (чтобы JSON --raw в stdout оставался допустимым) и исключается из поиска.

Примеры:

# нечёткий поиск в индексе для отдельного языка
bin/plugin yetisearch-pro query "install theme" --index pages_17 --fuzzy

# поиск с языком при стратегии единого индекса
bin/plugin yetisearch-pro query "installer" --index pages --lang fr

# с фильтрами и сортировкой
bin/plugin yetisearch-pro query "config" -x pages_17 -F "taxonomy.category=plugins" -s "title:asc"

# ближайшие места в пределах 5 км, без текста запроса
bin/plugin yetisearch-pro query --lat=51.5074 --lng=-0.1278 --radius=5 --sort=distance

Команда Schedule

bin/plugin yetisearch-pro schedule [options]

Options:
  -l, --list              Display configured schedule summary
  -t, --task=TASK         Task to run: reindex, maintenance, embed, or all (default: all)
  -f, --force             Run even if the task is disabled

Команда Embed

Превращает новые и изменённые документы в эмбеддинги для семантического поиска. Требует включённого семантического поиска.

bin/plugin yetisearch-pro embed [options]

Options:
  -x, --index=INDEX       Index key (e.g., pages); all semantic indexes by default
  -l, --limit=LIMIT       Documents per API round [default: 100]
  -s, --status            Show embedding coverage and exit
  -t, --test              Check the provider settings with one short request
      --reset             Forget stored embeddings and embed everything again

--status также показывает порог бессмыслицы каждого индекса и то, измерен ли он на вашем содержимом или является значением по умолчанию. После обновления до 2.1.1 запустите embed один раз: он измеряет порог, даже если каждая страница уже имеет эмбеддинг.

Команда Cache

bin/plugin yetisearch-pro cache [ACTION] [options]

Actions:
  stats                   Display cache statistics (default)
  monitor                 Monitor cache activity in real-time
  purge                   Clear all cache entries
  warm                    Pre-populate cache with common queries

Options:
  -i, --index=INDEX       Index name (auto-detected if omitted)
  -d, --details           Show detailed cache entries

Эндпоинт запросов и фильтры

JSON-эндпоинт запросов (query_route, по умолчанию /ys) обрабатывается во время onPluginsInitialized, когда запрос явно просит JSON (Accept: application/json, ?ajax=1 или .json). Это сокращает инициализацию страницы для быстрого автодополнения и ответов API.

DSL фильтров

Передавайте фильтры параметром filter в одной из двух форм.

Форма массива, по одному filter[field][operator]=value на условие. Оператор — имя: eq, eqor (равно или пусто), neq или ne, gt, gte, lt, lte, in, nin, like, contains или exists. filter[field]=value сам по себе означает равно:

/ys?q=search+terms&filter[version][eqor]=v3&filter[category][eq]=plugins

Строковая форма, field:operator:value, несколько условий соединяются через AND (пробел с каждой стороны, в URL кодируется как %20 или +). Оператор — один из =, =?, !=, >, >=, <, <=, in, like, contains или exists. Для in разделяйте значения запятыми:

/ys?q=search+terms&filter=version:=?:v3
/ys?q=search+terms&filter=version:=?:v3%20AND%20category:=:plugins
/ys?q=search+terms&filter=version:in:v2,v3

=? совпадает со значением или пустым полем, что подходит страницам, действующим для всех версий. Условие, которое парсер не может прочитать, например version[=]v3, пропускается без ошибки, и поиск выполняется без фильтра. Передача filter дважды (filter=a&filter=b) оставляет только последний, поэтому для нескольких условий используйте AND или форму массива. Старый параметр version=v3 всё ещё принимается и ведёт себя как filter=version:=?:v3.

Флаги включения и выключения в запросе (unique_by_route, semantic, fuzzy) принимают 1, true, yes и on либо 0, false, no и off. unique_by_route=0 возвращает каждый подходящий документ вместо одного на маршрут.

Поиск по местоположению добавляет в запрос lat, lng, radius, units и sort=distance. Что делает каждый из них, см. в разделе Геопоиск.

При включённом семантическом поиске добавьте semantic=0 к запросу, чтобы получить только ранжирование по ключевым словам. Ответ содержит "semantic": true, когда смысл участвовал в ранжировании.

Эндпоинт подсказок

Лёгкий эндпоинт подсказок доступен по адресу <query_route>/suggest (например, /ys/suggest):

  • Параметры: term или q (вводимая строка), limit (по умолчанию 10)
  • Возвращает JSON-массив { text, score, count } с минимальными накладными расходами Grav

События (расширяемость)

Эти события Grav позволяют настраивать индексацию и изменять документ перед индексацией:

  • onYetisearchCollectObjects(index, lang, &documents) — добавить или изменить документы, не являющиеся страницами, для индексации
  • onYetisearchBuildDocument(subject, lang, index, &doc) — изменить документ перед индексацией (например, добавить собственные поля frontmatter или местоположение). С 2.2.0 событие также срабатывает для каждого PDF-документа, с владеющей страницей в subject; проверяйте doc['doc_type'] === 'pdf', чтобы их различить. Присваивайте изменённый документ обратно через $event['doc'] = $doc; изменение $event['doc']['key'] на месте не сохраняется. Другой пример см. в разделе Геопоиск.
  • onYetisearchProBeforeSearch(query, lang, &filters, &options) — добавить собственные фильтры или изменить параметры поиска перед выполнением запроса. В поиске по местоположению проверенное местоположение находится в options['geo'] (lat, lng, radius, units, sort)
  • onYetisearchPageSkip(page, config) — решить, пропускать ли страницу при индексации. config — определение строящегося индекса. Задайте skip как true в событии, чтобы исключить страницу

Событие передаёт слушателю значения копией. Прочитайте значение из события, измените его и присвойте обратно ($event['options'] = $options). Изменение на месте, например $event['options']['geo'] = ... или $doc = &$event['doc'], теряется.

Пример: добавление собственных полей

public function onYetisearchBuildDocument(Event $event)
{
    $page = $event['subject'];
    $doc = $event['doc'];

    // добавить собственное поле из заголовков страницы
    $doc['author'] = $page->header()->author ?? '';

    // присвоить изменённый документ обратно, иначе изменение не сохранится
    $event['doc'] = $doc;
}

Пример: индексация FlexObjects

public function onYetisearchCollectObjects(Event $event)
{
    $documents = $event['documents'];

    // добавить FlexObjects или другой собственный контент
    $flex = $this->grav['flex'];
    $collection = $flex->getCollection('my-objects');

    foreach ($collection as $object) {
        $documents[] = [
            'id' => 'flex:' . $object->getKey(),
            'title' => $object->getProperty('title'),
            'content' => $object->getProperty('description'),
            'url' => $object->url(),
        ];
    }

    $event['documents'] = $documents;
}

Интеграция с темой Helios

YetiSearch Pro и тема документации Helios созданы для совместной работы. Helios включает встроенную поддержку YetiSearch Pro с предварительно настроенной комбинацией Cmd+K / Ctrl+K.

Чтобы включить YetiSearch Pro в Helios, задайте провайдера поиска в конфигурации Helios:

# user/config/themes/helios.yaml
search:
  provider: yetisearch

Советы и лучшие практики

Индексация

  • Запускайте --flush для первой индексации или после крупной перестройки содержимого
  • Используйте планировщик Grav для автоматической переиндексации на production-сайтах
  • Включайте обновления в реальном времени для сайтов, где содержимое часто меняется в админке

Настройка поиска

  • Настраивайте веса boost, чтобы отдавать приоритет совпадениям в заголовках и заголовках разделов перед текстом страницы
  • Используйте rerank.demote_prefixes, чтобы опустить менее релевантные разделы (например, справочники API) ниже в результатах
  • Включайте нечёткий поиск (options.fuzzy, по умолчанию включён), чтобы находить результаты, несмотря на опечатки. Если он пропускает слишком много ложных совпадений, повысьте search.trigram_threshold (0.45 в конфигурации плагина по умолчанию; больше — строже) или задайте options.fuzzy: false для индекса. Запрос может отключить его через fuzzy=0
  • При включённом стемминге понижайте stem_weight, чтобы точные совпадения оставались впереди совпадений с другими формами слова
  • При включённом семантическом поиске повышайте Minimum Similarity, если показываются слабо связанные страницы, или понижайте Meaning vs Keywords, если точные совпадения по ключевым словам должны побеждать чаще

Производительность

  • Стратегия индекса single проще и хорошо работает для большинства сайтов
  • Используйте стратегию per_language для больших многоязычных сайтов, где важен размер индекса
  • Настраивайте max_batch_size с учётом ограничений памяти сервера
  • Используйте команду прогрева кэша, чтобы заранее заполнить частые поисковые запросы

Устранение неполадок

General error: 1 near "RETURNING": syntax error

Эта ошибка означает, что ваша версия SQLite старше 3.35.0. Предложение RETURNING появилось в SQLite 3.35.0 (март 2021). Обновитесь до YetiSearch Pro 1.1.0+ (где используется библиотека YetiSearch 2.3.1+) — эта версия автоматически переходит на совместимый метод запроса на старых версиях SQLite. Минимальная поддерживаемая версия SQLite теперь 3.24.0.

Если обновить плагин нельзя, придётся обновить SQLite:

  • Ubuntu/Debian: sudo apt update && sudo apt install --only-upgrade libsqlite3-0 (Ubuntu 22.04+ поставляет 3.37+)
  • CentOS/RHEL: рассмотрите репозиторий Remi для более новых сборок PHP со свежим SQLite
  • Общий хостинг: попросите провайдера обновить SQLite или убедитесь, что PHP скомпилирован с SQLite 3.24.0+

no such module: fts5

SQLite в вашем PHP не скомпилирован с поддержкой FTS5. Решения:

  • Ubuntu/Debian: установите php-sqlite3 из стандартных репозиториев — большинство современных пакетов включают FTS5
  • macOS: используйте PHP из Homebrew (brew install php), который включает FTS5 по умолчанию
  • Сборка из исходников: убедитесь, что при сборке SQLite задан -DSQLITE_ENABLE_FTS5

Как проверить версию SQLite?

Выполните из командной строки:

$ php -r "echo \SQLite3::version()['versionString'];"

Или используйте встроенный диагностический скрипт:

$ php user/plugins/yetisearch-pro/vendor/yetidevworks/yetisearch/scripts/check_sqlite_features.php

Он сообщает вашу версию SQLite и доступность модулей FTS5 и R-tree.

Обновление до 2.3.0: полнотекстовый индекс перестраивается один раз

В этом выпуске встроенная библиотека YetiSearch обновлена до 2.6.1. В версиях библиотеки 2.5.x удаление страницы с фрагментами или PDF либо сохранение страницы, исключённой из поиска (например, неопубликованной), добавляло имена полей хранимых документов (title, route, content, url и так далее) в индекс как слова. После этого поиск по title или route возвращал каждый документ, пока следующее сохранение страницы не перестраивало индекс.

YetiSearch Pro один раз перестраивает полнотекстовый индекс каждого существующего индекса после обновления, прежде чем что-либо в него записать или удалить из него. Это происходит при первом запуске индексации, сохранении страницы, удалении страницы, изменении PDF или сохранении настроек и записывается в индекс, чтобы больше никогда не выполняться. Это важно, потому что с библиотекой 2.6.0 удаление из индекса, где всё ещё есть такие слова, ломает все последующие поиски по одному из них (2.6.1 также сама перестраивает такой индекс при первой записи). На демо-сайтах Helios это заняло около сотой секунды на индекс, и вручную переиндексировать ничего не нужно.

Если перестройка не удалась, журнал Grav сообщает об этом, и в этот индекс ничего не записывается, пока его не перестроит более поздний запрос.