├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── readme_1.png └── search.svg ├── blueprints.yaml ├── composer.json ├── composer.lock ├── css └── simplesearch.css ├── hebe.json ├── js └── simplesearch.js ├── languages.yaml ├── pages └── simplesearch.md ├── simplesearch.php ├── simplesearch.yaml ├── templates ├── partials │ ├── simplesearch_base.html.twig │ ├── simplesearch_item.html.twig │ └── simplesearch_searchbox.html.twig ├── simplesearch_results.html.twig └── simplesearch_results.json.twig └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php └── installed.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.3.0 2 | ## 08/26/2022 3 | 4 | 1. [](#new) 5 | * Added new `onSimpleSearchCollection()` event to allow for custom integration into SimpleSearch. 6 | * Require Grav 1.7+ 7 | 8 | # v2.2.2 9 | ## 05/25/2021 10 | 11 | 1. [](#bugfix) 12 | * `|raw` missing in JSON template 13 | 14 | # v2.2.1 15 | ## 12/02/2020 16 | 17 | 1. [](#new) 18 | * Require Grav 1.6.0+ 19 | * Added autoloader 20 | * Pass phpstan level 3 tests 21 | 1. [](#bugfix) 22 | * Fixed `header_keys_ignored` configuration option not taking effect 23 | 24 | # v2.2.0 25 | ## 09/14/2020 26 | 27 | 1. [](#improved) 28 | * Added config option to choose keys to ignore when searching header 29 | * Added ability to opt-out of page search by setting: `simplesearch: process: false` in page header 30 | * Added `form` and `forms` to default list of ignores in page header searches 31 | 1. [](#bugfix) 32 | * Fix issue to ensure matching ignore keys only checked at root level 33 | * Fix issue where searchable header content was growing exponentially 34 | 35 | # v2.1.0 36 | ## 05/27/2020 37 | 38 | 1. [](#new) 39 | * Added ability to search page headers (excluding title, taxonomy, content) 40 | 41 | # v2.0.0 42 | ## 05/13/2020 43 | 44 | 1. [](#new) 45 | * Allow searching the content of modular pages [#170](https://github.com/getgrav/grav-plugin-simplesearch/pull/170) 46 | * Allow configuration of Title/Content/Taxononmy searchable types [#184](https://github.com/getgrav/grav-plugin-simplesearch/pull/184) 47 | 1. [](#improved) 48 | * Improved default vs custom search page functionality [#186](https://github.com/getgrav/grav-plugin-simplesearch/pull/186) 49 | * Add field label for screen reader accessibility [#171](https://github.com/getgrav/grav-plugin-simplesearch/pull/171) 50 | * Added Traditional Chinese [#169](https://github.com/getgrav/grav-plugin-simplesearch/pull/169) 51 | * Updated Russian and English [#165](https://github.com/getgrav/grav-plugin-simplesearch/pull/165) 52 | * Added Galacian [#155](https://github.com/getgrav/grav-plugin-simplesearch/pull/155) 53 | 1. [](#bugfix) 54 | * Fixed input attribute [#165](https://github.com/getgrav/grav-plugin-simplesearch/pull/165) 55 | 56 | # v1.14.2 57 | ## 12/07/2018 58 | 59 | 1. [](#improved) 60 | * Optimize, cleanup and remove typos [#163](https://github.com/getgrav/grav-plugin-simplesearch/pull/163) 61 | * Removed `blog` as default filter [#166](https://github.com/getgrav/grav-plugin-simplesearch/pull/166) 62 | * Polish translation [#144](https://github.com/getgrav/grav-plugin-simplesearch/pull/144) 63 | * Kazakh translation [#153](https://github.com/getgrav/grav-plugin-simplesearch/pull/153) 64 | * Spelling corrections [#145](https://github.com/getgrav/grav-plugin-simplesearch/pull/145) 65 | 1. [](#bugfix) 66 | * Fix JS to work with IE11 [#161](https://github.com/getgrav/grav-plugin-simplesearch/pull/161) 67 | * Updated javascript to be compatible with IE11 [#161](https://github.com/getgrav/grav-plugin-simplesearch/pull/161) 68 | * Ensure `$values` is an array to prevent PHP error on implode [#146](https://github.com/getgrav/grav-plugin-simplesearch/pull/146) 69 | 70 | # v1.14.1 71 | ## 01/11/2018 72 | 73 | 1. [](#bugfix) 74 | * Fix for Gantry5 themes 75 | 76 | # v1.14.0 77 | ## 01/08/2018 78 | 79 | 1. [](#new) 80 | * Added Danish translations [#127](https://github.com/getgrav/grav-plugin-simplesearch/pull/127) 81 | 1. [](#improved) 82 | * New option to disable built-in JS [#130](https://github.com/getgrav/grav-plugin-simplesearch/pull/130) 83 | * Changed elipsis from `...` to `…` [#133](https://github.com/getgrav/grav-plugin-simplesearch/pull/133) 84 | * Added missing French translations [#136](https://github.com/getgrav/grav-plugin-simplesearch/pull/136) 85 | * Added missing German translations [#128](https://github.com/getgrav/grav-plugin-simplesearch/pull/128) 86 | 1. [](#bugfix) 87 | * Escaped `query` in Twig templates for XSS protection 88 | 89 | # v1.13.0 90 | ## 07/26/2017 91 | 92 | 1. [](#improved) 93 | * Support for multiple forms and fields in the same page 94 | 1. [](#bugfix) 95 | * Fix typo in `SEARCH_FIELD_MINIUMUM_CHARACTERS` translation string 96 | * Fixed validation and JS submission 97 | * Separated JS from inline to file 98 | * Fixed issue with min query length always enforced. It is now possible to have no minimum length by setting to `false` or `0` 99 | 100 | # v1.12.0 101 | ## 05/31/2017 102 | 103 | 1. [](#new) 104 | * Added option to switch between Rendered HTML and Raw Markdown content searching. Raw Markdown is faster than default. 105 | 106 | # v1.11.0 107 | ## 05/29/2017 108 | 109 | 1. [](#new) 110 | * Allow to use "@none"/"none@" in the "Category filter" in Admin to allow removing the filter 111 | 112 | # v1.10.2 113 | ## 04/19/2017 114 | 115 | 1. [](#bugfix) 116 | * Only check ACL if the Login plugin is installed [#112](https://github.com/getgrav/grav-plugin-simplesearch/pull/112) 117 | 118 | # v1.10.1 119 | ## 04/11/2017 120 | 121 | 1. [](#new) 122 | * Added Portuguese translation 123 | * Add hint when the minimum search field length is not matched 124 | 1. [](#bugfix) 125 | * Default `ignore_accented_characters` to false 126 | * Fallback to regular search if searching with `ignore_accented_characters` on an unsupported charset raises an exception [#107](https://github.com/getgrav/grav-plugin-simplesearch/issues/107) 127 | * Check ACL before listing a page in the search results [#102](https://github.com/getgrav/grav-plugin-simplesearch/pull/102) 128 | * Fix with ignoring `min_query_length` when using the button [#99](https://github.com/getgrav/grav-plugin-simplesearch/issues/99) 129 | 130 | # v1.10.0 131 | ## 01/23/2017 132 | 133 | 1. [](#new) 134 | * Added spanish translation 135 | * Added japanese translation 136 | * Added persian translation 137 | 1. [](#improved) 138 | * Added option to switch between Rendered HTML and Raw Markdown content searching. Raw Markdown is faster than default. 139 | * Removed jQuery dependency, fixes issue when jQuery is loaded in the footer [#57](https://github.com/getgrav/grav-plugin-simplesearch/pull/57) 140 | * Added option to ignore accents when searching [#89](https://github.com/getgrav/grav-plugin-simplesearch/pull/89) 141 | 1. [](#bugfix) 142 | * Remove unpublished and un-routable pages from the result set 143 | * Fixed issue when using @self as route 144 | * Fix overloaded property issue when searching on a page with simplesearch header [#80](https://github.com/getgrav/grav-plugin-simplesearch/issues/80) 145 | * Fix issue with empty string and leading commas [#71](https://github.com/getgrav/grav-plugin-simplesearch/issues/71) 146 | 147 | # v1.9.3 148 | ## 10/19/2016 149 | 150 | 1. [](#bugfix) 151 | * Fixed an issue with invalid syntax in `route: @self` logic 152 | 153 | # v1.9.2 154 | ## 09/19/2016 155 | 156 | 1. [](#bugfix) 157 | * Reverted change in events - causing problems 158 | * Reverted fix for `route: @self`, breaking `filter: @self` (used in getgrav.org) 159 | 160 | # v1.9.1 161 | ## 09/08/2016 162 | 163 | 1. [](#bugfix) 164 | * Fixed logic to use `onPageInitialized` event 165 | 166 | # v1.9.0 167 | ## 09/06/2016 168 | 169 | 1. [](#new) 170 | * Multiple search boxes support [#52](https://github.com/getgrav/grav-plugin-simplesearch/pull/52) 171 | * Added Croatian and Russian translation 172 | 1. [](#improved) 173 | * Added support for Grav's autoescape twig setting 174 | 1. [](#bugfix) 175 | * Fix searching on `@self `[#53](https://github.com/getgrav/grav-plugin-simplesearch/pull/53) 176 | 177 | # v1.8.0 178 | ## 07/14/2016 179 | 180 | 1. [](#new) 181 | * Added dutch and romanian 182 | 1. [](#bugfix) 183 | * Fix translating the search input placeholder 184 | 185 | # v1.7.1 186 | ## 05/03/2016 187 | 188 | 1. [](#new) 189 | * Added configurable `min length` option for how many characters needed before you can search 190 | 191 | # v1.7.0 192 | ## 04/30/2016 193 | 194 | 1. [](#new) 195 | * Added support for taxonomy searching in regular searches (not just on-page searches as was the case previously) 196 | * Added support for `route: '@self'` to use the route of the current page without specifying it. 197 | * Added display search button option - #33 198 | * Refactored code for clarity 199 | 200 | # v1.6.2 201 | ## 01/06/2016 202 | 203 | 1. [](#improved) 204 | * Improved the README instructions on how to save all pages 205 | 206 | # v1.6.1 207 | ## 11/11/2015 208 | 209 | 1. [](#improved) 210 | * Strip HTML tags from title and content before searching 211 | 212 | # v1.6.0 213 | ## 11/11/2015 214 | 215 | 1. [](#new) 216 | * Removing `filter:` from configuration will search **ALL** pages 217 | 218 | # v1.5.1 219 | ## 10/15/2015 220 | 221 | 1. [](#improved) 222 | * Minor performance fix 223 | * Updated README.md with more help 224 | 1. [](#bugfix) 225 | * Fix for special character searches 226 | 227 | # v1.5.0 228 | ## 10/07/2015 229 | 230 | 1. [](#new) 231 | * Allow simplesearch to work with on-page collections 232 | 233 | # v1.4.1 234 | ## 08/31/2015 235 | 236 | 1. [](#improved) 237 | * Fixed some blueprint issues 238 | 239 | # v1.4.0 240 | ## 08/25/2015 241 | 242 | 1. [](#improved) 243 | * Added blueprints for Grav Admin plugin 244 | * Added results sorting 245 | 246 | # v1.3.0 247 | ## 07/21/2015 248 | 249 | 1. [](#new) 250 | * Added support for modular pages in results 251 | 252 | # v1.2.7 253 | ## 07/17/2015 254 | 255 | 1. [](#bugfix) 256 | * Fixed "Undefined index: extension" error 257 | 258 | # v1.2.6 259 | ## 07/14/2015 260 | 261 | 1. [](#bugfix) 262 | * Fixed URL issue that showed up with multi-languages 263 | 264 | # v1.2.5 265 | ## 04/24/2015 266 | 267 | 1. [](#bugfix) 268 | * Fixed issue with broken image 269 | 270 | # v1.2.4 271 | ## 02/19/2015 272 | 273 | 2. [](#improved) 274 | * Implemented new `param_sep` variable from Grav 0.9.18 275 | 276 | # v1.2.3 277 | ## 01/06/2015 278 | 279 | 1. [](#improved) 280 | * Improved `README.md` file with more information 281 | 282 | # v1.2.2 283 | ## 12/21/2014 284 | 285 | 1. [](#bugfix) 286 | * Fix for invalid base_url in some instances 287 | 288 | # v1.2.1 289 | ## 11/30/2014 290 | 291 | 1. [](#new) 292 | * ChangeLog started... 293 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Grav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grav SimpleSearch Plugin 2 | 3 | ![SimpleSearch](assets/readme_1.png) 4 | 5 | `SimpleSearch` is a simple, yet very powerful [Grav][grav] plugin that adds search capabilities to your Grav instance. By default it can search Page **Titles**, **Content**, **Taxonomy**, and also a raw page **Header**. 6 | 7 | # Installation 8 | 9 | Installing the SimpleSearch plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. 10 | 11 | ## GPM Installation (Preferred) 12 | 13 | The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: 14 | 15 | bin/gpm install simplesearch 16 | 17 | This will install the SimpleSearch plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/simplesearch`. 18 | 19 | ## Manual Installation 20 | 21 | To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `simplesearch`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-simplesearch) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). 22 | 23 | You should now have all the plugin files under 24 | 25 | /your/site/grav/user/plugins/simplesearch 26 | 27 | > NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate. 28 | 29 | # Config Options 30 | 31 | To effectively use the plugin, you first need to create an override config. To do so, create the folder `user/config/plugins` (if it doesn't exist already) and copy the [simplesearch.yaml][simplesearch] config file in there. 32 | 33 | ``` 34 | enabled: true 35 | built_in_css: true 36 | built_in_js: true 37 | display_button: false 38 | min_query_length: 3 39 | route: /search 40 | search_content: rendered 41 | template: simplesearch_results 42 | filters: 43 | category: 44 | filter_combinator: and 45 | ignore_accented_characters: false 46 | order: 47 | by: date 48 | dir: desc 49 | searchable_types: 50 | title: true 51 | content: true 52 | taxonomy: true 53 | header: false 54 | header_keys_ignored: ['title', 'taxonomy','content', 'form', 'forms', 'media_order'] 55 | ``` 56 | 57 | By creating the configuration file: `user/config/plugins/simplesearch.yaml` you have effectively created a site-wide configuration for SimpleSearch. However, you may want to have multiple searches. 58 | 59 | > NOTE: If you want to search **ALL PAGES** just keep the `filters` section empty. 60 | 61 | To accomplish multiple search types in a single site, you should use **page-based** configuration. This is simple to do, simply provide any or all of the configuration options under a `simplesearch:` header in your page frontmatter. For example: 62 | 63 | ``` 64 | simplesearch: 65 | process: true 66 | route: @self 67 | filters: 68 | - @self 69 | - @taxonomy: [tag] 70 | filter_combinator: and 71 | ``` 72 | 73 | These page headers will only be taken into account if the search route points to this page. For example: here the route points to `@self` which in turn resolves to `/blog`. You can also specify the route explicitly with `route: /blog` if you so choose. This header is within the `/user/pages/blog/blog.md` file. We will cover this self-controlled form of search handling below. 74 | 75 | # Usage 76 | 77 | There are two approaches to using SimpleSearch. 78 | 79 | ## 1. Standalone Search Page 80 | 81 | This is the traditional approach and involves having a searchbox 'somewhere' on your site. When you search you are shown a new page that displays the search results. On this page you will see a summary of the results and be able to click a link to visit each applicable page within your site. Think about how **Google** and other traditional search engines work. 82 | 83 | After installing the SimpleSearch plugin, you can add a simple **searchbox** to your site by including the provided twig template. Or copy it from the plugin to your theme and customize it as you please: 84 | 85 | ``` 86 | {% include 'partials/simplesearch_searchbox.html.twig' %} 87 | ``` 88 | 89 | By default the **simplesearch_searchbox** Twig template uses the `route` as defined in the configuration. The SimpleSearch plugin uses this route and then appends a `query:` parameter to create the following final URL. 90 | 91 | ``` 92 | http://yoursite.com/search/query:something 93 | ``` 94 | 95 | 1. `/search`: This is the **route** setting and it can be changed 96 | 2. `/query:something`: This is the query itself, where `something` is what you are searching for. 97 | 98 | The plugin actively looks for URLs requested that match the configured `route` and if so it intercepts the call and renders the results template as specified by the configuration options, (defaults to `simplesearch_results.html.twig` as provided by the plugin). 99 | 100 | With this approach, the filters control which pages are searched. You can have multiple taxonomy filters here, and can configure the combinator to require **any** match (via `or`) or require **all** conditions to match (via `and`). 101 | 102 | You can also completely customize the look and feel of the results by overriding the template. There are two methods to do this. 103 | 104 | 1. Copy the file [templates/simplesearch_results.html.twig][results] under your theme templates `user/themes/_your-theme_/templates/` and customize it. 105 | 106 | 2. Create your very own results output. For this you need to change the `template` reference in the config (let's say **mysearch_results**). In your theme you would then create the new template under `user/themes/_your-theme_/templates/mysearch_results.html.twig` and write your customizations. This is how it looks by default: 107 | 108 | ``` 109 | {% extends 'partials/simplesearch_base.html.twig' %} 110 | 111 | {% block content %} 112 |
113 |

