PageRenderTime 62ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/concrete/blocks/search/controller.php

http://github.com/concrete5/concrete5
PHP | 490 lines | 290 code | 56 blank | 144 comment | 53 complexity | 5f0b5dc8e414b9302fba2794a75a82fc MD5 | raw file
Possible License(s): MIT, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. <?php
  2. namespace Concrete\Block\Search;
  3. use CollectionAttributeKey;
  4. use Concrete\Core\Attribute\Key\CollectionKey;
  5. use Concrete\Core\Block\BlockController;
  6. use Concrete\Core\Page\PageList;
  7. use Core;
  8. use Database;
  9. use Page;
  10. use Request;
  11. class Controller extends BlockController
  12. {
  13. /**
  14. * Search title.
  15. *
  16. * @var string
  17. */
  18. public $title = '';
  19. /**
  20. * Button text.
  21. *
  22. * @var string
  23. */
  24. public $buttonText = '>';
  25. /**
  26. * The base search path.
  27. *
  28. * @var string
  29. */
  30. public $baseSearchPath = '';
  31. /**
  32. * Destination page (another page).
  33. *
  34. * @var int|null
  35. */
  36. public $postTo_cID = null;
  37. /**
  38. * Destination page (external URL).
  39. *
  40. * @var string
  41. */
  42. public $resultsURL = '';
  43. /**
  44. * Unused?
  45. *
  46. * @var string[]
  47. */
  48. public $reservedParams = ['page=', 'query=', 'search_paths[]=', 'submit=', 'search_paths%5B%5D='];
  49. /**
  50. * The name of the database that holds the block data.
  51. *
  52. * @var string
  53. */
  54. protected $btTable = 'btSearch';
  55. /**
  56. * Set this to true if the data sent to the save/performSave methods can contain NULL values that should be persisted.
  57. *
  58. * @var bool
  59. */
  60. protected $supportSavingNullValues = true;
  61. /**
  62. * Add/Edit dialog width (in pixels).
  63. *
  64. * @var int
  65. */
  66. protected $btInterfaceWidth = 400;
  67. /**
  68. * Add/Edit dialog height (in pixels).
  69. *
  70. * @var int
  71. */
  72. protected $btInterfaceHeight = 420;
  73. /**
  74. * The CSS class of the block wrapper (unused?).
  75. *
  76. * @var string
  77. */
  78. protected $btWrapperClass = 'ccm-ui';
  79. /**
  80. * List of database table fields that contains fields with collection identifiers.
  81. *
  82. * @var string[]
  83. */
  84. protected $btExportPageColumns = ['postTo_cID'];
  85. /**
  86. * Should the database record be cached?
  87. *
  88. * @var bool
  89. */
  90. protected $btCacheBlockRecord = true;
  91. /**
  92. * Should the block output be cached?
  93. *
  94. * @var bool
  95. */
  96. protected $btCacheBlockOutput = null;
  97. /**
  98. * The color to be used to highlight results.
  99. *
  100. * @var string
  101. */
  102. protected $hColor = '#EFE795';
  103. /**
  104. * Whether or not to search all sites.
  105. *
  106. * @var bool
  107. */
  108. protected $search_all;
  109. /**
  110. * Whether or not to users can search all sites from the frontend.
  111. *
  112. * @var bool
  113. */
  114. protected $allow_user_options;
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function getBlockTypeName()
  119. {
  120. return t('Search');
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. public function getBlockTypeDescription()
  126. {
  127. return t('Add a search box to your site.');
  128. }
  129. /**
  130. * Highlight parts of a text.
  131. *
  132. * @param string $fulltext the whole text
  133. * @param string $highlight The text to be highlighted
  134. *
  135. * @return string
  136. */
  137. public function highlightedMarkup($fulltext, $highlight)
  138. {
  139. if (!$highlight) {
  140. return $fulltext;
  141. }
  142. $this->hText = $fulltext;
  143. $this->hHighlight = $highlight;
  144. $this->hText = @preg_replace('#' . preg_quote($this->hHighlight, '#') . '#ui', '<span style="background-color:' . $this->hColor . ';">$0</span>', $this->hText);
  145. return $this->hText;
  146. }
  147. /**
  148. * Highlight parts of a text (extended version).
  149. *
  150. * @param string $fulltext the whole text
  151. * @param string $highlight The text to be highlighted
  152. *
  153. * @return string|null
  154. */
  155. public function highlightedExtendedMarkup($fulltext, $highlight)
  156. {
  157. $text = @preg_replace("#\n|\r#", ' ', $fulltext);
  158. $matches = [];
  159. $highlight = str_replace(['"', "'", '&quot;'], '', $highlight); // strip the quotes as they mess the regex
  160. if (!$highlight) {
  161. $text = $this->app->make('helper/text')->shorten($fulltext, 180);
  162. if (strlen($fulltext) > 180) {
  163. $text .= '&hellip;<wbr>';
  164. }
  165. $result = $text;
  166. } else {
  167. $result = null;
  168. $regex = '([[:alnum:]|\'|\.|_|\s]{0,45})' . preg_quote($highlight, '#') . '([[:alnum:]|\.|_|\s]{0,45})';
  169. preg_match_all("#$regex#ui", $text, $matches);
  170. if (!empty($matches[0])) {
  171. $body_length = 0;
  172. $body_string = [];
  173. foreach ($matches[0] as $line) {
  174. $body_length += strlen($line);
  175. $r = $this->highlightedMarkup($line, $highlight);
  176. if ($r) {
  177. $body_string[] = $r;
  178. }
  179. if ($body_length > 150) {
  180. break;
  181. }
  182. }
  183. if (!empty($body_string)) {
  184. $result = (string) @implode('&hellip;<wbr>', $body_string);
  185. }
  186. }
  187. }
  188. return $result;
  189. }
  190. /**
  191. * Set the color to be used to highlight results.
  192. *
  193. * @param string $color
  194. */
  195. public function setHighlightColor($color)
  196. {
  197. $this->hColor = $color;
  198. }
  199. /**
  200. * Is there some page in the PageSearchIndex database table?
  201. *
  202. * @return bool
  203. */
  204. public function indexExists()
  205. {
  206. $db = Database::connection();
  207. return (bool) $db->fetchColumn('select cID from PageSearchIndex limit 1');
  208. }
  209. /**
  210. * {@inheritdoc}
  211. *
  212. * @see \Concrete\Core\Block\BlockController::cacheBlockOutput()
  213. */
  214. public function cacheBlockOutput()
  215. {
  216. if ($this->btCacheBlockOutput === null) {
  217. $this->btCacheBlockOutput = false;
  218. if ($this->postTo_cID || (string) $this->resultsURL !== '') {
  219. if ($this->request->get('query') === null) {
  220. $this->btCacheBlockOutput = true;
  221. }
  222. }
  223. $this->btCacheBlockOutput = (($this->postTo_cID || $this->resultsURL) && Request::request('query') === null);
  224. }
  225. return $this->btCacheBlockOutput;
  226. }
  227. /**
  228. * Default view method.
  229. */
  230. public function view()
  231. {
  232. $this->set('title', $this->title);
  233. $this->set('buttonText', $this->buttonText);
  234. $this->set('baseSearchPath', $this->baseSearchPath);
  235. $this->set('postTo_cID', $this->postTo_cID);
  236. $this->set('allowUserOptions', $this->allow_user_options);
  237. $this->set('searchAll', $this->search_all);
  238. if ((string) $this->resultsURL !== '') {
  239. $resultsPage = null;
  240. $resultsURL = $this->resultsURL;
  241. } else {
  242. $resultsPage = $this->postTo_cID ? Page::getById($this->postTo_cID) : null;
  243. if (is_object($resultsPage) && !$resultsPage->isError()) {
  244. $resultsURL = $resultsPage->getCollectionPath();
  245. } else {
  246. $resultsPage = null;
  247. $c = Page::getCurrentPage();
  248. $resultsURL = $c->getCollectionPath();
  249. }
  250. }
  251. $resultsURL = $this->app->make('helper/text')->encodePath($resultsURL);
  252. $this->set('resultTargetURL', $resultsURL);
  253. if ($resultsPage !== null) {
  254. $this->set('resultTarget', $resultsPage);
  255. } else {
  256. $this->set('resultTarget', $resultsURL);
  257. }
  258. //run query if display results elsewhere not set, or the cID of this page is set
  259. if ($resultsPage === null && (string) $this->resultsURL === '') {
  260. if ((string) $this->request->request('query') !== '' || $this->request->request('akID') || $this->request->request('month')) {
  261. $this->do_search();
  262. }
  263. }
  264. }
  265. /**
  266. * Method called when the "add block" dialog is going to be shown.
  267. */
  268. public function add()
  269. {
  270. $this->edit();
  271. }
  272. /**
  273. * Method called when the "edit block" dialog is going to be shown.
  274. */
  275. public function edit()
  276. {
  277. $this->set('pageSelector', $this->app->make('helper/form/page_selector'));
  278. $this->set('searchAll', $this->search_all);
  279. $this->set('allowUserOptions', $this->allow_user_options);
  280. }
  281. /**
  282. * {@inheritdoc}
  283. */
  284. public function save($data)
  285. {
  286. $data += [
  287. 'title' => '',
  288. 'buttonText' => '',
  289. 'baseSearchPath' => '',
  290. 'searchUnderCID' => 0,
  291. 'postTo_cID' => 0,
  292. 'externalTarget' => 0,
  293. 'resultsURL' => '',
  294. 'resultsPageKind' => '',
  295. 'allowUserOptions' => '',
  296. ];
  297. $args = [
  298. 'title' => (string) $data['title'],
  299. 'buttonText' => (string) $data['buttonText'],
  300. 'baseSearchPath' => '',
  301. 'postTo_cID' => null,
  302. 'resultsURL' => '',
  303. 'search_all' => 0,
  304. 'allow_users_options' => 0,
  305. ];
  306. switch ($data['baseSearchPath']) {
  307. case 'THIS':
  308. $c = Page::getCurrentPage();
  309. if (is_object($c) && !$c->isError()) {
  310. $args['baseSearchPath'] = (string) $c->getCollectionPath();
  311. }
  312. break;
  313. case 'OTHER':
  314. if ($data['searchUnderCID']) {
  315. $searchUnderCID = (int) $data['searchUnderCID'];
  316. $searchUnderC = Page::getByID($searchUnderCID);
  317. if (is_object($searchUnderC) && !$searchUnderC->isError()) {
  318. $args['baseSearchPath'] = (string) $searchUnderC->getCollectionPath();
  319. }
  320. }
  321. break;
  322. case 'ALL':
  323. $args['search_all'] = true;
  324. break;
  325. }
  326. if ($args['baseSearchPath'] === '/') {
  327. $args['baseSearchPath'] = '';
  328. }
  329. switch ($data['resultsPageKind']) {
  330. case 'CID':
  331. if ($data['postTo_cID']) {
  332. $postTo_cID = (int) $data['postTo_cID'];
  333. $postTo_c = Page::getByID($postTo_cID);
  334. if (is_object($postTo_c) && !$postTo_c->isError()) {
  335. $args['postTo_cID'] = $postTo_cID;
  336. }
  337. }
  338. break;
  339. case 'URL':
  340. $args['resultsURL'] = (string) $data['resultsURL'];
  341. break;
  342. }
  343. if ($data['allowUserOptions'] === 'ALLOW') {
  344. $args['allow_user_options'] = true;
  345. } else {
  346. $args['allow_user_options'] = 0;
  347. }
  348. parent::save($args);
  349. }
  350. /**
  351. * Perform the search.
  352. *
  353. * @return null|false
  354. */
  355. public function do_search()
  356. {
  357. $query = (string) $this->request->request('query');
  358. $ipl = new PageList();
  359. $options = $this->request->request('options');
  360. if ($options) {
  361. //Overrides search all settings with user submitted option
  362. if ($options === 'ALL') {
  363. $ipl->setSiteTreeToAll();
  364. }
  365. } else {
  366. //If options are not send by user then use default options from the block settings.
  367. //If Search All is enabled set search site tree to all.
  368. if ((int) $this->search_all === 1) {
  369. $ipl->setSiteTreeToAll();
  370. }
  371. }
  372. $aksearch = false;
  373. $akIDs = $this->request->request('akID');
  374. if (is_array($akIDs)) {
  375. foreach ($akIDs as $akID => $req) {
  376. $fak = CollectionAttributeKey::getByID($akID);
  377. if (is_object($fak)) {
  378. $type = $fak->getAttributeType();
  379. $cnt = $type->getController();
  380. $cnt->setAttributeKey($fak);
  381. $cnt->searchForm($ipl);
  382. $aksearch = true;
  383. }
  384. }
  385. }
  386. if ($this->request->request('month') !== null && $this->request->request('year') !== null) {
  387. $year = @(int) ($this->request->request('year'));
  388. $month = abs(@(int) ($this->request->request('month')));
  389. if (strlen(abs($year)) < 4) {
  390. $year = (($year < 0) ? '-' : '') . str_pad(abs($year), 4, '0', STR_PAD_LEFT);
  391. }
  392. if ($month < 12) {
  393. $month = str_pad($month, 2, '0', STR_PAD_LEFT);
  394. }
  395. $daysInMonth = date('t', strtotime("$year-$month-01"));
  396. $dh = Core::make('helper/date');
  397. /* @var $dh \Concrete\Core\Localization\Service\Date */
  398. $start = $dh->toDB("$year-$month-01 00:00:00", 'user');
  399. $end = $dh->toDB("$year-$month-$daysInMonth 23:59:59", 'user');
  400. $ipl->filterByPublicDate($start, '>=');
  401. $ipl->filterByPublicDate($end, '<=');
  402. $aksearch = true;
  403. }
  404. if ($query === '' && $aksearch === false) {
  405. return false;
  406. }
  407. if ($query !== '') {
  408. $ipl->filterByKeywords($query);
  409. }
  410. $search_paths = $this->request->request('search_paths');
  411. if (is_array($search_paths)) {
  412. foreach ($search_paths as $path) {
  413. if ($path === '') {
  414. continue;
  415. }
  416. $ipl->filterByPath($path);
  417. }
  418. } elseif ($this->baseSearchPath != '') {
  419. $ipl->filterByPath($this->baseSearchPath);
  420. }
  421. $cak = CollectionKey::getByHandle('exclude_search_index');
  422. if (is_object($cak)) {
  423. $ipl->filterByExcludeSearchIndex(false);
  424. }
  425. $pagination = $ipl->getPagination();
  426. $results = $pagination->getCurrentPageResults();
  427. $this->set('query', $query);
  428. $this->set('results', $results);
  429. $this->set('do_search', true);
  430. $this->set('searchList', $ipl);
  431. $this->set('pagination', $pagination);
  432. }
  433. }