View source
<?php
namespace Drupal\help_topics\Plugin\Search;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\Config;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\Query\PagerSelectExtender;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\State\StateInterface;
use Drupal\help\HelpSectionManager;
use Drupal\help_topics\SearchableHelpInterface;
use Drupal\search\Plugin\SearchIndexingInterface;
use Drupal\search\Plugin\SearchPluginBase;
use Drupal\search\SearchIndexInterface;
use Drupal\search\SearchQuery;
use Symfony\Component\DependencyInjection\ContainerInterface;
class HelpSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface {
protected $database;
protected $searchSettings;
protected $languageManager;
protected $account;
protected $messenger;
protected $state;
protected $helpSectionManager;
protected $searchIndex;
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('database'), $container
->get('config.factory')
->get('search.settings'), $container
->get('language_manager'), $container
->get('messenger'), $container
->get('current_user'), $container
->get('state'), $container
->get('plugin.manager.help_section'), $container
->get('search.index'));
}
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, Config $search_settings, LanguageManagerInterface $language_manager, MessengerInterface $messenger, AccountInterface $account, StateInterface $state, HelpSectionManager $help_section_manager, SearchIndexInterface $search_index) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
$this->searchSettings = $search_settings;
$this->languageManager = $language_manager;
$this->messenger = $messenger;
$this->account = $account;
$this->state = $state;
$this->helpSectionManager = $help_section_manager;
$this->searchIndex = $search_index;
}
public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowedIfHasPermission($account, 'access administration pages');
return $return_as_object ? $result : $result
->isAllowed();
}
public function getType() {
return $this
->getPluginId();
}
public function execute() {
if ($this
->isSearchExecutable()) {
$results = $this
->findResults();
if ($results) {
return $this
->prepareResults($results);
}
}
return [];
}
protected function findResults() {
$this
->addCacheContexts([
'user.permissions',
]);
if (!$this->account
->hasPermission('access administration pages')) {
return NULL;
}
$permissions = $this->database
->select('help_search_items', 'hsi')
->distinct()
->fields('hsi', [
'permission',
])
->condition('permission', '', '<>')
->execute()
->fetchCol();
$denied_permissions = array_filter($permissions, function ($permission) {
return !$this->account
->hasPermission($permission);
});
$query = $this->database
->select('search_index', 'i')
->condition('i.langcode', $this->languageManager
->getCurrentLanguage()
->getId())
->extend(SearchQuery::class)
->extend(PagerSelectExtender::class);
$query
->innerJoin('help_search_items', 'hsi', 'i.sid = hsi.sid AND i.type = :type', [
':type' => $this
->getType(),
]);
if ($denied_permissions) {
$query
->condition('hsi.permission', $denied_permissions, 'NOT IN');
}
$query
->searchExpression($this
->getKeywords(), $this
->getType());
$find = $query
->fields('i', [
'langcode',
])
->fields('hsi', [
'section_plugin_id',
'topic_id',
])
->groupBy('i.langcode')
->groupBy('hsi.section_plugin_id')
->groupBy('hsi.topic_id')
->limit(10)
->execute();
$status = $query
->getStatus();
if ($status & SearchQuery::EXPRESSIONS_IGNORED) {
$this->messenger
->addWarning($this
->t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', [
'@count' => $this->searchSettings
->get('and_or_limit'),
]));
}
if ($status & SearchQuery::LOWER_CASE_OR) {
$this->messenger
->addWarning($this
->t('Search for either of the two terms with uppercase <strong>OR</strong>. For example, <strong>cats OR dogs</strong>.'));
}
if ($status & SearchQuery::NO_POSITIVE_KEYWORDS) {
$this->messenger
->addWarning($this
->formatPlural($this->searchSettings
->get('index.minimum_word_size'), 'You must include at least one keyword to match in the content, and punctuation is ignored.', 'You must include at least one keyword to match in the content. Keywords must be at least @count characters, and punctuation is ignored.'));
}
return $find;
}
protected function prepareResults(StatementInterface $found) {
$results = [];
$plugins = [];
$languages = [];
$keys = $this
->getKeywords();
foreach ($found as $item) {
$section_plugin_id = $item->section_plugin_id;
if (!isset($plugins[$section_plugin_id])) {
$plugins[$section_plugin_id] = $this
->getSectionPlugin($section_plugin_id);
}
if ($plugins[$section_plugin_id]) {
$langcode = $item->langcode;
if (!isset($languages[$langcode])) {
$languages[$langcode] = $this->languageManager
->getLanguage($item->langcode);
}
$topic = $plugins[$section_plugin_id]
->renderTopicForSearch($item->topic_id, $languages[$langcode]);
if ($topic) {
if (isset($topic['cacheable_metadata'])) {
$this
->addCacheableDependency($topic['cacheable_metadata']);
}
$results[] = [
'title' => $topic['title'],
'link' => $topic['url']
->toString(),
'snippet' => search_excerpt($keys, $topic['title'] . ' ' . $topic['text'], $item->langcode),
'langcode' => $item->langcode,
];
}
}
}
return $results;
}
public function updateIndex() {
$this
->updateTopicList();
$limit = (int) $this->searchSettings
->get('index.cron_limit');
$query = $this->database
->select('help_search_items', 'hsi');
$query
->fields('hsi', [
'sid',
'section_plugin_id',
'topic_id',
]);
$query
->leftJoin('search_dataset', 'sd', 'sd.sid = hsi.sid AND sd.type = :type', [
':type' => $this
->getType(),
]);
$query
->where('sd.sid IS NULL');
$query
->groupBy('hsi.sid')
->groupBy('hsi.section_plugin_id')
->groupBy('hsi.topic_id')
->range(0, $limit);
$items = $query
->execute()
->fetchAll();
if (count($items) < $limit) {
$query = $this->database
->select('help_search_items', 'hsi');
$query
->fields('hsi', [
'sid',
'section_plugin_id',
'topic_id',
]);
$query
->leftJoin('search_dataset', 'sd', 'sd.sid = hsi.sid AND sd.type = :type', [
':type' => $this
->getType(),
]);
$query
->condition('sd.reindex', 0, '<>');
$query
->groupBy('hsi.sid')
->groupBy('hsi.section_plugin_id')
->groupBy('hsi.topic_id')
->range(0, $limit - count($items));
$items = $items + $query
->execute()
->fetchAll();
}
$language_list = $this->languageManager
->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$section_plugins = [];
$words = [];
try {
foreach ($items as $item) {
$section_plugin_id = $item->section_plugin_id;
if (!isset($section_plugins[$section_plugin_id])) {
$section_plugins[$section_plugin_id] = $this
->getSectionPlugin($section_plugin_id);
}
if (!$section_plugins[$section_plugin_id]) {
$this
->removeItemsFromIndex($item->sid);
continue;
}
$section_plugin = $section_plugins[$section_plugin_id];
$this->searchIndex
->clear($this
->getType(), $item->sid);
foreach ($language_list as $langcode => $language) {
$topic = $section_plugin
->renderTopicForSearch($item->topic_id, $language);
if ($topic) {
$text = '<h1>' . $topic['title'] . '</h1>' . "\n" . $topic['text'];
$words += $this->searchIndex
->index($this
->getType(), $item->sid, $langcode, $text, FALSE);
}
}
}
} finally {
$this->searchIndex
->updateWordWeights($words);
}
}
public function indexClear() {
$this->searchIndex
->clear($this
->getType());
}
public function updateTopicList() {
$old_list = $this->database
->select('help_search_items', 'hsi')
->fields('hsi', [
'sid',
'topic_id',
'section_plugin_id',
'permission',
])
->execute();
$old_list_ordered = [];
$sids_to_remove = [];
foreach ($old_list as $item) {
$old_list_ordered[$item->section_plugin_id][$item->topic_id] = $item;
$sids_to_remove[$item->sid] = $item->sid;
}
$section_plugins = $this->helpSectionManager
->getDefinitions();
foreach ($section_plugins as $section_plugin_id => $section_plugin_definition) {
$plugin = $this
->getSectionPlugin($section_plugin_id);
if (!$plugin) {
continue;
}
$permission = $section_plugin_definition['permission'] ?? '';
foreach ($plugin
->listSearchableTopics() as $topic_id) {
if (isset($old_list_ordered[$section_plugin_id][$topic_id])) {
$old_item = $old_list_ordered[$section_plugin_id][$topic_id];
if ($old_item->permission == $permission) {
unset($sids_to_remove[$old_item->sid]);
continue;
}
$this->database
->update('help_search_items')
->condition('sid', $old_item->sid)
->fields([
'permission' => $permission,
])
->execute();
unset($sids_to_remove[$old_item->sid]);
continue;
}
$this->database
->insert('help_search_items')
->fields([
'section_plugin_id' => $section_plugin_id,
'permission' => $permission,
'topic_id' => $topic_id,
])
->execute();
}
}
$this
->removeItemsFromIndex($sids_to_remove);
}
public function markForReindex() {
$this
->updateTopicList();
$this->searchIndex
->markForReindex($this
->getType());
}
public function indexStatus() {
$this
->updateTopicList();
$total = $this->database
->select('help_search_items', 'hsi')
->countQuery()
->execute()
->fetchField();
$query = $this->database
->select('help_search_items', 'hsi');
$query
->addExpression('COUNT(DISTINCT(hsi.sid))');
$query
->leftJoin('search_dataset', 'sd', 'hsi.sid = sd.sid AND sd.type = :type', [
':type' => $this
->getType(),
]);
$condition = new Condition('OR');
$condition
->condition('sd.reindex', 0, '<>')
->isNull('sd.sid');
$query
->condition($condition);
$remaining = $query
->execute()
->fetchField();
return [
'remaining' => $remaining,
'total' => $total,
];
}
protected function removeItemsFromIndex($sids) {
$sids = (array) $sids;
foreach (array_chunk($sids, 100) as $this_list) {
$this->database
->delete('help_search_items')
->condition('sid', $this_list, 'IN')
->execute();
}
foreach ($sids as $sid) {
$this->searchIndex
->clear($this
->getType(), $sid);
}
}
protected function getSectionPlugin($section_plugin_id) {
$section_plugin = $this->helpSectionManager
->createInstance($section_plugin_id);
return $section_plugin instanceof SearchableHelpInterface ? $section_plugin : FALSE;
}
}