PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/phpmyfaq/src/phpMyFAQ/Helper/SearchHelper.php

http://github.com/thorsten/phpMyFAQ
PHP | 382 lines | 247 code | 46 blank | 89 comment | 23 complexity | c87d4f699c0f99af3b30924f72433a03 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * Helper class for phpMyFAQ search.
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public License,
  6. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. * obtain one at http://mozilla.org/MPL/2.0/.
  8. *
  9. * @package phpMyFAQ\Helper
  10. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  11. * @copyright 2009-2021 phpMyFAQ Team
  12. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  13. * @link https://www.phpmyfaq.de
  14. * @since 2009-09-07
  15. */
  16. namespace phpMyFAQ\Helper;
  17. use Exception;
  18. use phpMyFAQ\Configuration;
  19. use phpMyFAQ\Faq;
  20. use phpMyFAQ\Helper;
  21. use phpMyFAQ\Link;
  22. use phpMyFAQ\Pagination;
  23. use phpMyFAQ\Search\SearchResultSet;
  24. use phpMyFAQ\Strings;
  25. use phpMyFAQ\Utils;
  26. use stdClass;
  27. /**
  28. * Class SearchHelper
  29. *
  30. * @package phpMyFAQ\Helper
  31. */
  32. class SearchHelper extends Helper
  33. {
  34. /**
  35. * Pagination object.
  36. *
  37. * @var Pagination
  38. */
  39. private $pagination = null;
  40. /**
  41. * Search term.
  42. *
  43. * @var string
  44. */
  45. private $searchTerm = '';
  46. /**
  47. * Constructor.
  48. *
  49. * @param Configuration $config
  50. */
  51. public function __construct(Configuration $config)
  52. {
  53. $this->config = $config;
  54. $this->pmfLang = $this->getTranslations();
  55. }
  56. /**
  57. * PMF_Pagination setter.
  58. *
  59. * @param Pagination $pagination Pagination
  60. */
  61. public function setPagination(Pagination $pagination)
  62. {
  63. $this->pagination = $pagination;
  64. }
  65. /**
  66. * Search term setter.
  67. *
  68. * @param string $searchTerm Search term
  69. */
  70. public function setSearchTerm(string $searchTerm)
  71. {
  72. $this->searchTerm = $searchTerm;
  73. }
  74. /**
  75. * Renders the results for Typehead.
  76. *
  77. * @param SearchResultSet $resultSet Result set object
  78. *
  79. * @return string
  80. */
  81. public function renderInstantResponseResult(SearchResultSet $resultSet): string
  82. {
  83. $results = [];
  84. $maxResults = $this->config->get('records.numberOfRecordsPerPage');
  85. $numOfResults = $resultSet->getNumberOfResults();
  86. if (0 < $numOfResults) {
  87. $i = 0;
  88. foreach ($resultSet->getResultSet() as $result) {
  89. if ($i > $maxResults) {
  90. continue;
  91. }
  92. // Build the link to the faq record
  93. $currentUrl = sprintf(
  94. '%s?%saction=faq&cat=%d&id=%d&artlang=%s&highlight=%s',
  95. $this->config->getDefaultUrl() . 'index.php',
  96. $this->sessionId,
  97. $result->category_id,
  98. $result->id,
  99. $result->lang,
  100. urlencode($this->searchTerm)
  101. );
  102. $question = html_entity_decode($result->question, ENT_QUOTES | ENT_XML1 | ENT_HTML5, 'UTF-8');
  103. $link = new Link($currentUrl, $this->config);
  104. $link->itemTitle = $result->question;
  105. $faq = new stdClass();
  106. $faq->categoryName = $this->Category->getPath($result->category_id);
  107. $faq->faqQuestion = Utils::chopString($question, 15);
  108. $faq->faqLink = $link->toString();
  109. $results[] = $faq;
  110. }
  111. }
  112. return json_encode($results);
  113. }
  114. /**
  115. * Renders the result page for Instant Response.
  116. *
  117. * @param SearchResultSet $resultSet SearchResultSet object
  118. *
  119. * @return string
  120. */
  121. public function renderAdminSuggestionResult(SearchResultSet $resultSet): string
  122. {
  123. $html = '';
  124. $confPerPage = $this->config->get('records.numberOfRecordsPerPage');
  125. $numOfResults = $resultSet->getNumberOfResults();
  126. if (0 < $numOfResults) {
  127. $i = 0;
  128. foreach ($resultSet->getResultSet() as $result) {
  129. if ($i > $confPerPage) {
  130. continue;
  131. }
  132. if (!isset($result->solution_id)) {
  133. $faq = new Faq($this->config);
  134. $solutionId = $faq->getSolutionIdFromId($result->id, $result->lang);
  135. } else {
  136. $solutionId = $result->solution_id;
  137. }
  138. // Build the link to the faq record
  139. $currentUrl = $this->config->getDefaultUrl() . sprintf('index.php?solution_id=%d', $solutionId);
  140. $html .= sprintf(
  141. '<label for="%d"><input id="%d" type="radio" name="faqURL" value="%s"> %s</label><br>',
  142. $result->id,
  143. $result->id,
  144. $currentUrl,
  145. $result->question
  146. );
  147. ++$i;
  148. }
  149. } else {
  150. $html = $this->translation['err_noArticles'];
  151. }
  152. return $html;
  153. }
  154. /**
  155. * Renders the result page for the main search page.
  156. *
  157. * @param SearchResultSet $resultSet
  158. * @param int $currentPage
  159. *
  160. * @return string
  161. * @throws Exception
  162. */
  163. public function renderSearchResult(SearchResultSet $resultSet, int $currentPage): string
  164. {
  165. $html = '';
  166. $confPerPage = $this->config->get('records.numberOfRecordsPerPage');
  167. $numOfResults = $resultSet->getNumberOfResults();
  168. $totalPages = ceil($numOfResults / $confPerPage);
  169. $lastPage = $currentPage * $confPerPage;
  170. $firstPage = $lastPage - $confPerPage;
  171. if (0 < $numOfResults) {
  172. $html .= sprintf(
  173. "<p role=\"heading\" aria-level=\"1\">%s</p>\n",
  174. $this->plurals->GetMsg('plmsgSearchAmount', $numOfResults)
  175. );
  176. if (1 < $totalPages) {
  177. $html .= sprintf(
  178. "<p><strong>%s%d %s %s</strong></p>\n",
  179. $this->translation['msgPage'],
  180. $currentPage,
  181. $this->translation['msgVoteFrom'],
  182. $this->plurals->GetMsg('plmsgPagesTotal', $totalPages)
  183. );
  184. }
  185. $html .= "<ul class=\"phpmyfaq-search-results list-unstyled\">\n";
  186. $counter = $displayedCounter = 0;
  187. $faqHelper = new FaqHelper($this->config);
  188. foreach ($resultSet->getResultSet() as $result) {
  189. if ($displayedCounter >= $confPerPage) {
  190. break;
  191. }
  192. ++$counter;
  193. if ($counter <= $firstPage) {
  194. continue;
  195. }
  196. ++$displayedCounter;
  197. // Set language for current category to fetch the correct category name
  198. $this->Category->setLanguage($result->lang);
  199. $categoryInfo = $this->Category->getCategoriesFromFaq($result->id);
  200. $categoryInfo = array_values($categoryInfo); //Reset the array keys
  201. $question = Utils::chopString($result->question, 15);
  202. $answerPreview = $faqHelper->renderAnswerPreview($result->answer, 25);
  203. $searchTerm = str_replace(
  204. ['^', '.', '?', '*', '+', '{', '}', '(', ')', '[', ']', '"'],
  205. '',
  206. $this->searchTerm
  207. );
  208. $searchTerm = preg_quote($searchTerm, '/');
  209. $searchItems = explode(' ', $searchTerm);
  210. if ($this->config->get('search.enableHighlighting') && Strings::strlen($searchItems[0]) > 1) {
  211. foreach ($searchItems as $item) {
  212. if (Strings::strlen($item) > 2) {
  213. $question = Utils::setHighlightedString($question, $item);
  214. $answerPreview = Utils::setHighlightedString($answerPreview, $item);
  215. }
  216. }
  217. }
  218. // Build the link to the faq record
  219. $currentUrl = sprintf(
  220. '%s?%saction=faq&amp;cat=%d&amp;id=%d&amp;artlang=%s&amp;highlight=%s',
  221. $this->config->getDefaultUrl(),
  222. $this->sessionId,
  223. $result->category_id,
  224. $result->id,
  225. $result->lang,
  226. urlencode($searchTerm)
  227. );
  228. $oLink = new Link($currentUrl, $this->config);
  229. $oLink->text = $question;
  230. $oLink->itemTitle = $oLink->tooltip = $result->question;
  231. $html .= '<li>';
  232. $html .= $this->renderScore($result->score * 33);
  233. $html .= sprintf(
  234. '<strong>%s</strong>: %s<br>',
  235. $categoryInfo[0]['name'],
  236. $oLink->toHtmlAnchor()
  237. );
  238. $html .= sprintf(
  239. "<small class=\"searchpreview\"><strong>%s</strong> %s...</small>\n",
  240. $this->translation['msgSearchContent'],
  241. $answerPreview
  242. );
  243. $html .= '</li>';
  244. }
  245. $html .= "</ul>\n";
  246. if (1 < $totalPages) {
  247. $html .= $this->pagination->render();
  248. }
  249. } else {
  250. $html = $this->translation['err_noArticles'];
  251. }
  252. return $html;
  253. }
  254. /**
  255. * Renders the scoring stars
  256. * @param int $relevance
  257. * @return string
  258. */
  259. private function renderScore(int $relevance = 0): string
  260. {
  261. $html = sprintf('<span title="%01.2f%%">', $relevance);
  262. $emptyStar = '<i aria-hidden="true" class="fa fa-star-o"></i>';
  263. $fullStar = '<i aria-hidden="true" class="fa fa-star"></i>';
  264. if (0 === (int)$relevance) {
  265. $html .= $emptyStar . $emptyStar . $emptyStar;
  266. } elseif ($relevance < 33) {
  267. $html .= $fullStar . $emptyStar . $emptyStar;
  268. } elseif ($relevance < 66) {
  269. $html .= $fullStar . $fullStar . $emptyStar;
  270. } else {
  271. $html .= $fullStar . $fullStar . $fullStar;
  272. }
  273. return $html . '</span> ';
  274. }
  275. /**
  276. * @param SearchResultSet $resultSet
  277. * @param int $recordId
  278. *
  279. * @return string
  280. */
  281. public function renderRelatedFaqs(SearchResultSet $resultSet, int $recordId): string
  282. {
  283. $html = '';
  284. $numOfResults = $resultSet->getNumberOfResults();
  285. if ($numOfResults > 0) {
  286. $html .= '<ul>';
  287. $counter = 0;
  288. foreach ($resultSet->getResultSet() as $result) {
  289. if ($counter >= 5) {
  290. continue;
  291. }
  292. if ($recordId == $result->id) {
  293. continue;
  294. }
  295. ++$counter;
  296. $url = sprintf(
  297. '%s?action=faq&amp;cat=%d&amp;id=%d&amp;artlang=%s',
  298. $this->config->getDefaultUrl(),
  299. $result->category_id,
  300. $result->id,
  301. $result->lang
  302. );
  303. $oLink = new Link($url, $this->config);
  304. $oLink->itemTitle = $result->question;
  305. $oLink->text = $result->question;
  306. $oLink->tooltip = $result->question;
  307. $html .= '<li>' . $oLink->toHtmlAnchor() . '</li>';
  308. }
  309. $html .= '</ul>';
  310. }
  311. return $html;
  312. }
  313. /**
  314. * Renders the list of the most popular search terms.
  315. *
  316. * @param array $mostPopularSearches Array with popular search terms
  317. *
  318. * @return string
  319. */
  320. public function renderMostPopularSearches(array $mostPopularSearches)
  321. {
  322. $html = '';
  323. foreach ($mostPopularSearches as $searchItem) {
  324. if (Strings::strlen($searchItem['searchterm']) > 0) {
  325. $html .= sprintf(
  326. '<a class="btn btn-primary m-1" href="?search=%s&submit=Search&action=search">%s ' .
  327. '<span class="badge badge-info">%dx</span> </a>',
  328. urlencode($searchItem['searchterm']),
  329. $searchItem['searchterm'],
  330. $searchItem['number']
  331. );
  332. }
  333. }
  334. return $html;
  335. }
  336. }