This plugin adds global search capabilities to WinterCMS.
This plugin is a WinterCMS-compatible fork of the original OFFLINE.SiteSearch plugin for OctoberCMS. It has been adapted to work seamlessly with WinterCMS, preserving the original functionality and adding enhancements specific to the WinterCMS ecosystem.
composer require spanjaan/wn-site-search-plugin
- English
- German
- Czech
- Russian
- Persian (Farsi)
- Portuguese
You can translate all contents into your own language.
- Winter.Pages
- Winter.Blog
- Native CMS pages (experimental)
These plugins are supported but deactivated by default. You can manually activate them by removing the comments in following files:
{PLUGIN ROOT}/models/settings/fields.yaml
{PLUGIN ROOT}/classes/SearchService.php
- Indikator.News
- Feegleweb.Octoshop
- Jiri.JKShop
- RadiantWeb.ProBlog
- Arrizalamin.Portfolio
- Responsiv.Showcase
- VojtaSvoboda.Brands
- Graker.PhotoAlbums
Multilingual contents via Winter.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.
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.
We cannot add support for every plugin but will add any plugin that has a notable project count on the Winter Marketplace.
Place this component on your page to display search results.
Create a search form that sends a query to your search page:
<form action="{{ 'search' | page }}" method="get">
<input name="q" type="text" placeholder="What are you looking for?" autocomplete="off">
<button type="submit">Search</button>
</form>
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.
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.
title = "Search results"
url = "/search"
layout = "default"
[searchResults]
resultsPerPage = 10
showProviderBadge = 1
noResultsMessage = "Your search did not return any results."
visitPageMessage = "Visit page"
==
<h2>Search results for {{ searchResults.query }}</h2>
{% component 'searchResults' %}
.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 {
}
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.
[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' %}
You can listen for the spanjaan.sitesearch.results
event and modify the query as you wish.
This is useful to remove certain results or change the sort order.
[searchResults]
resultsPerPage = 10
showProviderBadge = 1
noResultsMessage = "Your search returned no results."
visitPageMessage = "Visit page"
==
function onInit()
{
\Event::listen('spanjaan.sitesearch.results', function ($results) {
// return $results->filter(...);
return $results->sortByDesc('model.custom_attribute');
});
}
==
{% component 'searchResults' %}
The following properties are available to change the component's behaviour.
How many results to display on one page.
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).
This message is shown if there are no results returned.
A link is placed below each search result. Use this property to change that link's text.
Place this component anywhere you want to display a simple search input with "search as you type" capabilities.
Add the searchInput
component to any layout, partial or page.
title = "Home"
url = "/"
...
[searchInput]
useAutoComplete = 1
autoCompleteResultCount = 5
showProviderBadge = 1
searchPage = "search.htm"
==
{% component 'searchInput' %}
.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;
}
The following properties are available to change the component's behaviour.
If this property is enabled, a search query will be executed as soon as the user begins to type.
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.
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).
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.
To return search results for you own custom plugin, register an event listener for the spanjaan.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.
public function boot()
{
\Event::listen('spanjaan.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 -= \SpAnjaan\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!
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 spanjaan.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.
public function boot()
{
Event::listen('spanjaan.sitesearch.extend', function () {
return new DocumentsSearchProvider();
// or
// return [new DocumentsSearchProvider(), new FilesSearchProvider()];
});
}
<?php
use SpAnjaan\Sitesearch\Classes\Providers\ResultsProvider;
class DocumentsSearchProvider extends ResultsProvider
{
public function search()
{
// Get your matching models
$matching = YourCustomDocumentModel::where('title', 'like', "%{$this->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';
}
}
You can manage all of this plugin's settings in the Winter CMS backend.
No special configuration is required.
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 }}
.
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.
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 }}
.
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.
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.
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.
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.
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
)
To overwrite the default markup copy all files from plugins/spanjaan/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
.