Search Results

114 |

Query: "{{ query }}" - Found {{ search_results.count }} {{ 'Item'|pluralize(search_results.count) }}

115 | 116 | {% for page in search_results %} 117 | {% include 'partials/simplesearch_item.html.twig' with {'page':page} %} 118 | {% endfor %} 119 |
120 | {% endblock %} 121 | ``` 122 | 123 | ## 2. Self-Controlled Search Page 124 | 125 | This is a new feature of SimpleSearch and it's a very useful and simple way to provide a 'filter' like search of a collection listing page. In this example, we will assume you have a Blog listing page you wish to be able to search and filter based on a search box. 126 | 127 | To accomplish this, you need to use the page-based configuration as described above, and configure multiple filters, `@self` to use the page's content collection: http://learn.getgrav.org/content/headers#collection-headers 128 | 129 | ``` 130 | content: 131 | items: @self.children 132 | order: 133 | by: date 134 | dir: desc 135 | ``` 136 | 137 | This will mean the search will only search pages that this page already is using for the collection. The Items could be anything the page collections support: 138 | 139 | For further help with the `filters` and `order` settings, please refer to our [Taxonomy][taxonomy] and [Headers][headers] documentation. 140 | 141 | Multiple filters can be provided, and in order to search in the page's **Tag** field you would add `- @taxonomy: [tag]` as shown in the configuration example above. 142 | 143 | The only thing needed to provide this functionality is a search box that points to the current page and appends the `query` parameter. You can again simple include the sample `simplesearch_searchbox.html.twig` file or add your own. Because the route is configured to point to the blog page, and because the blog page already iterates over a collection, SimpleSearch will replace the page collection with the search-filtered collection. No results page is required. 144 | 145 | ## Performance 146 | 147 | Simplesearch is not a full-fledged index-powered search engine. It merely iterates over the pages and searches the content and title for matching strings. That's it. This is not going to result in screaming fast searches if your site has lots of content. One way to optimize things a little is to change the `search_content` configuration option from `rendered` to `raw`. This means the `rawMarkdown()` method is used rather than the `content()` method, to retrieve the page content, and in turn means plugin events, markdown processing, image processing, and other time-consuming tasks are not performed. This can often yield adequate search results without the need for all this extra work. 148 | 149 | ## Searching Taxonomy 150 | 151 | By default **SimpleSearch** will search in the **Title**, **Content**, and **Taxonomy**. All taxonomy will be searched unless you provide a **taxonomy filter** either in the page, or in the global plugin configuration: 152 | 153 | ``` 154 | filters: 155 | - @taxonomy: [tag] 156 | ``` 157 | 158 | This will ensure that only **tag** taxonomy types will be searched for the query. 159 | 160 | ``` 161 | filters: 162 | - @taxonomy: [tag, author] 163 | ``` 164 | 165 | Will ensure that both **tag** and **author** taxonomy types are searched. 166 | 167 | As **all taxonomy types are searched by default**, in order to stop searching into taxonomies completely simply set the filter to false: 168 | 169 | ``` 170 | filters: 171 | - '@taxonomy': false 172 | ``` 173 | 174 | ## Ignoring a page 175 | 176 | A page can be setup to "opt-out" of being included in the search results by setting the following in page frontmatter: 177 | 178 | ```yaml 179 | simplesearch: 180 | process: false 181 | ``` 182 | 183 | ## Ignoring accented characters 184 | 185 | You can tell Simplesearch to return a positive value when searching for characters that have an accent. So `éè` for example will be both equivalent to `e`. 186 | 187 | To do so, enable _Ignore accented characters_ in Admin, or manually set `ignore_accented_characters` to true in the plugin configuration. 188 | The `en_US` locale must be installed on the server. 189 | 190 | # Extending 191 | 192 | As of version `2.3.0` SimpleSearch has a Grav even that allow for integrating into custom logic and adding your own 'pages' into the searchable collection. Because SimpleSearch utilizes Grav pages for its searching mechanism, your event needs to build fake 'pages' from your data, then you can add to the collection being passed to the event. Some example psudeo code should help you out: 193 | 194 | ```php 195 | public function onSimpleSearchCollection(Event $event) 196 | { 197 | $collection = $event['collection']; 198 | $locator = $this->grav['locator']; 199 | $pages = $this->grav['pages']; 200 | 201 | //find all my custom files 202 | $finder = new Finder(); 203 | $data_location = $locator->findResource("user://data/custom-data"); 204 | 205 | foreach($finder->in($data_location)->name('*.json') as $file) { 206 | $content = $file->getContents(); 207 | $data = json_decode($content, true); 208 | 209 | $header['routes']['default'] = $data['url'], 210 | $page = new Page(); 211 | $page->title($data['title']); 212 | $page->content($data['content']); 213 | $page->path($file->getPathname()); 214 | $page->header($header); 215 | 216 | // Page needs to be added to Pages inorder to work in Collection 217 | $pages->addPage($page); 218 | // Add the fake page to the collection used to search 219 | $collection->addPage($page); 220 | } 221 | } 222 | ``` 223 | 224 | # Updating 225 | 226 | As development for SimpleSearch continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating SimpleSearch is easy, and can be done through Grav's GPM system, as well as manually. 227 | 228 | ## GPM Update (Preferred) 229 | 230 | The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: 231 | 232 | bin/gpm update simplesearch 233 | 234 | This command will check your Grav install to see if your SimpleSearch plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. 235 | 236 | 237 | > Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. 238 | 239 | [taxonomy]: http://learn.getgrav.org/content/taxonomy 240 | [headers]: http://learn.getgrav.org/content/headers 241 | [grav]: http://github.com/getgrav/grav 242 | [simplesearch]: simplesearch.yaml 243 | [results]: templates/simplesearch_results.html.twig 244 | -------------------------------------------------------------------------------- /assets/readme_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgrav/grav-plugin-simplesearch/54fc014ac70cc630278c35929b1000f6c823f7d8/assets/readme_1.png -------------------------------------------------------------------------------- /assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: SimpleSearch 2 | type: plugin 3 | slug: simplesearch 4 | version: 2.3.0 5 | description: "Don't be fooled, the **SimpleSearch** plugin provides a **fast** and highly **configurable** way to search your content." 6 | icon: search 7 | author: 8 | name: Team Grav 9 | email: devs@getgrav.org 10 | url: http://getgrav.org 11 | homepage: https://github.com/getgrav/grav-plugin-simplesearch 12 | demo: http://demo.getgrav.org/blog-skeleton 13 | keywords: simplesearch, plugin, search, page, content, find 14 | bugs: https://github.com/getgrav/grav-plugin-simplesearch/issues 15 | license: MIT 16 | 17 | dependencies: 18 | - { name: grav, version: '>=1.7.0' } 19 | 20 | form: 21 | validation: strict 22 | fields: 23 | enabled: 24 | type: toggle 25 | label: PLUGIN_ADMIN.PLUGIN_STATUS 26 | highlight: 1 27 | default: 0 28 | options: 29 | 1: PLUGIN_ADMIN.ENABLED 30 | 0: PLUGIN_ADMIN.DISABLED 31 | validate: 32 | type: bool 33 | 34 | search_content: 35 | type: select 36 | size: medium 37 | classes: fancy 38 | label: PLUGIN_SIMPLESEARCH.SEARCH_CONTENT 39 | default: rendered 40 | options: 41 | rendered: PLUGIN_SIMPLESEARCH.RENDERED_CONTENT 42 | raw: PLUGIN_SIMPLESEARCH.RAW_CONTENT 43 | 44 | built_in_css: 45 | type: toggle 46 | label: PLUGIN_SIMPLESEARCH.BUILTIN_CSS 47 | help: PLUGIN_SIMPLESEARCH.BUILTIN_CSS_HELP 48 | highlight: 1 49 | default: 1 50 | options: 51 | 1: PLUGIN_ADMIN.ENABLED 52 | 0: PLUGIN_ADMIN.DISABLED 53 | validate: 54 | type: bool 55 | 56 | built_in_js: 57 | type: toggle 58 | label: PLUGIN_SIMPLESEARCH.BUILTIN_JS 59 | help: PLUGIN_SIMPLESEARCH.BUILTIN_JS_HELP 60 | highlight: 1 61 | default: 1 62 | options: 63 | 1: PLUGIN_ADMIN.ENABLED 64 | 0: PLUGIN_ADMIN.DISABLED 65 | validate: 66 | type: bool 67 | 68 | display_button: 69 | type: toggle 70 | label: PLUGIN_SIMPLESEARCH.DISPLAY_SEARCH_BUTTON 71 | help: PLUGIN_SIMPLESEARCH.DISPLAY_SEARCH_BUTTON_HELP 72 | highlight: 0 73 | default: 0 74 | options: 75 | 1: PLUGIN_ADMIN.ENABLED 76 | 0: PLUGIN_ADMIN.DISABLED 77 | validate: 78 | type: bool 79 | 80 | ignore_accented_characters: 81 | type: toggle 82 | label: PLUGIN_SIMPLESEARCH.IGNORE_ACCENDED_CHARACTERS 83 | help: PLUGIN_SIMPLESEARCH.IGNORE_ACCENDED_CHARACTERS_HELP 84 | highlight: 0 85 | default: 0 86 | options: 87 | 1: PLUGIN_ADMIN.ENABLED 88 | 0: PLUGIN_ADMIN.DISABLED 89 | validate: 90 | type: bool 91 | 92 | min_query_length: 93 | type: text 94 | size: x-small 95 | label: PLUGIN_SIMPLESEARCH.MIN_QUERY_LENGTH 96 | help: PLUGIN_SIMPLESEARCH.MIN_QUERY_LENGTH_HELP 97 | validate: 98 | type: number 99 | min: 0 100 | 101 | route: 102 | type: text 103 | size: medium 104 | label: PLUGIN_SIMPLESEARCH.SEARCH_PAGE_ROUTE 105 | help: PLUGIN_SIMPLESEARCH.SEARCH_PAGE_ROUTE_HELP 106 | default: /random 107 | 108 | searchable_types: 109 | type: checkboxes 110 | label: PLUGIN_SIMPLESEARCH.SEARCHABLE_TYPES 111 | description: PLUGIN_SIMPLESEARCH.SEARCHABLE_TYPES_DESCRIPTION 112 | options: 113 | title: Title 114 | content: Content 115 | header: Header 116 | taxonomy: Taxonomy 117 | use: keys 118 | 119 | header_keys_ignored: 120 | type: selectize 121 | size: large 122 | label: PLUGIN_SIMPLESEARCH.HEADER_KEYS_IGNORED 123 | help: PLUGIN_SIMPLESEARCH.HEADER_KEYS_IGNORED_HELP 124 | classes: fancy 125 | validate: 126 | type: commalist 127 | 128 | template: 129 | type: text 130 | size: medium 131 | label: PLUGIN_SIMPLESEARCH.SEARCH_PAGE_TEMPLATE 132 | help: PLUGIN_SIMPLESEARCH.SEARCH_PAGE_TEMPLATE_HELP 133 | default: simplesearch_results 134 | 135 | filters.category: 136 | type: selectize 137 | label: PLUGIN_SIMPLESEARCH.CATEGORY_FILTER 138 | help: PLUGIN_SIMPLESEARCH.CATEGORY_FILTER_HELP 139 | validate: 140 | type: commalist 141 | 142 | filter_combinator: 143 | type: select 144 | size: medium 145 | classes: fancy 146 | label: PLUGIN_SIMPLESEARCH.FILTER_COMBINATOR 147 | default: and 148 | options: 149 | and: PLUGIN_SIMPLESEARCH.AND_COMBINATOR 150 | or: PLUGIN_SIMPLESEARCH.OR_COMBINATOR 151 | 152 | order.by: 153 | type: select 154 | size: long 155 | classes: fancy 156 | label: PLUGIN_ADMIN.DEFAULT_ORDERING 157 | help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP 158 | options: 159 | default: PLUGIN_ADMIN.DEFAULT_ORDERING_DEFAULT 160 | folder: PLUGIN_ADMIN.DEFAULT_ORDERING_FOLDER 161 | title: PLUGIN_ADMIN.DEFAULT_ORDERING_TITLE 162 | date: PLUGIN_ADMIN.DEFAULT_ORDERING_DATE 163 | 164 | order.dir: 165 | type: toggle 166 | label: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION 167 | highlight: asc 168 | default: desc 169 | help: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION_HELP 170 | options: 171 | asc: PLUGIN_ADMIN.ASCENDING 172 | desc: PLUGIN_ADMIN.DESCENDING 173 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getgrav/grav-plugin-simplesearch", 3 | "type": "grav-plugin", 4 | "description": "Simple search plugin for Grav CMS", 5 | "keywords": ["simplesearch", "search", "plugin"], 6 | "homepage": "https://github.com/getgrav/grav-plugin-simplesearch", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Team Grav", 11 | "email": "devs@getgrav.org", 12 | "homepage": "https://getgrav.org", 13 | "role": "Developer" 14 | } 15 | ], 16 | "support": { 17 | "issues": "https://github.com/getgrav/grav-plugin-simplesearch/issues", 18 | "irc": "https://chat.getgrav.org", 19 | "forum": "https://discourse.getgrav.org", 20 | "docs": "https://github.com/getgrav/grav-plugin-simplesearch/blob/master/README.md" 21 | }, 22 | "require": { 23 | "php": ">=7.1.3" 24 | }, 25 | "autoload": { 26 | "classmap": [ 27 | "simplesearch.php" 28 | ] 29 | }, 30 | "config": { 31 | "platform": { 32 | "php": "7.1.3" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "9386d6cf08fcab33be418ed5d678b2ce", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=7.1.3" 17 | }, 18 | "platform-dev": [], 19 | "platform-overrides": { 20 | "php": "7.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /css/simplesearch.css: -------------------------------------------------------------------------------- 1 | .search-wrapper .search-input { 2 | width: 80%; 3 | display: inline-block; 4 | } 5 | 6 | .search-submit { 7 | display: inline-block; 8 | border-radius: 4px; 9 | background: #eee; 10 | border: 1px solid #ccc; 11 | vertical-align: top; 12 | } 13 | 14 | .search-submit img { 15 | width: 20px; 16 | vertical-align: middle; 17 | } 18 | 19 | .search-image { 20 | float: left; 21 | } 22 | 23 | .search-item { 24 | margin-left: 130px; 25 | margin-bottom: 50px; 26 | } 27 | 28 | .search-item p { 29 | margin: 0; 30 | } 31 | 32 | .search-title h3 { 33 | margin: 0; 34 | } 35 | 36 | .search-details { 37 | font-size: 13px; 38 | } 39 | 40 | .search-row:last-child hr { 41 | display: none; 42 | } -------------------------------------------------------------------------------- /hebe.json: -------------------------------------------------------------------------------- 1 | { 2 | "project":"grav-plugin-simplesearch", 3 | "platforms":{ 4 | "grav":{ 5 | "nodes":{ 6 | "plugin":[ 7 | { 8 | "source":"/", 9 | "destination":"/user/plugins/simplesearch" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/simplesearch.js: -------------------------------------------------------------------------------- 1 | ((function(){ 2 | if (!Element.prototype.matches) { 3 | Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 4 | } 5 | var findAncestor = function(el, selector) { 6 | while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el, selector))) {} 7 | return el; 8 | }; 9 | 10 | var fields = document.querySelectorAll('input[name="searchfield"][data-search-input]'); 11 | Array.prototype.forEach.call(fields, function(field) { 12 | var form = findAncestor(field, 'form[data-simplesearch-form]'), 13 | min = field.getAttribute('data-min') || false, 14 | location = field.getAttribute('data-search-input'), 15 | separator = field.getAttribute('data-search-separator'); 16 | 17 | if (min) { 18 | var invalid = field.getAttribute('data-search-invalid'); 19 | field.addEventListener('keydown', function() { 20 | field.setCustomValidity(field.value.length >= min ? '' : invalid); 21 | }); 22 | } 23 | 24 | form.addEventListener('submit', function(event) { 25 | event.preventDefault(); 26 | 27 | if (field.checkValidity()) { 28 | window.location.href = location + separator + field.value; 29 | } 30 | }); 31 | }); 32 | })()); 33 | -------------------------------------------------------------------------------- /languages.yaml: -------------------------------------------------------------------------------- 1 | en: 2 | PLUGIN_SIMPLESEARCH: 3 | SEARCH_PLACEHOLDER: 'Search …' 4 | SEARCH_RESULTS: 'Search Results' 5 | SEARCH_RESULTS_SUMMARY_SINGULAR: 'Query: %s found one result' 6 | SEARCH_RESULTS_SUMMARY_PLURAL: 'Query: %s found %s results' 7 | SEARCH_FIELD_MINIMUM_CHARACTERS: 'Please add at least %s characters' 8 | SEARCH_CONTENT: 'Search Content' 9 | RENDERED_CONTENT: 'Rendered Content (Slower)' 10 | RAW_CONTENT: 'Raw Markdown Content (Faster)' 11 | BUILTIN_CSS: 'Use built in CSS' 12 | BUILTIN_CSS_HELP: 'Include the CSS provided by the simplesearch plugin' 13 | BUILTIN_JS: 'Use built in JavaScript' 14 | BUILTIN_JS_HELP: 'Include the JavaScript provided by the simplesearch plugin' 15 | DISPLAY_SEARCH_BUTTON: 'Display Search Button' 16 | DISPLAY_SEARCH_BUTTON_HELP: 'Display a search button near the search field' 17 | IGNORE_ACCENDED_CHARACTERS: 'Ignore accented characters' 18 | IGNORE_ACCENDED_CHARACTERS_HELP: 'If enabled, search terms will match accented characters regardless to their diacritics i.e. search results will show up for "cafe" and "café" no matter how you typed it.' 19 | MIN_QUERY_LENGTH: 'Minimum query length' 20 | MIN_QUERY_LENGTH_HELP: 'The minimum number of characters needed before search can be submitted' 21 | SEARCH_PAGE_ROUTE: 'Route' 22 | SEARCH_PAGE_ROUTE_HELP: 'Default route of the simplesearch plugin' 23 | SEARCH_PAGE_TEMPLATE: 'Template' 24 | SEARCH_PAGE_TEMPLATE_HELP: 'Name of the template for the search results' 25 | CATEGORY_FILTER: 'Category filter' 26 | CATEGORY_FILTER_HELP: 'Comma separated list of category names. Enter "@none" to search in all pages.' 27 | FILTER_COMBINATOR: 'Filter Combinator' 28 | AND_COMBINATOR: 'And - Boolean &&' 29 | OR_COMBINATOR: 'Or - Boolean ||' 30 | SEARCHABLE_TYPES: "Searchable Types" 31 | SEARCHABLE_TYPES_DESCRIPTION: "Title = Search Page Title
Content = Search Page Content
Header = Search Raw Page Headers
Taxonomy = Search Taxonomy" 32 | HEADER_KEYS_IGNORED: Header Keys to Ignore 33 | HEADER_KEYS_IGNORED_HELP: The root-level header keys that should be skipped when searching type "Header" 34 | 35 | ro: 36 | PLUGIN_SIMPLESEARCH: 37 | SEARCH_PLACEHOLDER: "Caută …" 38 | SEARCH_RESULTS: "Rezultatele căutării" 39 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Căutarea: %s a găsit un rezultat" 40 | SEARCH_RESULTS_SUMMARY_PLURAL: "Căutarea: %s a găsit %s rezultate" 41 | 42 | de: 43 | PLUGIN_SIMPLESEARCH: 44 | SEARCH_PLACEHOLDER: "Suche …" 45 | SEARCH_RESULTS: "Suchergebnisse" 46 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Suche: %s fand ein Ergebnis" 47 | SEARCH_RESULTS_SUMMARY_PLURAL: "Suche: %s fand %s Ergebnisse" 48 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Bitte geben Sie mindestens %s Zeichen ein" 49 | SEARCH_VALUE: 'Suchen' 50 | 51 | fr: 52 | PLUGIN_SIMPLESEARCH: 53 | SEARCH_PLACEHOLDER: "Recherche …" 54 | SEARCH_RESULTS: "Résultats de la recherche" 55 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Recherche : Un résultat trouvé pour %s" 56 | SEARCH_RESULTS_SUMMARY_PLURAL: "Recherche : %2$s résultats trouvés pour %1$s" 57 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Veuillez ajouter au moins %s caractères" 58 | 59 | it: 60 | PLUGIN_SIMPLESEARCH: 61 | SEARCH_PLACEHOLDER: "Cerca …" 62 | SEARCH_RESULTS: "Risultati della ricerca" 63 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Ricerca: %s. Trovato un risultato" 64 | SEARCH_RESULTS_SUMMARY_PLURAL: "Ricerca: %s. Trovati %s risultati" 65 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Inserisci almeno %s caratteri" 66 | 67 | zh: 68 | PLUGIN_SIMPLESEARCH: 69 | SEARCH_PLACEHOLDER: "搜索 …" 70 | SEARCH_RESULTS: "搜索结果" 71 | SEARCH_RESULTS_SUMMARY_SINGULAR: "查询: %s 找到 1 个结果" 72 | SEARCH_RESULTS_SUMMARY_PLURAL: "查询: %s 找到 %s 个结果" 73 | 74 | zh-TW: 75 | PLUGIN_SIMPLESEARCH: 76 | SEARCH_PLACEHOLDER: "搜尋 …" 77 | SEARCH_RESULTS: "搜尋結果" 78 | SEARCH_RESULTS_SUMMARY_SINGULAR: "查詢: %s 找到 1 個結果" 79 | SEARCH_RESULTS_SUMMARY_PLURAL: "查詢: %s 找到 %s 個結果" 80 | SEARCH_FIELD_MINIMUM_CHARACTERS: "請輸入至少 %s 個字元" 81 | nl: 82 | PLUGIN_SIMPLESEARCH: 83 | SEARCH_PLACEHOLDER: "Zoeken …" 84 | SEARCH_RESULTS: "Zoek resultaten" 85 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Query: %s is 1 keer gevonden" 86 | SEARCH_RESULTS_SUMMARY_PLURAL: "Query: %s is %s keer gevonden" 87 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Geef minstens %s tekens in" 88 | 89 | hr: 90 | PLUGIN_SIMPLESEARCH: 91 | SEARCH_PLACEHOLDER: "Traži …" 92 | SEARCH_RESULTS: "Rezultati pretrage" 93 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Upit: %s je pronašao jedan rezultat" 94 | SEARCH_RESULTS_SUMMARY_PLURAL: "Upit: %s je pronašao %s rezultata" 95 | 96 | ru: 97 | PLUGIN_SIMPLESEARCH: 98 | SEARCH_PLACEHOLDER: 'Найти …' 99 | SEARCH_RESULTS: 'Результат поиска' 100 | SEARCH_RESULTS_SUMMARY_SINGULAR: 'По запросу: %s найден 1 результат' 101 | SEARCH_RESULTS_SUMMARY_PLURAL: 'По запросу: %s результатов найдено %s' 102 | SEARCH_FIELD_MINIMUM_CHARACTERS: 'Добавьте не менее %s символов' 103 | SEARCH_CONTENT: 'Поиск контента' 104 | RENDERED_CONTENT: 'Обработанный контент (медленнее)' 105 | RAW_CONTENT: 'Необработанный Markdown контент (быстрее)' 106 | BUILTIN_CSS: 'Использовать встроенный CSS' 107 | BUILTIN_CSS_HELP: 'Использовать CSS, предоставленный плагином simplesearch' 108 | BUILTIN_JS: 'Использовать встроенный JavaScript' 109 | BUILTIN_JS_HELP: 'Использовать JavaScript, предоставленный плагином simplesearch' 110 | DISPLAY_SEARCH_BUTTON: 'Показать кнопку поиска' 111 | DISPLAY_SEARCH_BUTTON_HELP: 'Показать кнопку поиска рядом с полем поиска' 112 | IGNORE_ACCENDED_CHARACTERS: 'Игнорировать акцентированные символы' 113 | IGNORE_ACCENDED_CHARACTERS_HELP: 'Если этот параметр включен, поисковые термины будут совпадать с акцентированными символами, независимо от их диакритических знаков, т.е. результаты поиска будут отображаться как "кафе" и "кафе́", независимо от того, как вы их набрали' 114 | MIN_QUERY_LENGTH: 'Минимальная длина запроса' 115 | MIN_QUERY_LENGTH_HELP: 'Минимальное количество символов, необходимое для отправки запроса' 116 | SEARCH_PAGE_ROUTE: 'Маршрут' 117 | SEARCH_PAGE_ROUTE_HELP: 'Маршрут по умолчанию для плагина simplesearch' 118 | SEARCH_PAGE_TEMPLATE: 'Шаблон' 119 | SEARCH_PAGE_TEMPLATE_HELP: 'Имя шаблона для страницы результатов поиска' 120 | CATEGORY_FILTER: 'Фильтр категорий' 121 | CATEGORY_FILTER_HELP: 'Разделенный запятыми список названий категорий. Введите "@none" для поиска на всех страницах' 122 | FILTER_COMBINATOR: 'Фильтр комбинатор' 123 | AND_COMBINATOR: 'И - Булева &&' 124 | OR_COMBINATOR: 'Или - Булева ||' 125 | 126 | uk: 127 | PLUGIN_SIMPLESEARCH: 128 | SEARCH_PLACEHOLDER: 'Знайти …' 129 | SEARCH_RESULTS: 'Результат пошуку' 130 | SEARCH_RESULTS_SUMMARY_SINGULAR: 'На запит: %s знайдено 1 результат' 131 | SEARCH_RESULTS_SUMMARY_PLURAL: 'На запит: %s результатів знайдено %s' 132 | SEARCH_FIELD_MINIMUM_CHARACTERS: 'Введіть не менше %s символів' 133 | SEARCH_CONTENT: 'Пошук контенту' 134 | RENDERED_CONTENT: 'Оброблений контент (повільніше)' 135 | RAW_CONTENT: 'Необроблений Markdown контент (швидше)' 136 | BUILTIN_CSS: 'Використовувати вбудований CSS' 137 | BUILTIN_CSS_HELP: 'Використовувати CSS, наданий плагіном simplesearch' 138 | BUILTIN_JS: 'Використовувати вбудований JavaScript' 139 | BUILTIN_JS_HELP: 'Використовувати JavaScript, наданий плагіном simplesearch' 140 | DISPLAY_SEARCH_BUTTON: 'Показати кнопку пошуку' 141 | DISPLAY_SEARCH_BUTTON_HELP: 'Показати кнопку пошуку поряд з полем пошуку' 142 | IGNORE_ACCENDED_CHARACTERS: 'Ігнорувати акцентовані символи' 143 | IGNORE_ACCENDED_CHARACTERS_HELP: 'Якщо цей параметр включений, пошукові терміни будуть збігатися з акцентованими символами, незалежно від їх діакритичних знаків, тобто результати пошуку будуть відображатися як "кафе" і "кафе́", незалежно від того, як ви їх набрали' 144 | MIN_QUERY_LENGTH: 'Мінімальна довжина запиту' 145 | MIN_QUERY_LENGTH_HELP: 'Мінімальна кількість символів, необхідна для відправлення запиту' 146 | SEARCH_PAGE_ROUTE: 'Маршрут' 147 | SEARCH_PAGE_ROUTE_HELP: 'Маршрут за замовчуванням для плагіна simplesearch' 148 | SEARCH_PAGE_TEMPLATE: 'Шаблон' 149 | SEARCH_PAGE_TEMPLATE_HELP: "Ім'я шаблону для сторінки результатів пошуку" 150 | CATEGORY_FILTER: 'Фільтр категорій' 151 | CATEGORY_FILTER_HELP: 'Розділений комами список назв категорій. Введіть "@none" для пошуку на всіх сторінках' 152 | FILTER_COMBINATOR: 'Фільтр комбінатор' 153 | AND_COMBINATOR: 'І - Булева &&' 154 | OR_COMBINATOR: 'Або - Булева ||' 155 | 156 | es: 157 | PLUGIN_SIMPLESEARCH: 158 | SEARCH_PLACEHOLDER: "Buscar …" 159 | SEARCH_RESULTS: "Resultados de la búsqueda" 160 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Consulta: %s se encontró 1 resultado" 161 | SEARCH_RESULTS_SUMMARY_PLURAL: "Consulta: %s se encontraron %s resultados" 162 | 163 | ja: 164 | PLUGIN_SIMPLESEARCH: 165 | SEARCH_PLACEHOLDER: "検索する …" 166 | SEARCH_RESULTS: "検索結果" 167 | SEARCH_RESULTS_SUMMARY_SINGULAR: "検索 : %s に一つの結果があります。" 168 | SEARCH_RESULTS_SUMMARY_PLURAL: "検索 : %s に %s の結果があります。" 169 | 170 | fa: 171 | PLUGIN_SIMPLESEARCH: 172 | SEARCH_PLACEHOLDER: "جستجو …" 173 | SEARCH_RESULTS: "نتایج جستجو" 174 | SEARCH_RESULTS_SUMMARY_SINGULAR: "جستار: %s یک نتیجه یافت شد" 175 | SEARCH_RESULTS_SUMMARY_PLURAL: "جستار: %s %s نتیجه یافت شد" 176 | 177 | cs: 178 | PLUGIN_SIMPLESEARCH: 179 | SEARCH_PLACEHOLDER: "Vyhledat …" 180 | SEARCH_RESULTS: "Výsledky hledání" 181 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Hledání výrazu '%s' našlo jeden výsledek" 182 | SEARCH_RESULTS_SUMMARY_PLURAL: "Hledání výrazu '%s' našlo %s výsledků" 183 | 184 | pt: 185 | PLUGIN_SIMPLESEARCH: 186 | SEARCH_PLACEHOLDER: "O que você procura?" 187 | SEARCH_RESULTS: "Resultados da pesquisa" 188 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Pesquisa: %s. Foram encontrados 1 resultados" 189 | SEARCH_RESULTS_SUMMARY_PLURAL: "Pesquisa: %s. Foram encontrados %s resultados" 190 | 191 | sv: 192 | PLUGIN_SIMPLESEARCH: 193 | SEARCH_PLACEHOLDER: "Sök …" 194 | SEARCH_RESULTS: "Sökresultat" 195 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Sökning: %s hittade ett resultat" 196 | SEARCH_RESULTS_SUMMARY_PLURAL: "Sökning: %s hittade %s resultat" 197 | 198 | da: 199 | PLUGIN_SIMPLESEARCH: 200 | SEARCH_PLACEHOLDER: "Søg …" 201 | SEARCH_RESULTS: "Søgeresultat" 202 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Søgning: %s fandt et resultat" 203 | SEARCH_RESULTS_SUMMARY_PLURAL: "Søgning: %s fandt %s resultater" 204 | 205 | kk: 206 | PLUGIN_SIMPLESEARCH: 207 | SEARCH_PLACEHOLDER: "іздеу …" 208 | SEARCH_RESULTS: "Іздеу нәтижесі" 209 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Сұраныс бойынша: %s табылды 1" 210 | SEARCH_RESULTS_SUMMARY_PLURAL: "Сұраныс бойынша: %s табылды %s" 211 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Кемінде %s таңба қосу" 212 | 213 | pl: 214 | PLUGIN_SIMPLESEARCH: 215 | SEARCH_PLACEHOLDER: "Szukaj…" 216 | SEARCH_RESULTS: "Wyniki wyszukiwania" 217 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Znaleziono jeden wynik dla frazy %s." 218 | SEARCH_RESULTS_SUMMARY_PLURAL: "Znaleziono %2$s wyników dla frazy %1$s." 219 | SEARCH_FIELD_MINIMUM_CHARACTERS: "Fraza musi składać się z minimum %s znaków." 220 | 221 | gl: 222 | PLUGIN_SIMPLESEARCH: 223 | SEARCH_PLACEHOLDER: "Procurar …" 224 | SEARCH_RESULTS: "Resultados da procura" 225 | SEARCH_RESULTS_SUMMARY_SINGULAR: "Consulta: %s atopouse 1 resultado" 226 | SEARCH_RESULTS_SUMMARY_PLURAL: "Consulta: %s atopáronse %s resultados" -------------------------------------------------------------------------------- /pages/simplesearch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Search Results 3 | order_by: date 4 | order_dir: desc 5 | template: simplesearch_results 6 | --- 7 | 8 | -------------------------------------------------------------------------------- /simplesearch.php: -------------------------------------------------------------------------------- 1 | ['onPluginsInitialized', 0], 38 | 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 39 | 'onGetPageTemplates' => ['onGetPageTemplates', 0], 40 | ]; 41 | } 42 | 43 | /** 44 | * Add page template types. (for Admin plugin) 45 | * 46 | * @return void 47 | */ 48 | public function onGetPageTemplates(Event $event) 49 | { 50 | /** @var Types $types */ 51 | $types = $event->types; 52 | $types->scanTemplates('plugins://simplesearch/templates'); 53 | } 54 | 55 | 56 | /** 57 | * Add current directory to twig lookup paths. 58 | * 59 | * @return void 60 | */ 61 | public function onTwigTemplatePaths() 62 | { 63 | $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; 64 | } 65 | 66 | /** 67 | * Enable search only if url matches to the configuration. 68 | * 69 | * @return void 70 | */ 71 | public function onPluginsInitialized() 72 | { 73 | if ($this->isAdmin()) { 74 | return; 75 | } 76 | 77 | $this->enable([ 78 | 'onPagesInitialized' => ['onPagesInitialized', 0], 79 | 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] 80 | ]); 81 | } 82 | 83 | 84 | /** 85 | * Build search results. 86 | * 87 | * @return void 88 | */ 89 | public function onPagesInitialized() 90 | { 91 | $page = $this->grav['page']; 92 | 93 | $route = null; 94 | if (isset($page->header()->simplesearch['route'])) { 95 | $route = $page->header()->simplesearch['route']; 96 | 97 | // Support `route: '@self'` syntax 98 | if ($route === '@self') { 99 | $route = $page->route(); 100 | $page->header()->simplesearch['route'] = $route; 101 | } 102 | } 103 | 104 | // If a page exists merge the configs 105 | if (isset($page)) { 106 | $this->config->set('plugins.simplesearch', $this->mergeConfig($page)); 107 | } 108 | 109 | /** @var Uri $uri */ 110 | $uri = $this->grav['uri']; 111 | $query = $uri->param('query') ?: $uri->query('query'); 112 | $route = $this->config->get('plugins.simplesearch.route'); 113 | 114 | // performance check for route 115 | if (!($route && $route == $uri->path())) { 116 | return; 117 | } 118 | 119 | // set the template is not set in the page header (the page header setting takes precedence over the plugin config setting) 120 | if (!isset($page->header()->template)) { 121 | $template_override = $this->config->get('plugins.simplesearch.template', 'simplesearch_results'); 122 | $page->template($template_override); 123 | } 124 | 125 | // Explode query into multiple strings. Drop empty values 126 | // @phpstan-ignore-next-line 127 | $this->query = array_filter(array_filter(explode(',', $query), 'trim'), 'strlen'); 128 | 129 | /** @var Taxonomy $taxonomy_map */ 130 | $taxonomy_map = $this->grav['taxonomy']; 131 | $taxonomies = []; 132 | $find_taxonomy = []; 133 | 134 | $filters = (array)$this->config->get('plugins.simplesearch.filters'); 135 | $operator = $this->config->get('plugins.simplesearch.filter_combinator', 'and'); 136 | $new_approach = false; 137 | 138 | // if @none found, skip processing taxonomies 139 | $should_process = true; 140 | if (is_array($filters)) { 141 | $the_filter = reset($filters); 142 | 143 | if (is_array($the_filter)) { 144 | if (in_array(reset($the_filter), ['@none', 'none@'])) { 145 | $should_process = false; 146 | } 147 | } 148 | } 149 | 150 | if (!$should_process || !$filters || $query === false || (count($filters) === 1 && !reset($filters))) { 151 | /** @var Pages $pages */ 152 | $pages = $this->grav['pages']; 153 | $this->collection = $pages->all(); 154 | } else { 155 | 156 | foreach ($filters as $key => $filter) { 157 | // flatten item if it's wrapped in an array 158 | if (is_int($key)) { 159 | if (is_array($filter)) { 160 | $key = key($filter); 161 | $filter = $filter[$key]; 162 | } else { 163 | $key = $filter; 164 | } 165 | } 166 | 167 | // see if the filter uses the new 'items-type' syntax 168 | if ($key === '@self' || $key === 'self@') { 169 | $new_approach = true; 170 | } elseif ($key === '@taxonomy' || $key === 'taxonomy@') { 171 | $taxonomies = $filter === false ? false : array_merge($taxonomies, (array)$filter); 172 | } else { 173 | $find_taxonomy[$key] = $filter; 174 | } 175 | } 176 | 177 | if ($new_approach) { 178 | $params = $page->header()->content; 179 | $params['query'] = $this->config->get('plugins.simplesearch.query'); 180 | $this->collection = $page->collection($params, false); 181 | } else { 182 | $this->collection = new Collection(); 183 | $this->collection->append($taxonomy_map->findTaxonomy($find_taxonomy, $operator)->toArray()); 184 | } 185 | } 186 | 187 | //Drop unpublished pages, but do not drop unroutable pages right now to be able to search modular pages which are unroutable per se 188 | $this->collection->published(); 189 | /** @var Collection $modularPageCollection */ 190 | $modularPageCollection = $this->collection->copy(); 191 | //Get published modular pages 192 | $modularPageCollection->modular(); 193 | foreach ($modularPageCollection as $cpage) { 194 | $parent = $cpage->parent(); 195 | if (!$parent || !$parent->published()) { 196 | $modularPageCollection->remove($cpage); 197 | } 198 | } 199 | //Drop unroutable pages 200 | $this->collection->routable(); 201 | //Add modular pages again 202 | $this->collection->merge($modularPageCollection); 203 | 204 | //Allow for integration to SimpleSearch collection 205 | $this->grav->fireEvent('onSimpleSearchCollection', new Event(['collection' => $this->collection])); 206 | 207 | //Check if user has permission to view page 208 | if ($this->grav['config']->get('plugins.login.enabled')) { 209 | $this->collection = $this->checkForPermissions($this->collection); 210 | } 211 | $extras = []; 212 | 213 | if ($query) { 214 | foreach ($this->collection as $cpage) { 215 | 216 | $header = $cpage->header(); 217 | if (isset($header->simplesearch['process']) && $header->simplesearch['process'] === false) { 218 | $this->collection->remove($cpage); 219 | continue; 220 | } 221 | 222 | foreach ($this->query as $query) { 223 | $query = trim($query); 224 | 225 | if ($this->notFound($query, $cpage, $taxonomies)) { 226 | $this->collection->remove($cpage); 227 | continue; 228 | } 229 | 230 | if ($cpage->modular()) { 231 | $this->collection->remove($cpage); 232 | $parent = $cpage->parent(); 233 | $extras[$parent->path()] = ['slug' => $parent->slug()]; 234 | } 235 | 236 | } 237 | } 238 | } 239 | 240 | if (!empty($extras)) { 241 | $this->collection->append($extras); 242 | } 243 | 244 | // use a configured sorting order if not already done 245 | if (!$new_approach) { 246 | $this->collection = $this->collection->order( 247 | $this->config->get('plugins.simplesearch.order.by'), 248 | $this->config->get('plugins.simplesearch.order.dir') 249 | ); 250 | } 251 | 252 | // Display simplesearch page if no page was found for the current route 253 | $pages = $this->grav['pages']; 254 | $page = $pages->dispatch($this->config->get('plugins.simplesearch.route', '/search'), true); 255 | if (!isset($page)) { 256 | // create the search page 257 | $page = new Page; 258 | $page->init(new \SplFileInfo(__DIR__ . '/pages/simplesearch.md')); 259 | 260 | // override the template is set in the plugin config (the plugin config setting takes precedence over the page header setting) 261 | $template_override = $this->config->get('plugins.simplesearch.template'); 262 | if (isset($template_override)) { 263 | $page->template($template_override); 264 | } 265 | 266 | // fix RuntimeException: Cannot override frozen service "page" issue 267 | unset($this->grav['page']); 268 | 269 | $this->grav['page'] = $page; 270 | } 271 | } 272 | 273 | /** 274 | * Filter the pages, and return only the pages the user has access to. 275 | * Implementation based on Login Plugin authorizePage() function. 276 | * 277 | * @param Collection $collection 278 | * @return Collection 279 | */ 280 | public function checkForPermissions($collection) 281 | { 282 | $user = $this->grav['user']; 283 | $returnCollection = new Collection(); 284 | foreach ($collection as $page) { 285 | 286 | $header = $page->header(); 287 | $rules = isset($header->access) ? (array)$header->access : []; 288 | 289 | if ($this->config->get('plugins.login.parent_acl')) { 290 | // If page has no ACL rules, use its parent's rules 291 | if (!$rules) { 292 | $parent = $page->parent(); 293 | while (!$rules and $parent) { 294 | $header = $parent->header(); 295 | $rules = isset($header->access) ? (array)$header->access : []; 296 | $parent = $parent->parent(); 297 | } 298 | } 299 | } 300 | 301 | // Continue to the page if it has no ACL rules. 302 | if (!$rules) { 303 | $returnCollection[$page->path()] = ['slug' => $page->slug()]; 304 | } else { 305 | // Continue to the page if user is authorized to access the page. 306 | foreach ($rules as $rule => $value) { 307 | if (is_array($value)) { 308 | foreach ($value as $nested_rule => $nested_value) { 309 | if ($user->authorize($rule . '.' . $nested_rule) == $nested_value) { 310 | $returnCollection[$page->path()] = ['slug' => $page->slug()]; 311 | break; 312 | } 313 | } 314 | } else { 315 | if ($user->authorize($rule) == $value) { 316 | $returnCollection[$page->path()] = ['slug' => $page->slug()]; 317 | break; 318 | } 319 | } 320 | } 321 | } 322 | } 323 | return $returnCollection; 324 | } 325 | 326 | /** 327 | * @param string $query 328 | * @param Page $page 329 | * @param array|false $taxonomies 330 | * @return bool 331 | */ 332 | private function notFound($query, $page, $taxonomies) 333 | { 334 | $searchable_types = $search_content = $this->config->get('plugins.simplesearch.searchable_types'); 335 | $results = true; 336 | $search_content = $this->config->get('plugins.simplesearch.search_content'); 337 | 338 | $result = null; 339 | foreach ($searchable_types as $type => $enabled) { 340 | if ($type === 'title' && $enabled) { 341 | $result = $this->matchText(strip_tags($page->title()), $query) === false; 342 | } elseif ($type === 'taxonomy' && $enabled) { 343 | if ($taxonomies === false) { 344 | continue; 345 | } 346 | $page_taxonomies = $page->taxonomy(); 347 | $taxonomy_match = false; 348 | foreach ((array)$page_taxonomies as $taxonomy => $values) { 349 | // if taxonomies filter set, make sure taxonomy filter is valid 350 | if (!is_array($values) || (is_array($taxonomies) && !empty($taxonomies) && !in_array($taxonomy, $taxonomies))) { 351 | continue; 352 | } 353 | 354 | $taxonomy_values = implode('|', $values); 355 | if ($this->matchText($taxonomy_values, $query) !== false) { 356 | $taxonomy_match = true; 357 | break; 358 | } 359 | } 360 | $result = !$taxonomy_match; 361 | } elseif ($type === 'content' && $enabled) { 362 | if ($search_content === 'raw') { 363 | $content = $page->rawMarkdown(); 364 | } else { 365 | $content = $page->content(); 366 | } 367 | $result = $this->matchText(strip_tags($content), $query) === false; 368 | } elseif ($type === 'header' && $enabled) { 369 | $header = (array) $page->header(); 370 | $content = $this->getArrayValues($header); 371 | $result = $this->matchText(strip_tags($content), $query) === false; 372 | } 373 | $results = (bool)$result; 374 | if ($results === false) { 375 | break; 376 | } 377 | } 378 | return $results; 379 | } 380 | 381 | /** 382 | * @param string $haystack 383 | * @param string $needle 384 | * @return false|int 385 | */ 386 | private function matchText($haystack, $needle) 387 | { 388 | if ($this->config->get('plugins.simplesearch.ignore_accented_characters')) { 389 | setlocale(LC_ALL, 'en_US'); 390 | try { 391 | $result = mb_stripos(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $haystack), iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $needle)); 392 | } catch (\Exception $e) { 393 | $result = mb_stripos($haystack, $needle); 394 | } 395 | setlocale(LC_ALL, ''); 396 | return $result; 397 | } 398 | 399 | return mb_stripos($haystack, $needle); 400 | } 401 | 402 | /** 403 | * Set needed variables to display the search results. 404 | * 405 | * @return void 406 | */ 407 | public function onTwigSiteVariables() 408 | { 409 | $twig = $this->grav['twig']; 410 | 411 | if ($this->query) { 412 | $twig->twig_vars['query'] = implode(', ', $this->query); 413 | $twig->twig_vars['search_results'] = $this->collection; 414 | } 415 | 416 | if ($this->config->get('plugins.simplesearch.built_in_css')) { 417 | $this->grav['assets']->add('plugin://simplesearch/css/simplesearch.css'); 418 | } 419 | 420 | if ($this->config->get('plugins.simplesearch.built_in_js')) { 421 | $this->grav['assets']->addJs('plugin://simplesearch/js/simplesearch.js', ['group' => 'bottom']); 422 | } 423 | } 424 | 425 | /** 426 | * @param array $array 427 | * @param array|null $ignore_keys 428 | * @param int $level 429 | * @return string 430 | */ 431 | protected function getArrayValues($array, $ignore_keys = null, $level = 0) { 432 | $output = ''; 433 | 434 | if (is_null($ignore_keys)) { 435 | $config = $this->config(); 436 | $ignore_keys = $config['header_keys_ignored'] ?? ['title', 'taxonomy','content', 'form', 'forms', 'media_order']; 437 | } 438 | foreach ($array as $key => $child) { 439 | 440 | if ($level === 0 && in_array($key, $ignore_keys, true)) { 441 | continue; 442 | } 443 | 444 | if (is_array($child)) { 445 | $output .= " " . $this->getArrayValues($child, $ignore_keys, $level + 1); 446 | } else { 447 | $output .= " " . $child; 448 | } 449 | 450 | } 451 | return trim($output); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /simplesearch.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | built_in_css: true 3 | built_in_js: true 4 | display_button: false 5 | min_query_length: 3 6 | route: /search 7 | search_content: rendered 8 | template: simplesearch_results 9 | filters: 10 | category: 11 | filter_combinator: and 12 | ignore_accented_characters: false 13 | order: 14 | by: date 15 | dir: desc 16 | searchable_types: 17 | title: true 18 | content: true 19 | taxonomy: true 20 | header: false 21 | header_keys_ignored: [ 'title', 'taxonomy','content', 'form', 'forms', 'media_order' ] -------------------------------------------------------------------------------- /templates/partials/simplesearch_base.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'partials/base.html.twig' %} 2 | -------------------------------------------------------------------------------- /templates/partials/simplesearch_item.html.twig: -------------------------------------------------------------------------------- 1 |
2 | 3 | {% set banner = page.media.images|first %} 4 | 5 | {% if banner %} 6 |
7 | {{ banner.cropZoom(100,100).html|raw }} 8 |
9 | {% endif %} 10 |
11 |
12 |

{{ page.title }}

13 |
14 | 15 |
16 | {{ page.date|date(config.system.pages.dateformat.short) }} 17 |
18 | 19 |

{{ page.summary|raw }}

20 | 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /templates/partials/simplesearch_searchbox.html.twig: -------------------------------------------------------------------------------- 1 | {% set min_chars = config.get('plugins.simplesearch.min_query_length', 3) %} 2 |
3 |
4 | 0 %} data-min="{{- min_chars -}}" {% endif %} 10 | required 11 | placeholder="{{"PLUGIN_SIMPLESEARCH.SEARCH_PLACEHOLDER"|t}}" 12 | value="{{ query|e }}" 13 | data-search-invalid="{{ "PLUGIN_SIMPLESEARCH.SEARCH_FIELD_MINIMUM_CHARACTERS"|t(min_chars)|raw }}" 14 | data-search-separator="{{ config.system.param_sep }}" 15 | data-search-input="{{ base_url }}{{ config.plugins.simplesearch.route == '@self' ? '' : (config.plugins.simplesearch.route == '/' ? '' : config.plugins.simplesearch.route) }}/query" 16 | autofocus 17 | /> 18 | {% if config.plugins.simplesearch.display_button %} 19 | 22 | {% endif %} 23 |
24 |
25 | -------------------------------------------------------------------------------- /templates/simplesearch_results.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'partials/simplesearch_base.html.twig' %} 2 | 3 | {% block content %} 4 |
5 |

