array('column' => 'n.type'), 'langcode' => array('column' => 'n.langcode'), 'author' => array('column' => 'n.uid'), 'term' => array('column' => 'ti.tid', 'join' => array('table' => 'taxonomy_index', 'alias' => 'ti', 'condition' => 'n.nid = ti.nid')), ); /** * {@inheritdoc} */ static public function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('database'), $container->get('plugin.manager.entity'), $container->get('module_handler'), $container->get('config.factory')->get('search.settings'), $container->get('keyvalue')->get('state'), $container->get('request')->attributes->get('_account') ); } /** * Constructs a \Drupal\node\Plugin\Search\NodeSearch object. * * @param array $configuration * A configuration array containing information about the plugin instance. * @param string $plugin_id * The plugin_id for the plugin instance. * @param array $plugin_definition * The plugin implementation definition. * @param \Drupal\Core\Database\Connection $database * A database connection object. * @param \Drupal\Core\Entity\EntityManager $entity_manager * An entity manager object. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * A module manager object. * @param \Drupal\Core\Config\Config $search_settings * A config object for 'search.settings'. * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state * The Drupal state object used to set 'node.cron_last'. * @param \Drupal\Core\Session\AccountInterface $account * The $account object to use for checking for access to advanced search. */ public function __construct(array $configuration, $plugin_id, array $plugin_definition, Connection $database, EntityManager $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, KeyValueStoreInterface $state, AccountInterface $account = NULL) { $this->database = $database; $this->entityManager = $entity_manager; $this->moduleHandler = $module_handler; $this->searchSettings = $search_settings; $this->state = $state; $this->account = $account; parent::__construct($configuration, $plugin_id, $plugin_definition); } /** * {@inheritdoc} */ public function access($operation = 'view', AccountInterface $account = NULL) { return !empty($account) && $account->hasPermission('access content'); } /** * {@inheritdoc} */ public function execute() { $results = array(); if (!$this->isSearchExecutable()) { return $results; } $keys = $this->keywords; // Build matching conditions. $query = $this->database ->select('search_index', 'i', array('target' => 'slave')) ->extend('Drupal\search\SearchQuery') ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); $query->join('node_field_data', 'n', 'n.nid = i.sid'); $query->condition('n.status', 1) ->addTag('node_access') ->searchExpression($keys, $this->getPluginId()); // Handle advanced search filters in the f query string. // $_GET['f'] is an array that looks like this in the URL: // ?f[]=type:page&f[]=term:27&f[]=term:13&f[]=langcode:en // So $parameters['f'] looks like: // array('type:page', 'term:27', 'term:13', 'langcode:en'); // We need to parse this out into query conditions. $parameters = $this->getParameters(); if (!empty($parameters['f']) && is_array($parameters['f'])) { $filters = array(); // Match any query value that is an expected option and a value // separated by ':' like 'term:27'. $pattern = '/^(' . implode('|', array_keys($this->advanced)) . '):([^ ]*)/i'; foreach ($parameters['f'] as $item) { if (preg_match($pattern, $item, $m)) { // Use the matched value as the array key to eliminate duplicates. $filters[$m[1]][$m[2]] = $m[2]; } } // Now turn these into query conditions. This assumes that everything in // $filters is a known type of advanced search. foreach ($filters as $option => $matched) { $info = $this->advanced[$option]; // Insert additional conditions. By default, all use the OR operator. $operator = empty($info['operator']) ? 'OR' : $info['operator']; $where = new Condition($operator); foreach ($matched as $value) { $where->condition($info['column'], $value); } $query->condition($where); if (!empty($info['join'])) { $query->join($info['join']['table'], $info['join']['alias'], $info['join']['condition']); } } } // Only continue if the first pass query matches. if (!$query->executeFirstPass()) { return array(); } // Add the ranking expressions. $this->addNodeRankings($query); // Load results. $find = $query // Add the language code of the indexed item to the result of the query, // since the node will be rendered using the respective language. ->fields('i', array('langcode')) ->limit(10) ->execute(); $node_storage = $this->entityManager->getStorageController('node'); $node_render = $this->entityManager->getRenderController('node'); foreach ($find as $item) { // Render the node. $node = $node_storage->load($item->sid); $build = $node_render->view($node, 'search_result', $item->langcode); unset($build['#theme']); $node->rendered = drupal_render($build); // Fetch comment count for snippet. $node->rendered .= ' ' . $this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode)); $extra = $this->moduleHandler->invokeAll('node_search_result', array($node, $item->langcode)); $language = language_load($item->langcode); $uri = $node->uri(); $username = array( '#theme' => 'username', '#account' => $node->getAuthor(), ); $results[] = array( 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), 'type' => check_plain($this->entityManager->getStorageController('node_type')->load($node->bundle())->label()), 'title' => $node->label($item->langcode), 'user' => drupal_render($username), 'date' => $node->getChangedTime(), 'node' => $node, 'extra' => $extra, 'score' => $item->calculated_score, 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), 'langcode' => $node->language()->id, ); } return $results; } /** * Gathers the rankings from the the hook_ranking() implementations. * * @param $query * A query object that has been extended with the Search DB Extender. */ protected function addNodeRankings(SelectExtender $query) { if ($ranking = $this->moduleHandler->invokeAll('ranking')) { $tables = &$query->getTables(); foreach ($ranking as $rank => $values) { // @todo - move rank out of drupal variables. if ($node_rank = variable_get('node_rank_' . $rank, 0)) { // If the table defined in the ranking isn't already joined, then add it. if (isset($values['join']) && !isset($tables[$values['join']['alias']])) { $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']); } $arguments = isset($values['arguments']) ? $values['arguments'] : array(); $query->addScore($values['score'], $arguments, $node_rank); } } } } /** * {@inheritdoc} */ public function updateIndex() { $limit = (int) $this->searchSettings->get('index.cron_limit'); $result = $this->database->queryRange("SELECT DISTINCT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = :type AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(':type' => $this->getPluginId()), array('target' => 'slave')); $nids = $result->fetchCol(); if (!$nids) { return; } // The indexing throttle should be aware of the number of language variants // of a node. $counter = 0; $node_storage = $this->entityManager->getStorageController('node'); foreach ($node_storage->loadMultiple($nids) as $node) { // Determine when the maximum number of indexable items is reached. $counter += count($node->getTranslationLanguages()); if ($counter > $limit) { break; } $this->indexNode($node); } } /** * Indexes a single node. * * @param \Drupal\Core\Entity\EntityInterface $node * The node to index. */ protected function indexNode(EntityInterface $node) { // Save the changed time of the most recent indexed node, for the search // results half-life calculation. $this->state->set('node.cron_last', $node->getChangedTime()); $languages = $node->getTranslationLanguages(); $node_render = $this->entityManager->getRenderController('node'); foreach ($languages as $language) { // Render the node. $build = $node_render->view($node, 'search_index', $language->id); unset($build['#theme']); $node->rendered = drupal_render($build); $text = '