/phpmyfaq/src/phpMyFAQ/Search.php
PHP | 353 lines | 203 code | 42 blank | 108 comment | 24 complexity | a5c3c58c502c0e2e912eaef7ea710ebe MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, LGPL-3.0
- <?php
- /**
- * The phpMyFAQ Search class.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public License,
- * v. 2.0. If a copy of the MPL was not distributed with this file, You can
- * obtain one at http://mozilla.org/MPL/2.0/.
- *
- * @package phpMyFAQ
- * @author Thorsten Rinne <thorsten@phpmyfaq.de>
- * @author Matteo Scaramuccia <matteo@scaramuccia.com>
- * @author Adrianna Musiol <musiol@imageaccess.de>
- * @copyright 2008-2021 phpMyFAQ Team
- * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
- * @link https://www.phpmyfaq.de
- * @since 2008-01-26
- */
- namespace phpMyFAQ;
- use DateTime;
- use Exception;
- use phpMyFAQ\Search\Elasticsearch;
- use phpMyFAQ\Search\SearchFactory;
- use stdClass;
- /**
- * Class Search
- *
- * @package phpMyFAQ
- */
- class Search
- {
- /** @var Configuration */
- private $config;
- /** @var int */
- private $categoryId = null;
- /** @var Category */
- private $category = null;
- /** @var string */
- private $table;
- /**
- * Constructor.
- *
- * @param Configuration $config
- */
- public function __construct(Configuration $config)
- {
- $this->config = $config;
- $this->table = Database::getTablePrefix() . 'faqsearches';
- }
- /**
- * Setter for category.
- *
- * @param int $categoryId Entity ID
- */
- public function setCategoryId(int $categoryId): void
- {
- $this->categoryId = $categoryId;
- }
- /**
- * Getter for category.
- *
- * @return int
- */
- public function getCategoryId(): ?int
- {
- return $this->categoryId;
- }
- /**
- * The search function to handle the different search engines.
- *
- * @param string $searchTerm Text/Number (solution id)
- * @param bool $allLanguages true to search over all languages
- * @throws Exception
- * @return mixed[]
- */
- public function search(string $searchTerm, $allLanguages = true): array
- {
- if (is_numeric($searchTerm)) {
- return $this->searchDatabase($searchTerm, $allLanguages);
- }
- if ($this->config->get('search.enableElasticsearch')) {
- return $this->searchElasticsearch($searchTerm, $allLanguages);
- } else {
- return $this->searchDatabase($searchTerm, $allLanguages);
- }
- }
- /**
- * The auto complete function to handle the different search engines.
- *
- * @param string $searchTerm Text to auto complete
- * @throws Exception
- * @return mixed[]
- */
- public function autoComplete(string $searchTerm): array
- {
- if ($this->config->get('search.enableElasticsearch')) {
- $esSearch = new Elasticsearch($this->config);
- $allCategories = $this->getCategory()->getAllCategoryIds();
- $esSearch->setCategoryIds($allCategories);
- $esSearch->setLanguage($this->config->getLanguage()->getLanguage());
- return $esSearch->autoComplete($searchTerm);
- } else {
- return $this->searchDatabase($searchTerm, false);
- }
- }
- /**
- * The search function for the database powered full text search.
- *
- * @param string $searchTerm Text/Number (solution id)
- * @param bool $allLanguages true to search over all languages
- * @throws Exception
- * @return mixed[]
- */
- public function searchDatabase(string $searchTerm, $allLanguages = true): array
- {
- $fdTable = Database::getTablePrefix() . 'faqdata AS fd';
- $fcrTable = Database::getTablePrefix() . 'faqcategoryrelations';
- $condition = ['fd.active' => "'yes'"];
- $search = SearchFactory::create($this->config, ['database' => Database::getType()]);
- if (!is_null($this->getCategoryId()) && 0 < $this->getCategoryId()) {
- if ($this->getCategory() instanceof Category) {
- $children = $this->getCategory()->getChildNodes($this->getCategoryId());
- $selectedCategory = [
- $fcrTable . '.category_id' => array_merge((array)$this->getCategoryId(), $children),
- ];
- } else { // @phpstan-ignore-line
- $selectedCategory = [
- $fcrTable . '.category_id' => $this->getCategoryId(),
- ];
- }
- $condition = array_merge($selectedCategory, $condition);
- }
- if ((!$allLanguages) && (!is_numeric($searchTerm))) {
- $selectedLanguage = ['fd.lang' => "'" . $this->config->getLanguage()->getLanguage() . "'"];
- $condition = array_merge($selectedLanguage, $condition);
- }
- $search->setTable($fdTable)
- ->setResultColumns(
- [
- 'fd.id AS id',
- 'fd.lang AS lang',
- 'fd.solution_id AS solution_id',
- $fcrTable . '.category_id AS category_id',
- 'fd.thema AS question',
- 'fd.content AS answer'
- ]
- )
- ->setJoinedTable($fcrTable)
- ->setJoinedColumns(
- [
- 'fd.id = ' . $fcrTable . '.record_id',
- 'fd.lang = ' . $fcrTable . '.record_lang'
- ]
- )
- ->setConditions($condition);
- if (is_numeric($searchTerm)) {
- $search->setMatchingColumns(['fd.solution_id']);
- } else {
- $search->setMatchingColumns(['fd.thema', 'fd.content', 'fd.keywords']);
- }
- $result = $search->search($searchTerm);
- if (!$this->config->getDb()->numRows($result)) {
- return [];
- } else {
- return $this->config->getDb()->fetchAll($result);
- }
- }
- /**
- * The search function for the Elasticsearch powered full text search.
- *
- * @param string $searchTerm Text/Number (solution id)
- * @param bool $allLanguages true to search over all languages
- * @return stdClass[]
- */
- public function searchElasticsearch(string $searchTerm, $allLanguages = true): array
- {
- $esSearch = new Elasticsearch($this->config);
- if (!is_null($this->getCategoryId()) && 0 < $this->getCategoryId()) {
- if ($this->getCategory() instanceof Category) {
- $children = $this->getCategory()->getChildNodes($this->getCategoryId());
- $esSearch->setCategoryIds(array_merge([$this->getCategoryId()], $children));
- }
- } else {
- $allCategories = $this->getCategory()->getAllCategoryIds();
- $esSearch->setCategoryIds($allCategories);
- }
- if (!$allLanguages) {
- $esSearch->setLanguage($this->config->getLanguage()->getLanguage());
- }
- return $esSearch->search($searchTerm);
- }
- /**
- * Logging of search terms for improvements.
- *
- * @param string $searchTerm Search term
- * @throws Exception
- */
- public function logSearchTerm(string $searchTerm): void
- {
- if (Strings::strlen($searchTerm) === 0) {
- return;
- }
- $date = new DateTime();
- $query = sprintf(
- "INSERT INTO %s (id, lang, searchterm, searchdate) VALUES (%d, '%s', '%s', '%s')",
- $this->table,
- $this->config->getDb()->nextId($this->table, 'id'),
- $this->config->getLanguage()->getLanguage(),
- $this->config->getDb()->escape($searchTerm),
- $date->format('Y-m-d H:i:s')
- );
- $this->config->getDb()->query($query);
- }
- /**
- * Deletes a search term.
- *
- * @param string $searchTerm
- * @return bool
- */
- public function deleteSearchTerm(string $searchTerm): bool
- {
- $query = sprintf(
- "
- DELETE FROM
- %s
- WHERE
- searchterm = '%s'",
- $this->table,
- $searchTerm
- );
- return $this->config->getDb()->query($query);
- }
- /**
- * Deletes all search terms.
- *
- * @return bool
- */
- public function deleteAllSearchTerms(): bool
- {
- $query = sprintf('DELETE FROM %s', $this->table);
- return $this->config->getDb()->query($query);
- }
- /**
- * Returns the most popular searches.
- *
- * @param int $numResults Number of Results, default: 7
- * @param bool $withLang Should the language be included in the result?
- *
- * @return array<string[]>
- */
- public function getMostPopularSearches(int $numResults = 7, bool $withLang = false): array
- {
- $searchResult = [];
- $byLang = $withLang ? ', lang' : '';
- $query = sprintf(
- '
- SELECT
- MIN(id) as id, searchterm, COUNT(searchterm) AS number %s
- FROM
- %s
- GROUP BY
- searchterm %s
- ORDER BY
- number
- DESC',
- $byLang,
- $this->table,
- $byLang
- );
- $result = $this->config->getDb()->query($query);
- if (false !== $result) {
- $i = 0;
- while ($row = $this->config->getDb()->fetchObject($result)) {
- if ($i < $numResults) {
- $searchResult[] = (array)$row;
- }
- ++$i;
- }
- }
- return $searchResult;
- }
- /**
- * Returns row count from the "faqsearches" table.
- *
- * @return int
- */
- public function getSearchesCount(): int
- {
- $sql = sprintf(
- 'SELECT COUNT(1) AS count FROM %s',
- $this->table
- );
- $result = $this->config->getDb()->query($sql);
- return (int)$this->config->getDb()->fetchObject($result)->count;
- }
- /**
- * Sets the Entity object.
- *
- * @param Category $category
- */
- public function setCategory(Category $category): void
- {
- $this->category = $category;
- }
- /**
- * @return Category
- */
- public function getCategory(): Category
- {
- return $this->category;
- }
- }