Don't manipulate $pages directly

Makes a Twig filter called apply_search available, which can be used in
templates to
filter the pages array on demand. Because of that, we can stop mutating
the pages array, which other extensions and themes may rely on.
This commit is contained in:
pontus.horn
2020-01-22 22:10:25 +01:00
parent b539f8e359
commit ba5fabdfdc
2 changed files with 62 additions and 49 deletions

View File

@@ -73,54 +73,52 @@ class PicoSearch extends AbstractPicoPlugin
}
/**
* If accessing search results, filter the $pages array to pages matching the search terms.
* Filter the input array to pages matching the search terms
* and sort them by relevance.
*
* @see Pico::getPages()
* @see Pico::getCurrentPage()
* @see Pico::getPreviousPage()
* @see Pico::getNextPage()
* @param array &$pages data of all known pages
* @param array &$currentPage data of the page being served
* @param array &$previousPage data of the previous page
* @param array &$nextPage data of the next page
* @return void
* @param array $pages data of all known pages
* @return array filtered and sorted pages
*/
public function onPagesLoaded(&$pages, &$currentPage, &$previousPage, &$nextPage)
public function applySearch($pages)
{
if ($currentPage && isset($this->search_area) || isset($this->search_terms)) {
if (isset($this->search_area)) {
$pages = array_filter($pages, function ($page) {
return substr($page['id'], 0, strlen($this->search_area)) === $this->search_area;
});
}
if (!isset($this->search_area) && !isset($this->search_terms)) {
return array();
}
$pico = $this->getPico();
$excludes = $pico->getConfig('search_excludes');
if (!empty($excludes)) {
foreach ($excludes as $exclude_path) {
unset($pages[$exclude_path]);
}
}
if (isset($this->search_area)) {
$pages = array_filter($pages, function ($page) {
return substr($page['id'], 0, strlen($this->search_area)) === $this->search_area;
});
}
if (isset($this->search_terms)) {
$pages = array_map(function ($page) {
$page['search_rank'] = $this->getSearchRankForPage($page);
return $page;
}, $pages);
$pages = array_filter($pages, function ($page) {
return $page['search_rank'] > 0;
});
uasort($pages, function ($a, $b) {
if ($a['search_rank'] == $b['search_rank']) {
return 0;
}
return $a['search_rank'] > $b['search_rank'] ? -1 : 1;
});
$pico = $this->getPico();
$excludes = $pico->getConfig('search_excludes');
if (!empty($excludes)) {
foreach ($excludes as $exclude_path) {
unset($pages[$exclude_path]);
}
}
if (isset($this->search_terms)) {
$pages = array_map(function ($page) {
$page['search_rank'] = $this->getSearchRankForPage($page);
return $page;
}, $pages);
$pages = array_filter($pages, function ($page) {
return $page['search_rank'] > 0;
});
uasort($pages, function ($a, $b) {
if ($a['search_rank'] == $b['search_rank']) {
return 0;
}
return $a['search_rank'] > $b['search_rank'] ? -1 : 1;
});
}
return $pages;
}
public function getSearchRankForPage($page) {
@@ -176,4 +174,8 @@ class PicoSearch extends AbstractPicoPlugin
public function onPageRendering(&$twig, &$twigVariables, &$templateName) {
$twigVariables['search_terms'] = $this->search_terms;
}
public function onTwigRegistration() {
$this->getPico()->getTwig()->addFilter(new \Twig\TwigFilter('apply_search', [$this, 'applySearch']));
}
}

View File

@@ -1,21 +1,28 @@
# Pico-Search
A plugin for the flat file CMS [Pico](https://github.com/picocms/Pico). Allows you to create a very basic search form
that searches through titles and content of your pages. The search results page filters the `pages` array to only
that searches through titles and content of your pages. The search results page can filter the `pages` array to only
contain pages matching the search terms.
You can optionally scope the search to only get results from within a certain folder. For example, on the page
`yoursite.com/blog/search/foobar`, the `pages` array will only contain results from pages in the `blog` folder.
`yoursite.com/blog/search/foobar`, the filtered array will only contain results from pages in the `blog` folder.
Search results can be paginated using a plugin such as [Pico-Pagination](https://github.com/rewdy/Pico-Pagination).
The search plugin should be executed before the pagination plugin (execution order is determined by file name).
## Breaking changes as of January 2020
If you were using an earlier version of the plugin, the current version will not work as before. Previously the `pages`
array was directly modified to filter and sort search results. This approach was not compatible with other Pico
plugins and themes which depend on the `pages` array to contain all pages.
Instead, this plugin now makes a Twig filter called `apply_search` available, which can be used in your templates to
filter the `pages` array on demand: `{% set search_results = pages|apply_search %}`. This also makes it possible to
combine with other plugins that behave similarly: `{% set results = pages|apply_search|paginate %}`
## Installation
- **Copy the file `40-PicoSearch.php` to the `plugins` sub-folder of your Pico installation directory.**
- **Add a file named `search.md` to your content root or the sub-folder you want to make searchable.**
This is your search results page. You can leave it empty of content, but set the `Template` meta tag to a template
that loops through the pages and displays them. Your `search.md` might look like this:
that displays the filtered pages. Your `search.md` might look like this:
```
/*
@@ -26,14 +33,15 @@ The search plugin should be executed before the pagination plugin (execution ord
- **Add a template file with the name defined in `search.md`.**
Your template file (`search.twig` in the above example) should contain something like the following section, which
lists the pages matching the search (substitute `paged_pages` for `pages` if using Pico-Pagination):
lists the pages matching the search:
```twig
{% if search_terms %}
<div class="SearchResults">
{% if pages %}
{% set search_results = pages|apply_search %}
{% if search_results %}
<h2>Search results for {{ search_terms|e('html') }}</h2>
{% for page in pages %}
{% for page in search_results %}
<div class="SearchResult">
<h3><a href="{{ page.url }}">{{ page.title }}</a></h3>
{% if page.description %}<p>{{ page.description }}</p>{% endif %}
@@ -46,6 +54,9 @@ The search plugin should be executed before the pagination plugin (execution ord
{% endif %}
```
The `apply_search` filter takes any search terms for the current page and filters the specified array
by them, as well as sorting them by relevance.
If you simply want to make your search results page look like your standard page, you may want to edit your theme's `index.twig` file and change `{{ content }}` to `{% block content %} {{ content }} {% endblock %}`. This allows you to extend this base template and reuse all the other parts of it in your search results template:
```twig