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\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.'));
}
$unindexed = $this->state
->get('help_search_unindexed_count', 1);
if ($unindexed) {
$this
->messenger()
->addWarning($this
->t('Help search is not fully indexed. Some results may be missing or incorrect.'));
}
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);
$this
->updateIndexState();
}
}
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 updateIndexState() {
$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(),
]);
$query
->isNull('sd.sid');
$never_indexed = $query
->execute()
->fetchField();
$this->state
->set('help_search_unindexed_count', $never_indexed);
}
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 = $this->database
->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;
}
}