CoreNodeSearchFacetSource.php 10.1 KB
Newer Older
1 2
<?php

3
namespace Drupal\core_search_facets\Plugin\facets\facet_source;
4 5 6 7

use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
8 9 10
use Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface;
use Drupal\facets\FacetInterface;
use Drupal\facets\FacetSource\FacetSourcePluginBase;
11
use Drupal\facets\QueryType\QueryTypePluginManager;
12
use Drupal\field\Entity\FieldConfig;
13
use Drupal\field\Entity\FieldStorageConfig;
14
use Drupal\search\SearchPageInterface;
15
use Drupal\search\SearchPluginManager;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
17
use Symfony\Component\HttpFoundation\RequestStack;
18 19

/**
20
 * Represents a facet source which represents the Search API views.
21
 *
22
 * @FacetsFacetSource(
23
 *   id = "core_node_search",
24
 *   deriver = "Drupal\core_search_facets\Plugin\facets\facet_source\CoreNodeSearchFacetSourceDeriver"
25 26
 * )
 */
27
class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSearchFacetSourceInterface {
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

  use DependencySerializationTrait;
  use StringTranslationTrait;

  /**
   * The entity manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager|null
   */
  protected $entityTypeManager;

  /**
   * The typed data manager.
   *
   * @var \Drupal\Core\TypedData\TypedDataManager|null
   */
  protected $typedDataManager;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|null
   */
  protected $configFactory;

53 54 55 56 57
  /**
   * The plugin manager for core search plugins.
   *
   * @var \Drupal\search\SearchPluginManager
   */
58 59
  protected $searchManager;

60 61 62 63 64
  /**
   * The facet query being executed.
   */
  protected $facetQueryExtender;

65 66 67
  /**
   * {@inheritdoc}
   */
68
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, QueryTypePluginManager $query_type_plugin_manager, SearchPluginManager $search_manager, RequestStack $request_stack) {
69 70
    parent::__construct($configuration, $plugin_id, $plugin_definition, $query_type_plugin_manager);
    $this->searchManager = $search_manager;
71
    $this->setSearchKeys($request_stack->getMasterRequest()->query->get('keys'));
72 73 74 75 76 77
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
78 79 80
    /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
    $request_stack = $container->get('request_stack');

81 82 83 84
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
85
      $container->get('plugin.manager.facets.query_type'),
86 87
      $container->get('plugin.manager.search'),
      $request_stack
88 89 90 91 92 93 94
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getPath() {
95 96 97
    $request = \Drupal::requestStack()->getMasterRequest();
    $search_page = $request->attributes->get('entity');
    if ($search_page instanceof SearchPageInterface) {
98
      return '/search/' . $search_page->getPath();
99
    }
100
    return '/';
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
  }

  /**
   * {@inheritdoc}
   */
  public function fillFacetsWithResults($facets) {
    foreach ($facets as $facet) {
      $configuration = array(
        'query' => NULL,
        'facet' => $facet,
      );

      // Get the Facet Specific Query Type so we can process the results
      // using the build() function of the query type.
      $query_type = $this->queryTypePluginManager->createInstance($facet->getQueryType(), $configuration);
      $query_type->build();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getQueryTypesForFacet(FacetInterface $facet) {
124 125 126 127 128 129 130 131
    // Verify if the field exists. Otherwise the type will be a column
    // (type,uid...) from the node and we can use the field identifier directly.
    if ($field = FieldStorageConfig::loadByName('node', $facet->getFieldIdentifier())) {
      $field_type = $field->getType();
    }
    else {
      $field_type = $facet->getFieldIdentifier();
    }
132

133
    return $this->getQueryTypesForFieldType($field_type);
134 135 136
  }

  /**
137
   * Get the query types for a field type.
borisson_'s avatar
borisson_ committed
138
   *
139 140
   * @param string $field_type
   *   The field type.
borisson_'s avatar
borisson_ committed
141
   *
142
   * @return array
borisson_'s avatar
borisson_ committed
143
   *   An array of query types.
144
   */
145
  protected function getQueryTypesForFieldType($field_type) {
146
    $query_types = [];
147
    switch ($field_type) {
148 149
      case 'type':
      case 'uid':
150
      case 'langcode':
151
      case 'integer':
152
      case 'entity_reference':
153
        $query_types['string'] = 'core_node_search_string';
154
        break;
155 156

      case 'created':
157
      case 'changed':
158 159 160
        $query_types['string'] = 'core_node_search_date';
        break;

161 162 163 164 165 166 167 168 169 170 171 172
    }

    return $query_types;
  }

  /**
   * {@inheritdoc}
   */
  public function isRenderedInCurrentRequest() {
    $request = \Drupal::requestStack()->getMasterRequest();
    $search_page = $request->attributes->get('entity');
    if ($search_page instanceof SearchPageInterface) {
173
      $facet_source_id = 'core_node_search:' . $search_page->id();
174 175 176 177 178 179 180 181 182 183 184
      if ($facet_source_id == $this->getPluginId()) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
185
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
186 187 188 189 190 191 192

    $form['field_identifier'] = [
      '#type' => 'select',
      '#options' => $this->getFields(),
      '#title' => $this->t('Facet field'),
      '#description' => $this->t('Choose the indexed field.'),
      '#required' => TRUE,
193
      '#default_value' => $this->facet->getFieldIdentifier(),
194 195 196 197 198 199 200 201 202
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getFields() {
203 204
    // Default fields.
    $facet_fields = $this->getDefaultFields();
205 206 207 208

    // Get the allowed field types.
    $allowed_field_types = \Drupal::moduleHandler()->invokeAll('facets_core_allowed_field_types', array($field_types = []));

209 210
    // Get the current field instances and detect if the field type is allowed.
    $fields = FieldConfig::loadMultiple();
211
    /** @var \Drupal\Field\FieldConfigInterface $field */
212
    foreach ($fields as $field) {
213 214
      // Verify if the target type is allowed for entity reference fields,
      // otherwise verify the field type(i.e. integer, float...).
215 216 217
      $target_is_allowed = in_array($field->getFieldStorageDefinition()->getSetting('target_type'), $allowed_field_types);
      $field_is_allowed = in_array($field->getFieldStorageDefinition()->getType(), $allowed_field_types);
      if ($target_is_allowed || $field_is_allowed) {
218
        /** @var \Drupal\field\Entity\FieldConfig $field */
219 220 221
        if (!array_key_exists($field->getName(), $facet_fields)) {
          $facet_fields[$field->getName()] = $this->t('@label', ['@label' => $field->getLabel()]);
        }
222 223
      }
    }
224

225 226 227 228 229 230 231
    return $facet_fields;
  }

  /**
   * Getter for default node fields.
   *
   * @return array
232
   *   An array containing the default fields enabled on a node.
233 234 235 236 237 238
   */
  protected function getDefaultFields() {
    return [
      'type' => $this->t('Content Type'),
      'uid' => $this->t('Author'),
      'langcode' => $this->t('Language'),
239
      'created' => $this->t('Post date'),
240
      'changed' => $this->t('Updated date'),
241
    ];
242 243
  }

244 245 246 247
  /**
   * {@inheritdoc}
   */
  public function getFacetQueryExtender() {
248 249
    if (!$this->facetQueryExtender) {
      $this->facetQueryExtender = db_select('search_index', 'i', array('target' => 'replica'))->extend('Drupal\core_search_facets\FacetsQuery');
250
      $this->facetQueryExtender->join('node_field_data', 'n', 'n.nid = i.sid AND n.langcode = i.langcode');
251
      $this->facetQueryExtender
252 253 254
         // ->condition('n.status', 1).
         ->addTag('node_access')
         ->searchExpression($this->keys, 'node_search');
255
    }
256 257 258 259 260 261 262
    return $this->facetQueryExtender;
  }

  /**
   * {@inheritdoc}
   */
  public function getQueryInfo(FacetInterface $facet) {
263 264 265 266
    $query_info = [];
    $field_name = $facet->getFieldIdentifier();
    $default_fields = $this->getDefaultFields();
    if (array_key_exists($facet->getFieldIdentifier(), $default_fields)) {
267 268 269 270
      // We add the language code of the indexed item to the result of the
      // query. So in this case we need to use the search_index table alias (i)
      // for the langcode field. Otherwise we will have same nid for multiple
      // languages as result. For more details see NodeSearch::findResults().
271 272 273 274 275 276 277 278
      // @TODO review if I can refactor this.
      $table_alias = $facet->getFieldIdentifier() == 'langcode' ? 'i' : 'n';
      $query_info = [
        'fields' => [
          $table_alias . '.' . $facet->getFieldIdentifier() => [
            'table_alias' => $table_alias,
            'field' => $facet->getFieldIdentifier(),
          ],
279
        ],
280
      ];
borisson_'s avatar
borisson_ committed
281
    }
282 283 284
    else {
      // Gets field info, finds table name and field name.
      $table = "node__{$field_name}";
285 286 287 288 289 290 291 292 293 294 295 296
      // The column name will be different depending on the field type, it's
      // always the fields machine name, suffixed with '_value'. Entity
      // reference fields change that suffix into '_target_id'.
      $field_config = FieldStorageConfig::loadByName('node', $facet->getFieldIdentifier());
      $field_type = $field_config->getType();
      if ($field_type == 'entity_reference') {
        $column = $facet->getFieldIdentifier() . '_target_id';
      }
      else {
        $column = $facet->getFieldIdentifier() . '_value';
      }

297 298 299 300
      $query_info['fields'][$field_name . '.' . $column] = array(
        'table_alias' => $table,
        'field' => $column,
      );
301

302 303 304 305 306
      // Adds the join on the node table.
      $query_info['joins'] = array(
        $table => array(
          'table' => $table,
          'alias' => $table,
307
          'condition' => "n.vid = $table.revision_id AND i.langcode = $table.langcode",
308 309 310
        ),
      );
    }
311 312 313 314 315 316 317 318

    // Returns query info, makes sure all keys are present.
    return $query_info + [
      'joins' => [],
      'fields' => [],
    ];
  }

319
  /**
borisson_'s avatar
borisson_ committed
320 321
   * Checks if the search has facets.
   *
322 323 324
   * @TODO move to the Base class???
   */
  public function hasFacets() {
325
    $manager = \Drupal::service('entity_type.manager')->getStorage('facets_facet');
326
    $facets = $manager->loadMultiple();
borisson_'s avatar
borisson_ committed
327
    foreach ($facets as $facet) {
328 329 330 331 332 333 334
      if ($facet->getFacetSourceId() == $this->getPluginId()) {
        return TRUE;
      }
    }
    return FALSE;
  }

335
}