# SiteSearch Plugin for OctoberCMS
This plugin adds global search capabilities to OctoberCMS.
## Available languages
* English
* German
* Czech
* Russian
* Persian (Farsi)
* Portuguese
You can translate all contents into your own language.
## Currently supported content types
* [RainLab.Pages](https://octobercms.com/plugin/rainlab-pages)
* [RainLab.Blog](https://octobercms.com/plugin/rainlab-blog)
* [Indikator.News](https://github.com/gergo85/oc-news)
* [Feegleweb.Octoshop](https://octobercms.com/plugin/feegleweb-octoshop)
* [Jiri.JKShop](http://octobercms.com/plugin/jiri-jkshop)
* [RadiantWeb.ProBlog](https://octobercms.com/plugin/radiantweb-problog)
* [Arrizalamin.Portfolio](https://octobercms.com/plugin/arrizalamin-portfolio)
* [Responsiv.Showcase](https://octobercms.com/plugin/responsiv-showcase)
* [VojtaSvoboda.Brands](https://octobercms.com/plugin/vojtasvoboda-brands)
* [Graker.PhotoAlbums](https://octobercms.com/plugin/graker-photoalbums)
* Native CMS pages (experimental)
**Multilingual contents via RainLab.Translate are supported.**
Support for more plugins is added upon request.
**You can easily extend this plugin to search your custom plugin's contents as well.
See the documentation for further information.**
### Get native support for your plugin
If you are a plugin developer and wish to have native support for your contents in SiteSearch please submit a pull
request for your search provider or send us a copy of you plugin so we can create the provider for you.
We cannot add support for every plugin but will add any plugin that has a notable project count on the October
Marketplace.
## Components
### searchResults
Place this component on your page to display search results.
#### Usage example
Create a search form that sends a query to your search page:
##### Search form
```html
```
**Important**: Use the `q` parameter to send the user's query.
Alternatively you can also use the `searchInput` component described below to generate this form
for you.
##### Search results
Create a page to display your search results. Add the `searchResults` component to it.
Use the `searchResults.query` parameter to display the user's search query.
```html
title = "Search results"
url = "/search"
layout = "default"
[searchResults]
resultsPerPage = 10
showProviderBadge = 1
noResultsMessage = "Your search did not return any results."
visitPageMessage = "Visit page"
==
Search results for {{ searchResults.query }}
{% component 'searchResults' %}
```
##### Example css to style the component
```css
.ss-result {
margin-bottom: 2em;
}
.ss-result__aside {
float: right;
margin-left: .5em;
}
.ss-result__title {
font-weight: bold;
margin-bottom: .5em;
}
.ss-result__badge {
font-size: .7em;
padding: .2em .5em;
border-radius: 4px;
margin-left: .75em;
background: #eee;
display: inline-block;
}
.ss-result__text {
margin-bottom: .5em;
}
.ss-result__url {
}
```
#### Modify the query before searching
If you want to modify the user's search query before the search is executed you can call the `forceQuery` method on the `searchResults` component from your page's `onStart` method.
```php
[searchResults]
resultsPerPage = 10
showProviderBadge = 1
noResultsMessage = "Your search returned no results."
visitPageMessage = "Visit page"
==
function onStart()
{
$query = Request::get('q');
$query = str_replace('ё', 'e', $query);
$this->page->components['searchResults']->forceQuery($query);
}
==
{% component 'searchResults' %}
```
#### Change the results collection before displaying
You can listen for the `offline.sitesearch.results` event and modify the query as you wish.
This is useful to remove certain results or change the sort order.
```php
[searchResults]
resultsPerPage = 10
showProviderBadge = 1
noResultsMessage = "Your search returned no results."
visitPageMessage = "Visit page"
==
function onInit()
{
\Event::listen('offline.sitesearch.results', function ($results) {
// return $results->filter(...);
return $results->sortByDesc('model.custom_attribute');
});
}
==
{% component 'searchResults' %}
```
#### Properties
The following properties are available to change the component's behaviour.
##### resultsPerPage
How many results to display on one page.
##### showProviderBadge
The search works by querying multiple providers (Pages, Blog, or other). If this option is enabled
each search result is marked with a badge to show which provider returned the result.
This is useful if your site has many different entities (ex. teams, employees, pages, blog entries).
##### noResultsMessage
This message is shown if there are no results returned.
##### visitPageMessage
A link is placed below each search result. Use this property to change that link's text.
### searchInput
Place this component anywhere you want to display a simple search input with "search as you type" capabilities.
#### Usage example
Add the `searchInput` component to any layout, partial or page.
```html
title = "Home"
url = "/"
...
[searchInput]
useAutoComplete = 1
autoCompleteResultCount = 5
showProviderBadge = 1
searchPage = "search.htm"
==
{% component 'searchInput' %}
```
##### Example css to style the component
```css
.ss-search-form {
position: relative;
}
.ss-search-form__results {
display: none;
position: absolute;
left: 0;
top: 35px;
width: 100%;
background: #fff;
padding: 1em;
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
}
.ss-search-form__results--visible {
display: block;
}
```
#### Properties
The following properties are available to change the component's behaviour.
##### useAutoComplete
If this property is enabled, a search query will be executed as soon as the user begins to type.
##### autoCompleteResultCount
This many results will be displayed to the user below the input field. There will be a
"Show all results" link the user can click that takes her to a full search results page if one has
been specified via the `searchPage` property.
##### showProviderBadge
The search works by querying multiple providers (Pages, Blog, or other). If this option is enabled
each search result is marked with a badge to show which provider returned the result.
This is useful if your site has many different entities (ex. teams, employees, pages, blog entries).
##### searchPage
The filename of the page where you have placed a `searchResults` component. If a user clicks on the "Show all
results" link it will take him to this page where a full search is run using the `searchResults` component.
## Add support for custom plugin contents
### Simple method
To return search results for you own custom plugin, register an event listener for the `offline.sitesearch.query`
event in your plugin's boot method.
Return an array containing a `provider` string and `results` array. Each result must provide at least a `title` key.
#### Example to search for custom `documents`
```php
public function boot()
{
\Event::listen('offline.sitesearch.query', function ($query) {
// The controller is used to generate page URLs.
$controller = \Cms\Classes\Controller::getController() ?? new \Cms\Classes\Controller();
// Search your plugin's contents
$items = YourCustomDocumentModel
::where('title', 'like', "%${query}%")
->orWhere('content', 'like', "%${query}%")
->get();
// Now build a results array
$results = $items->map(function ($item) use ($query, $controller) {
// If the query is found in the title, set a relevance of 2
$relevance = mb_stripos($item->title, $query) !== false ? 2 : 1;
// Optional: Add an age penalty to older results. This makes sure that
// newer results are listed first.
// if ($relevance > 1 && $item->created_at) {
// $ageInDays = $item->created_at->diffInDays(\Illuminate\Support\Carbon::now());
// $relevance -= \OFFLINE\SiteSearch\Classes\Providers\ResultsProvider::agePenaltyForDays($ageInDays);
// }
return [
'title' => $item->title,
'text' => $item->content,
'url' => $controller->pageUrl('cms-page-file-name', ['slug' => $item->slug]),
'thumb' => optional($item->images)->first(), // Instance of System\Models\File
'relevance' => $relevance, // higher relevance results in a higher
// position in the results listing
// 'meta' => 'data', // optional, any other information you want
// to associate with this result
// 'model' => $item, // optional, pass along the original model
];
});
return [
'provider' => 'Document', // The badge to display for this result
'results' => $results,
];
});
}
```
That's it!
### Advanced method
If you need a bit more flexibility you can also create your own `ResultsProvider` class. Simply extend SiteSearch's
`ResultProvider` and implement the needed methods. Have a look at the existing providers shipped by this plugin to get
an idea of all the possibilities.
When your own `ResultsProvider` class is ready, register an event listener for the `offline.sitesearch.extend`
event in your plugin's boot method. There you can return one `ResultsProvider` (or multiple in an array) which will
be included every time a user runs a search on your website.
#### Advanced example to search for custom `documents`
```php
public function boot()
{
Event::listen('offline.sitesearch.extend', function () {
return new DocumentsSearchProvider();
// or
// return [new DocumentsSearchProvider(), new FilesSearchProvider()];
});
}
```
```php
query}%")
->orWhere('content', 'like', "%{$this->query}%")
->get();
// Create a new Result for every match
foreach ($matching as $match) {
$result = $this->newResult();
$result->relevance = 1;
$result->title = $match->title;
$result->text = $match->description;
$result->url = $match->url;
$result->thumb = $match->image;
$result->model = $match;
$result->meta = [
'some_data' => $match->some_other_property,
];
// Add the results to the results collection
$this->addResult($result);
}
return $this;
}
public function displayName()
{
return 'My Result';
}
public function identifier()
{
return 'VendorName.PluginName';
}
}
```
## Settings
You can manage all of this plugin's settings in the October CMS backend.
### Rainlab.Pages
No special configuration is required.
### Rainlab.Blog
Make sure you select your CMS page with the `blogPost` component as the `blog post page` in the backend settings.
You can access a post's published_at date in your search results via `{{ result.meta }}`.
### Feegleweb.Octoshop
Make sure you set the `Url of product detail page` setting to point to the right url. Only specify the fixed part of
the URL: `/product`. If your products are located under `/product/:slug` the default value is okay.
### Jiri.JKShop
Make sure you set the `Url of product detail page` setting to point to the right url. Only specify the fixed part of
the URL: `/product`. If your products are located under `/product/:slug` the default value is okay.
You can access an article's price in your search results via `{{ result.meta }}`.
### Indikator.News
Make sure you set the `News post page` setting to point to the right url. Only specify the fixed part of
the URL: `/news/post`. If your products are located under `/news/post/:slug` the default value is okay.
### RadiantWeb.ProBlog
Make sure you set the `Url of blog post page` setting to point to the right url. Only specify the fixed part of
the URL: `/blog`. If your posts are located under `/blog/:category/:slug` the default value is okay.
### ArrizalAmin.Portfolio
Make sure you set the `Url of portfolio detail page` setting to point to the right url. Only specify the fixed part of
the URL: `/portfolio/project`. If your detail page is located under `/portfolio/project/:slug` the default value is okay.
### VojtaSvoboda.Brands
Make sure you set the `Url of brand detail page` setting to point to the right URL. Only specify the fixed part of the URL: `/brand`. If your brand detail page is located under `/brand/:slug` then insert only `/brand` without the slug parameter.
### CMS pages (experimental)
If you want to provide search results for CMS pages change the `enabled` setting to `On`.
You have to specifically add the component `siteSearchInclude` to every CMS page you want to be searched.
Pages **without** this component will **not** be searched.
This feature works best with simple pages that include components, but don't rely on url parametres or other
variables (like a page number). CMS pages with dynamic URLs (like `/page/:slug`) won't be linked correctly from the search results listing.
If you have CMS pages with more complex dynamic contents consider writing your own search provider (see `Add support for custom
plugin contents`)
## Overwrite default markup
To overwrite the default markup copy all files from `plugins/offline/sitesearch/components/searchresults` to
`themes/your-theme/partials/searchResults` and modify them as needed.
If you gave an alias to the `searchResults` component make sure to put the markup in the appropriate partials directory `themes/your-theme/partials/your-given-alias`.