SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Controllers/AdvancedSearchController.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Controllers;
  15.  
  16. use Exception;
  17.  
  18. use Kohana;
  19.  
  20. use Sprout\Helpers\Enc;
  21. use Sprout\Helpers\FrontEndEntrance;
  22. use Sprout\Helpers\FrontEndSearch;
  23. use Sprout\Helpers\Navigation;
  24. use Sprout\Helpers\Pdb;
  25. use Sprout\Helpers\Register;
  26. use Sprout\Helpers\Search;
  27. use Sprout\Helpers\SearchHandler;
  28. use Sprout\Helpers\Tags;
  29. use Sprout\Helpers\TreenodeFrontendMatcher;
  30. use Sprout\Helpers\View;
  31.  
  32.  
  33. /**
  34. * - No description yet -
  35. **/
  36. class AdvancedSearchController extends Controller implements FrontEndEntrance
  37. {
  38.  
  39. /**
  40.   * Constructor
  41.   **/
  42. public function __construct()
  43. {
  44. parent::__construct();
  45. }
  46.  
  47.  
  48. /**
  49.   * Acts as the entrance-point of the controller.
  50.   * Redirects to the target page
  51.   **/
  52. public function entrance($argument)
  53. {
  54. $this->index();
  55. }
  56.  
  57. /**
  58.   * Return an array of valid arguments to the entrance of this controller.
  59.   *
  60.   * @return array The valid arguments, in key-value pairs
  61.   **/
  62. public function _getEntranceArguments()
  63. {
  64. return array('form' => 'Search form');
  65. }
  66.  
  67.  
  68. /**
  69.   * Does the actual search
  70.   **/
  71. public function index()
  72. {
  73. Navigation::setPageNodeMatcher(new TreenodeFrontendMatcher('advanced_search', 'form'));
  74.  
  75. $view = $this->doSearch();
  76.  
  77. // Page title
  78. $page_title = 'Search';
  79. $page_node = Navigation::matchedNode();
  80. if ($page_node) {
  81. $page_title = $page_node->getNavigationName();
  82. }
  83.  
  84. // Prepare the view
  85. $page_view = new View('skin/inner');
  86. $page_view->page_title = $page_title;
  87. $page_view->main_content = $view;
  88. $page_view->controller_name = 'user';
  89.  
  90. echo $page_view->render();
  91. }
  92.  
  93.  
  94. /**
  95.   * Does the actual search. Returns HTML
  96.   **/
  97. private function doSearch()
  98. {
  99.  
  100.  
  101. // Build typelist
  102. $config_handlers = Register::getSearchHandlers();
  103. $avail_types = array();
  104. foreach ($config_handlers as $ch) {
  105. if (! $ch instanceof SearchHandler) throw new Exception("Invalid SearchHandler registered");
  106.  
  107. $avail_types[$ch->getMainTable()] = ucwords($ch->getMainTable());
  108. }
  109.  
  110.  
  111. // Check input GET
  112. if (! isset($_GET['page'])) $_GET['page'] = 1;
  113. $_GET['page'] = (int) $_GET['page'];
  114.  
  115. $_GET['q'] = trim(Enc::cleanfunky(@$_GET['q']));
  116. $_GET['tag'] = trim(preg_replace('/[^-a-z0-9 ,]/', '', @$_GET['tag']));
  117. $_GET['date'] = trim(Enc::cleanfunky(@$_GET['date']));
  118.  
  119. if (empty($_GET['q_type'])) $_GET['q_type'] = 'or';
  120. if (empty($_GET['tag_type'])) $_GET['tag_type'] = 'or';
  121.  
  122. if (empty($_GET['type']) or !is_array($_GET['type'])) {
  123. $_GET['type'] = [];
  124. }
  125. foreach ($_GET['type'] as $idx => $val) {
  126. if (!isset($avail_types[$val])) unset($_GET['type'][$idx]);
  127. }
  128.  
  129. if (empty($_GET['type'])) {
  130. $_GET['type'] = array_keys($avail_types);
  131. }
  132.  
  133.  
  134. $srchform = new View('sprout/advanced_search_form');
  135. $srchform->avail_types = $avail_types;
  136. $srchform = $srchform->render();
  137.  
  138.  
  139. if (!$_GET['q'] and !$_GET['tag'] and !$_GET['date']) {
  140. return $srchform;
  141. }
  142.  
  143.  
  144. // Work out the WHERE for tags
  145. if ($_GET['tag']) {
  146. $conn = Pdb::getConnection();
  147. $tags = Tags::splitupTags($_GET['tag']);
  148. $tagwhere = array();
  149. foreach ($tags as $t) {
  150. $tagwhere[] = $conn->quote($t);
  151. }
  152. }
  153.  
  154. // Prep handlers
  155. $search_handlers = array();
  156. foreach ($_GET['type'] as $type) {
  157. $ch = null;
  158. foreach ($config_handlers as $ch) {
  159. if ($ch->getMainTable() == $type) break;
  160. }
  161. if ($ch === null) continue;
  162.  
  163. if ($_GET['tag']) {
  164. $ch->addJoin("INNER JOIN ~tags AS tags ON tags.record_table = '{$ch->getMainTable()}' AND tags.record_id = main.id");
  165.  
  166. $ch->addWhere('tags.name IN (' . implode(', ', $tagwhere) . ')');
  167.  
  168. if ($_GET['tag_type'] == 'and') {
  169. $ch->addHaving('COUNT(tags.name) = ' . count($tagwhere));
  170. }
  171. }
  172.  
  173. $where = $this->dateWhere($_GET['date']);
  174. if ($where) $ch->addWhere($where);
  175.  
  176. $search_handlers[] = $ch;
  177. }
  178.  
  179.  
  180. // Do the search
  181. Search::queryAnd($_GET['q_type'] == 'and');
  182. $search_result = Search::query($_GET['q'], $search_handlers, $_GET['page'] - 1);
  183.  
  184. if (! $search_result) {
  185. return $srchform;
  186. }
  187.  
  188. list ($res, $keywords, $num_results, $num_pages) = $search_result;
  189.  
  190. if ($res->rowCount() == 0) {
  191. return $srchform . '<p>No results were found matching your search terms.</p>';
  192. }
  193.  
  194.  
  195. // Instantiate search handlers
  196. $handler_inst = array();
  197. foreach ($search_handlers as $handler) {
  198. $class = $handler->getCtlrName();
  199.  
  200. $ctlr = @new $class;
  201. if (! $ctlr instanceof FrontEndSearch) throw new Exception("Search handler {$class} does not implement FrontEndSearch");
  202. $handler_inst[$class] = $ctlr;
  203. }
  204.  
  205. if (empty($_GET['fullform'])) {
  206. $srchform = new View('sprout/advanced_search_form');
  207. $srchform->avail_types = $avail_types;
  208. $srchform = $srchform->render();
  209. }
  210.  
  211. $out = $srchform;
  212.  
  213. // Iterate through results, passing the rendering task off to the appropriate controller
  214. foreach ($res as $row) {
  215.  
  216. $ctlr = $handler_inst[$row['controller_class']];
  217.  
  218. $out .= '<div class="search-result">';
  219. $out .= $ctlr->frontEndSearch($row['record_id'], $row['relevancy'], $keywords);
  220. $out .= '</div>';
  221. }
  222.  
  223. $out .= Search::paginate($_GET['page'], $num_pages, 'search-paginate');
  224.  
  225. return $out;
  226. }
  227.  
  228.  
  229. /**
  230.   * Take the datespec on the key side of Constants::$relative_dates and turn into a WHERE clause
  231.   **/
  232. private function dateWhere($date)
  233. {
  234. $date = trim($date);
  235. if (! $date) return null;
  236. if (! preg_match('!^([no])([0-9]+)$!', $date, $matches)) return null;
  237.  
  238. if ($matches[1] == 'n') {
  239. return "main.date_modified > DATE_SUB(NOW(), INTERVAL {$matches[2]} MONTH)";
  240. } else if ($matches[1] == 'o') {
  241. return "main.date_modified < DATE_SUB(NOW(), INTERVAL {$matches[2]} MONTH)";
  242. }
  243. }
  244.  
  245.  
  246. /**
  247.   * Provides data for the tag autocomplete
  248.   **/
  249. public static function tagAutocomplete()
  250. {
  251. header('Content-type: text/plain');
  252. echo json_encode(Tags::beginsWith($_GET['q']));
  253. }
  254. }
  255.