|
@@ -0,0 +1,370 @@
|
|
|
|
+<?php
|
|
|
|
+
|
|
|
|
+namespace Grav\Plugin;
|
|
|
|
+
|
|
|
|
+use Grav\Common\Config\Config;
|
|
|
|
+use Grav\Common\Data\Data;
|
|
|
|
+use Grav\Common\Page\Collection;
|
|
|
|
+use Grav\Common\Page\Page;
|
|
|
|
+use Grav\Common\Page\Types;
|
|
|
|
+use Grav\Common\Plugin;
|
|
|
|
+use Grav\Common\Taxonomy;
|
|
|
|
+use Grav\Common\Uri;
|
|
|
|
+use Grav\Common\Utils;
|
|
|
|
+use RocketTheme\Toolbox\Event\Event;
|
|
|
|
+
|
|
|
|
+class SimplesearchPlugin extends Plugin
|
|
|
|
+{
|
|
|
|
+
|
|
|
|
+ * @var array
|
|
|
|
+ */
|
|
|
|
+ protected $query;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @var string
|
|
|
|
+ */
|
|
|
|
+ protected $query_id;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @var Collection
|
|
|
|
+ */
|
|
|
|
+ protected $collection;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @return array
|
|
|
|
+ */
|
|
|
|
+ public static function getSubscribedEvents()
|
|
|
|
+ {
|
|
|
|
+ return [
|
|
|
|
+ 'onPluginsInitialized' => ['onPluginsInitialized', 0],
|
|
|
|
+ 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
|
|
|
|
+ 'onGetPageTemplates' => ['onGetPageTemplates', 0],
|
|
|
|
+ ];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Add page template types. (for Admin plugin)
|
|
|
|
+ */
|
|
|
|
+ public function onGetPageTemplates(Event $event)
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ $types = $event->types;
|
|
|
|
+ $types->scanTemplates('plugins://simplesearch/templates');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Add current directory to twig lookup paths.
|
|
|
|
+ */
|
|
|
|
+ public function onTwigTemplatePaths()
|
|
|
|
+ {
|
|
|
|
+ $this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Enable search only if url matches to the configuration.
|
|
|
|
+ */
|
|
|
|
+ public function onPluginsInitialized()
|
|
|
|
+ {
|
|
|
|
+ if ($this->isAdmin()) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $this->enable([
|
|
|
|
+ 'onPagesInitialized' => ['onPagesInitialized', 0],
|
|
|
|
+ 'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
|
|
|
|
+ ]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Build search results.
|
|
|
|
+ */
|
|
|
|
+ public function onPagesInitialized()
|
|
|
|
+ {
|
|
|
|
+ $page = $this->grav['page'];
|
|
|
|
+
|
|
|
|
+ $route = null;
|
|
|
|
+ if (isset($page->header()->simplesearch['route'])) {
|
|
|
|
+ $route = $page->header()->simplesearch['route'];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if ($route === '@self') {
|
|
|
|
+ $route = $page->route();
|
|
|
|
+ $page->header()->simplesearch['route'] = $route;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if ($page) {
|
|
|
|
+ $this->config->set('plugins.simplesearch', $this->mergeConfig($page));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $uri = $this->grav['uri'];
|
|
|
|
+ $query = $uri->param('query') ?: $uri->query('query');
|
|
|
|
+ $route = $this->config->get('plugins.simplesearch.route');
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!($route && $route == $uri->path())) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $this->query = array_filter(array_filter(explode(',', $query), 'trim'), 'strlen');
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $taxonomy_map = $this->grav['taxonomy'];
|
|
|
|
+ $taxonomies = [];
|
|
|
|
+ $find_taxonomy = [];
|
|
|
|
+
|
|
|
|
+ $filters = (array)$this->config->get('plugins.simplesearch.filters');
|
|
|
|
+ $operator = $this->config->get('plugins.simplesearch.filter_combinator', 'and');
|
|
|
|
+ $new_approach = false;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $should_process = true;
|
|
|
|
+ if (is_array($filters)) {
|
|
|
|
+ $the_filter = reset($filters);
|
|
|
|
+
|
|
|
|
+ if (is_array($the_filter)) {
|
|
|
|
+ if (in_array(reset($the_filter), ['@none', 'none@'])) {
|
|
|
|
+ $should_process = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!$should_process || !$filters || $query === false || (count($filters) == 1 && !reset($filters))) {
|
|
|
|
+
|
|
|
|
+ $pages = $this->grav['pages'];
|
|
|
|
+ $this->collection = $pages->all();
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ foreach ($filters as $key => $filter) {
|
|
|
|
+
|
|
|
|
+ if (is_int($key)) {
|
|
|
|
+ if (is_array($filter)) {
|
|
|
|
+ $key = key($filter);
|
|
|
|
+ $filter = $filter[$key];
|
|
|
|
+ } else {
|
|
|
|
+ $key = $filter;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if ($key === '@self' || $key === 'self@') {
|
|
|
|
+ $new_approach = true;
|
|
|
|
+ } elseif ($key === '@taxonomy' || $key === 'taxonomy@') {
|
|
|
|
+ $taxonomies = $filter === false ? false : array_merge($taxonomies, (array)$filter);
|
|
|
|
+ } else {
|
|
|
|
+ $find_taxonomy[$key] = $filter;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($new_approach) {
|
|
|
|
+ $params = $page->header()->content;
|
|
|
|
+ $params['query'] = $this->config->get('plugins.simplesearch.query');
|
|
|
|
+ $this->collection = $page->collection($params, false);
|
|
|
|
+ } else {
|
|
|
|
+ $this->collection = new Collection();
|
|
|
|
+ $this->collection->append($taxonomy_map->findTaxonomy($find_taxonomy, $operator)->toArray());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $this->collection->published()->routable();
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if ($this->grav['config']->get('plugins.login.enabled')) {
|
|
|
|
+ $this->collection = $this->checkForPermissions($this->collection);
|
|
|
|
+ }
|
|
|
|
+ $extras = [];
|
|
|
|
+
|
|
|
|
+ if ($query) {
|
|
|
|
+ foreach ($this->collection as $cpage) {
|
|
|
|
+ foreach ($this->query as $query) {
|
|
|
|
+ $query = trim($query);
|
|
|
|
+
|
|
|
|
+ if ($this->notFound($query, $cpage, $taxonomies)) {
|
|
|
|
+ $this->collection->remove($cpage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($cpage->modular()) {
|
|
|
|
+ $this->collection->remove($cpage);
|
|
|
|
+ $parent = $cpage->parent();
|
|
|
|
+ $extras[$parent->path()] = ['slug' => $parent->slug()];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!empty($extras)) {
|
|
|
|
+ $this->collection->append($extras);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!$new_approach) {
|
|
|
|
+ $this->collection = $this->collection->order(
|
|
|
|
+ $this->config->get('plugins.simplesearch.order.by'),
|
|
|
|
+ $this->config->get('plugins.simplesearch.order.dir')
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!isset($page->header()->simplesearch)) {
|
|
|
|
+
|
|
|
|
+ $page = new Page;
|
|
|
|
+ $page->init(new \SplFileInfo(__DIR__ . '/pages/simplesearch.md'));
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ $template_override = $this->config->get('plugins.simplesearch.template');
|
|
|
|
+ if ($template_override) {
|
|
|
|
+ $page->template($template_override);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ unset($this->grav['page']);
|
|
|
|
+
|
|
|
|
+ $this->grav['page'] = $page;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Filter the pages, and return only the pages the user has access to.
|
|
|
|
+ * Implementation based on Login Plugin authorizePage() function.
|
|
|
|
+ */
|
|
|
|
+ public function checkForPermissions($collection)
|
|
|
|
+ {
|
|
|
|
+ $user = $this->grav['user'];
|
|
|
|
+ $returnCollection = new Collection();
|
|
|
|
+ foreach ($collection as $page) {
|
|
|
|
+
|
|
|
|
+ $header = $page->header();
|
|
|
|
+ $rules = isset($header->access) ? (array)$header->access : [];
|
|
|
|
+
|
|
|
|
+ if ($this->config->get('plugins.login.parent_acl')) {
|
|
|
|
+
|
|
|
|
+ if (!$rules) {
|
|
|
|
+ $parent = $page->parent();
|
|
|
|
+ while (!$rules and $parent) {
|
|
|
|
+ $header = $parent->header();
|
|
|
|
+ $rules = isset($header->access) ? (array)$header->access : [];
|
|
|
|
+ $parent = $parent->parent();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!$rules) {
|
|
|
|
+ $returnCollection[$page->path()] = ['slug' => $page->slug()];
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ foreach ($rules as $rule => $value) {
|
|
|
|
+ if (is_array($value)) {
|
|
|
|
+ foreach ($value as $nested_rule => $nested_value) {
|
|
|
|
+ if ($user->authorize($rule . '.' . $nested_rule) == $nested_value) {
|
|
|
|
+ $returnCollection[$page->path()] = ['slug' => $page->slug()];
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if ($user->authorize($rule) == $value) {
|
|
|
|
+ $returnCollection[$page->path()] = ['slug' => $page->slug()];
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return $returnCollection;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param $query
|
|
|
|
+ * @param Page $page
|
|
|
|
+ * @param $taxonomies
|
|
|
|
+ * @return bool
|
|
|
|
+ */
|
|
|
|
+ private function notFound($query, $page, $taxonomies)
|
|
|
|
+ {
|
|
|
|
+ $searchable_types = ['title', 'content', 'taxonomy'];
|
|
|
|
+ $results = true;
|
|
|
|
+ $search_content = $this->config->get('plugins.simplesearch.search_content');
|
|
|
|
+
|
|
|
|
+ foreach ($searchable_types as $type) {
|
|
|
|
+ if ($type === 'title') {
|
|
|
|
+ $result = $this->matchText(strip_tags($page->title()), $query) === false;
|
|
|
|
+ } elseif ($type === 'taxonomy') {
|
|
|
|
+ if ($taxonomies === false) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ $page_taxonomies = $page->taxonomy();
|
|
|
|
+ $taxonomy_match = false;
|
|
|
|
+ foreach ((array)$page_taxonomies as $taxonomy => $values) {
|
|
|
|
+
|
|
|
|
+ if (!is_array($values) || (is_array($taxonomies) && !empty($taxonomies) && !in_array($taxonomy, $taxonomies))) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $taxonomy_values = implode('|', $values);
|
|
|
|
+ if ($this->matchText($taxonomy_values, $query) !== false) {
|
|
|
|
+ $taxonomy_match = true;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ $result = !$taxonomy_match;
|
|
|
|
+ } else {
|
|
|
|
+ if ($search_content == 'raw') {
|
|
|
|
+ $content = $page->rawMarkdown();
|
|
|
|
+ } else {
|
|
|
|
+ $content = $page->content();
|
|
|
|
+ }
|
|
|
|
+ $result = $this->matchText(strip_tags($content), $query) === false;
|
|
|
|
+ }
|
|
|
|
+ $results = $results && $result;
|
|
|
|
+ if ($results === false) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return $results;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private function matchText($haystack, $needle)
|
|
|
|
+ {
|
|
|
|
+ if ($this->config->get('plugins.simplesearch.ignore_accented_characters')) {
|
|
|
|
+ setlocale(LC_ALL, 'en_US');
|
|
|
|
+ try {
|
|
|
|
+ $result = mb_stripos(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $haystack), iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $needle));
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
+ $result = mb_stripos($haystack, $needle);
|
|
|
|
+ }
|
|
|
|
+ setlocale(LC_ALL, '');
|
|
|
|
+ return $result;
|
|
|
|
+ } else {
|
|
|
|
+ return mb_stripos($haystack, $needle);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * Set needed variables to display the search results.
|
|
|
|
+ */
|
|
|
|
+ public function onTwigSiteVariables()
|
|
|
|
+ {
|
|
|
|
+ $twig = $this->grav['twig'];
|
|
|
|
+
|
|
|
|
+ if ($this->query) {
|
|
|
|
+ $twig->twig_vars['query'] = implode(', ', $this->query);
|
|
|
|
+ $twig->twig_vars['search_results'] = $this->collection;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($this->config->get('plugins.simplesearch.built_in_css')) {
|
|
|
|
+ $this->grav['assets']->add('plugin://simplesearch/css/simplesearch.css');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($this->config->get('plugins.simplesearch.built_in_js')) {
|
|
|
|
+ $this->grav['assets']->addJs('plugin://simplesearch/js/simplesearch.js', ['group' => 'bottom']);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|