{{ "PLUGIN_SIMPLESEARCH.SEARCH_RESULTS"|t }}

6 |
7 | {% include 'partials/simplesearch_searchbox.html.twig' %} 8 |
9 |

10 | {% if query %} 11 | {% set count = search_results ? search_results.count : 0 %} 12 | {% if count is same as( 1 ) %} 13 | {{ "PLUGIN_SIMPLESEARCH.SEARCH_RESULTS_SUMMARY_SINGULAR"|t(query|e)|raw }} 14 | {% else %} 15 | {{ "PLUGIN_SIMPLESEARCH.SEARCH_RESULTS_SUMMARY_PLURAL"|t(query|e, count)|raw }} 16 | {% endif %} 17 | {% endif %} 18 |

19 | {% for page in search_results %} 20 | {% include 'partials/simplesearch_item.html.twig' with {'page': page} %} 21 | {% endfor %} 22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /templates/simplesearch_results.json.twig: -------------------------------------------------------------------------------- 1 | {"results":[ 2 | {%- for search_result in search_results -%} 3 | {{- search_result.route|json_encode|raw -}}{{ not loop.last ? ',' }} 4 | {%- endfor -%} 5 | ]} 6 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $baseDir . '/simplesearch.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInitca0f44aa14891d4b4dfc9b834c0e0763::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/../..' . '/simplesearch.php', 11 | ); 12 | 13 | public static function getInitializer(ClassLoader $loader) 14 | { 15 | return \Closure::bind(function () use ($loader) { 16 | $loader->classMap = ComposerStaticInitca0f44aa14891d4b4dfc9b834c0e0763::$classMap; 17 | 18 | }, null, ClassLoader::class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [] 2 | --------------------------------------------------------------------------------