/**
  * Tests processing stages.
  */
 public function testGetProcessingStages()
 {
     $namespaces = new ArrayObject();
     $sut = new ProcessorPluginManager($namespaces, $this->cache, $this->moduleHandler, $this->translator);
     $stages = [ProcessorInterface::STAGE_PRE_QUERY, ProcessorInterface::STAGE_POST_QUERY, ProcessorInterface::STAGE_BUILD];
     $this->assertEquals($stages, array_keys($sut->getProcessingStages()));
 }
 /**
  * Tests plugin manager's getDefinitions method.
  */
 public function testGetDefinitions()
 {
     $definitions = array('foo' => array('label' => $this->randomMachineName()));
     $this->discovery->expects($this->once())->method('getDefinitions')->willReturn($definitions);
     $this->assertSame($definitions, $this->sut->getDefinitions());
 }
Example #3
0
 /**
  * {@inheritdoc}
  */
 public function form(array $form, FormStateInterface $form_state)
 {
     $form['#attached']['library'][] = 'facets/drupal.facets.admin_css';
     /** @var \Drupal\facets\FacetInterface $facet */
     $facet = $this->entity;
     $widget_options = [];
     foreach ($this->getWidgetPluginManager()->getDefinitions() as $widget_id => $definition) {
         $widget_options[$widget_id] = !empty($definition['label']) ? $definition['label'] : $widget_id;
     }
     $form['widget'] = ['#type' => 'radios', '#title' => $this->t('Widget'), '#description' => $this->t('The widget used for displaying this facet.'), '#options' => $widget_options, '#default_value' => $facet->getWidget(), '#required' => TRUE, '#ajax' => ['trigger_as' => ['name' => 'widget_configure'], 'callback' => '::buildAjaxWidgetConfigForm', 'wrapper' => 'facets-widget-config-form', 'method' => 'replace', 'effect' => 'fade']];
     $form['widget_configs'] = ['#type' => 'container', '#attributes' => ['id' => 'facets-widget-config-form'], '#tree' => TRUE];
     $form['widget_configure_button'] = ['#type' => 'submit', '#name' => 'widget_configure', '#value' => $this->t('Configure widget'), '#limit_validation_errors' => [['widget']], '#submit' => ['::submitAjaxWidgetConfigForm'], '#ajax' => ['callback' => '::buildAjaxWidgetConfigForm', 'wrapper' => 'facets-widget-config-form'], '#attributes' => ['class' => ['js-hide']]];
     $this->buildWidgetConfigForm($form, $form_state);
     // Retrieve lists of all processors, and the stages and weights they have.
     if (!$form_state->has('processors')) {
         $all_processors = $facet->getProcessors(FALSE);
         $sort_processors = function (ProcessorInterface $a, ProcessorInterface $b) {
             return strnatcasecmp((string) $a->getPluginDefinition()['label'], (string) $b->getPluginDefinition()['label']);
         };
         uasort($all_processors, $sort_processors);
     } else {
         $all_processors = $form_state->get('processors');
     }
     $enabled_processors = $facet->getProcessors(TRUE);
     $stages = $this->processorPluginManager->getProcessingStages();
     $processors_by_stage = array();
     foreach ($stages as $stage => $definition) {
         $processors_by_stage[$stage] = $facet->getProcessorsByStage($stage, FALSE);
     }
     $form['#tree'] = TRUE;
     $form['#attached']['library'][] = 'facets/drupal.facets.index-active-formatters';
     $form['#title'] = $this->t('Edit %label facet', array('%label' => $facet->label()));
     // Add the list of all other processors with checkboxes to enable/disable
     // them.
     $form['facet_settings'] = array('#type' => 'fieldset', '#title' => $this->t('Facet settings'), '#attributes' => array('class' => array('search-api-status-wrapper')));
     foreach ($all_processors as $processor_id => $processor) {
         if (!$processor instanceof WidgetOrderProcessorInterface && !$processor instanceof UrlProcessorInterface) {
             $clean_css_id = Html::cleanCssIdentifier($processor_id);
             $form['facet_settings'][$processor_id]['status'] = array('#type' => 'checkbox', '#title' => (string) $processor->getPluginDefinition()['label'], '#default_value' => $processor->isLocked() || !empty($enabled_processors[$processor_id]), '#description' => $processor->getDescription(), '#attributes' => array('class' => array('search-api-processor-status-' . $clean_css_id), 'data-id' => $clean_css_id), '#disabled' => $processor->isLocked(), '#access' => !$processor->isHidden());
             $processor_form_state = new SubFormState($form_state, ['facet_settings', $processor_id, 'settings']);
             $processor_form = $processor->buildConfigurationForm($form, $processor_form_state, $facet);
             if ($processor_form) {
                 $form['facet_settings'][$processor_id]['settings'] = array('#type' => 'details', '#title' => $this->t('%processor settings', ['%processor' => (string) $processor->getPluginDefinition()['label']]), '#open' => TRUE, '#attributes' => array('class' => array('facets-processor-settings-' . Html::cleanCssIdentifier($processor_id), 'facets-processor-settings-facet', 'facets-processor-settings')), '#states' => array('visible' => array(':input[name="facet_settings[' . $processor_id . '][status]"]' => array('checked' => TRUE))));
                 $form['facet_settings'][$processor_id]['settings'] += $processor_form;
             }
         }
     }
     // Add the list of widget sort processors with checkboxes to enable/disable
     // them.
     $form['facet_sorting'] = array('#type' => 'fieldset', '#title' => $this->t('Facet sorting'), '#attributes' => array('class' => array('search-api-status-wrapper')));
     foreach ($all_processors as $processor_id => $processor) {
         if ($processor instanceof WidgetOrderProcessorInterface) {
             $clean_css_id = Html::cleanCssIdentifier($processor_id);
             $form['facet_sorting'][$processor_id]['status'] = array('#type' => 'checkbox', '#title' => (string) $processor->getPluginDefinition()['label'], '#default_value' => $processor->isLocked() || !empty($enabled_processors[$processor_id]), '#description' => $processor->getDescription(), '#attributes' => array('class' => array('search-api-processor-status-' . $clean_css_id), 'data-id' => $clean_css_id), '#disabled' => $processor->isLocked(), '#access' => !$processor->isHidden());
             $processor_form_state = new SubFormState($form_state, array('facet_sorting', $processor_id, 'settings'));
             $processor_form = $processor->buildConfigurationForm($form, $processor_form_state, $facet);
             if ($processor_form) {
                 $form['facet_sorting'][$processor_id]['settings'] = array('#type' => 'container', '#open' => TRUE, '#attributes' => array('class' => array('facets-processor-settings-' . Html::cleanCssIdentifier($processor_id), 'facets-processor-settings-sorting', 'facets-processor-settings')), '#states' => array('visible' => array(':input[name="facet_sorting[' . $processor_id . '][status]"]' => array('checked' => TRUE))));
                 $form['facet_sorting'][$processor_id]['settings'] += $processor_form;
             }
         }
     }
     $form['facet_settings']['only_visible_when_facet_source_is_visible'] = ['#type' => 'checkbox', '#title' => $this->t('Hide facet when facet source is not rendered'), '#description' => $this->t('When checked, this facet will only be rendered when the facet source is rendered.  If you want to show facets on other pages too, you need to uncheck this setting.'), '#default_value' => $facet->getOnlyVisibleWhenFacetSourceIsVisible()];
     $form['facet_settings']['show_only_one_result'] = ['#type' => 'checkbox', '#title' => $this->t('Make sure only one result can be shown.'), '#description' => $this->t('When checked, this will make sure that only one result can be selected for this facet at one time.'), '#default_value' => $facet->getShowOnlyOneResult()];
     $form['facet_settings']['url_alias'] = ['#type' => 'machine_name', '#title' => $this->t('Url alias'), '#default_value' => $facet->getUrlAlias(), '#maxlength' => 50, '#required' => TRUE, '#machine_name' => ['exists' => [\Drupal::service('entity_type.manager')->getStorage('facets_facet'), 'load'], 'source' => ['name']]];
     $empty_behavior_config = $facet->getEmptyBehavior();
     $form['facet_settings']['empty_behavior'] = ['#type' => 'radios', '#title' => t('Empty facet behavior'), '#default_value' => $empty_behavior_config['behavior'] ?: 'none', '#options' => ['none' => t('Do not display facet'), 'text' => t('Display text')], '#description' => $this->t('The action to take when a facet has no items.'), '#required' => TRUE];
     $form['facet_settings']['empty_behavior_container'] = ['#type' => 'container', '#states' => array('visible' => array(':input[name="facet_settings[empty_behavior]"]' => array('value' => 'text')))];
     $form['facet_settings']['empty_behavior_container']['empty_behavior_text'] = ['#type' => 'text_format', '#title' => $this->t('Empty text'), '#format' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text_format'] : 'plain_text', '#editor' => TRUE, '#default_value' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text'] : ''];
     $form['facet_settings']['query_operator'] = ['#type' => 'radios', '#title' => $this->t('Operator'), '#options' => ['OR' => $this->t('OR'), 'AND' => $this->t('AND')], '#description' => $this->t('AND filters are exclusive and narrow the result set. OR filters are inclusive and widen the result set.'), '#default_value' => $facet->getQueryOperator()];
     $form['facet_settings']['exclude'] = ['#type' => 'checkbox', '#title' => $this->t('Exclude'), '#description' => $this->t('Make the search exclude selected facets, instead of restricting it to them.'), '#default_value' => $facet->getExclude()];
     $form['facet_settings']['weight'] = ['#type' => 'number', '#title' => $this->t('Weight'), '#default_value' => $facet->getWeight(), '#maxlength' => 4, '#required' => TRUE];
     $form['weights'] = array('#type' => 'details', '#title' => t('Advanced settings'), '#collapsible' => TRUE, '#collapsed' => TRUE);
     $form['weights']['order'] = ['#markup' => "<h3>" . t('Processor order') . "</h3>"];
     // Order enabled processors per stage, create all the containers for the
     // different stages.
     foreach ($stages as $stage => $description) {
         $form['weights'][$stage] = array('#type' => 'fieldset', '#title' => $description['label'], '#attributes' => array('class' => array('search-api-stage-wrapper', 'search-api-stage-wrapper-' . Html::cleanCssIdentifier($stage))));
         $form['weights'][$stage]['order'] = array('#type' => 'table');
         $form['weights'][$stage]['order']['#tabledrag'][] = array('action' => 'order', 'relationship' => 'sibling', 'group' => 'search-api-processor-weight-' . Html::cleanCssIdentifier($stage));
     }
     $processor_settings = $facet->getProcessorConfigs();
     // Fill in the containers previously created with the processors that are
     // enabled on the facet.
     foreach ($processors_by_stage as $stage => $processors) {
         /** @var \Drupal\facets\Processor\ProcessorInterface $processor */
         foreach ($processors as $processor_id => $processor) {
             $weight = isset($processor_settings[$processor_id]['weights'][$stage]) ? $processor_settings[$processor_id]['weights'][$stage] : $processor->getDefaultWeight($stage);
             if ($processor->isHidden()) {
                 $form['processors'][$processor_id]['weights'][$stage] = array('#type' => 'value', '#value' => $weight);
                 continue;
             }
             $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'draggable';
             $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'search-api-processor-weight--' . Html::cleanCssIdentifier($processor_id);
             $form['weights'][$stage]['order'][$processor_id]['#weight'] = $weight;
             $form['weights'][$stage]['order'][$processor_id]['label']['#plain_text'] = (string) $processor->getPluginDefinition()['label'];
             $form['weights'][$stage]['order'][$processor_id]['weight'] = array('#type' => 'weight', '#title' => $this->t('Weight for processor %title', array('%title' => (string) $processor->getPluginDefinition()['label'])), '#title_display' => 'invisible', '#default_value' => $weight, '#parents' => array('processors', $processor_id, 'weights', $stage), '#attributes' => array('class' => array('search-api-processor-weight-' . Html::cleanCssIdentifier($stage))));
         }
     }
     // Add vertical tabs containing the settings for the processors. Tabs for
     // disabled processors are hidden with JS magic, but need to be included in
     // case the processor is enabled.
     $form['processor_settings'] = array('#title' => $this->t('Processor settings'), '#type' => 'vertical_tabs');
     return $form;
 }
 /**
  * Builds a facet and returns it as a renderable array.
  *
  * This method delegates to the relevant plugins to render a facet, it calls
  * out to a widget plugin to do the actual rendering when results are found.
  * When no results are found it calls out to the correct empty result plugin
  * to build a render array.
  *
  * Before doing any rendering, the processors that implement the
  * BuildProcessorInterface enabled on this facet will run.
  *
  * @param \Drupal\facets\FacetInterface $facet
  *   The facet we should build.
  *
  * @return array
  *   Facet render arrays.
  *
  * @throws \Drupal\facets\Exception\InvalidProcessorException
  *   Throws an exception when an invalid processor is linked to the facet.
  */
 public function build(FacetInterface $facet)
 {
     // It might be that the facet received here, is not the same as the already
     // loaded facets in the FacetManager.
     // For that reason, get the facet from the already loaded facets in the
     // FacetManager.
     $facet = $this->facets[$facet->id()];
     $facet_source_id = $facet->getFacetSourceId();
     if ($facet->getOnlyVisibleWhenFacetSourceIsVisible()) {
         // Block rendering and processing should be stopped when the facet source
         // is not available on the page. Returning an empty array here is enough
         // to halt all further processing.
         $facet_source = $facet->getFacetSource();
         if (!$facet_source->isRenderedInCurrentRequest()) {
             return [];
         }
     }
     // For clarity, process facets is called each build.
     // The first facet therefor will trigger the processing. Note that
     // processing is done only once, so repeatedly calling this method will not
     // trigger the processing more than once.
     $this->processFacets($facet_source_id);
     // Get the current results from the facets and let all processors that
     // trigger on the build step do their build processing.
     // @see \Drupal\facets\Processor\BuildProcessorInterface.
     // @see \Drupal\facets\Processor\WidgetOrderProcessorInterface.
     $results = $facet->getResults();
     $active_sorts = [];
     // Load all processors, because getProcessorsByStage does not return the
     // correct configuration for the processors.
     // @todo: Fix when https://www.drupal.org/node/2722267 is fixed.
     $processors = $facet->getProcessors();
     foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_BUILD) as $processor) {
         /** @var \Drupal\facets\Processor\BuildProcessorInterface $build_processor */
         $build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
         if ($build_processor instanceof WidgetOrderProcessorInterface) {
             // Sorting is handled last and together, to support nested sorts.
             $active_sorts[] = $processors[$build_processor->getPluginId()];
         } else {
             if (!$build_processor instanceof BuildProcessorInterface) {
                 throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a build definition but doesn't implement the required BuildProcessorInterface interface");
             }
             $results = $build_processor->build($facet, $results);
         }
     }
     uasort($results, function ($a, $b) use($active_sorts) {
         $return = 0;
         foreach ($active_sorts as $sort) {
             if ($return = $sort->sortResults($a, $b)) {
                 if ($sort->getConfiguration()['sort'] == 'DESC') {
                     $return *= -1;
                 }
                 break;
             }
         }
         return $return;
     });
     $facet->setResults($results);
     // No results behavior handling. Return a custom text or false depending on
     // settings.
     if (empty($facet->getResults())) {
         $empty_behavior = $facet->getEmptyBehavior();
         if ($empty_behavior['behavior'] == 'text') {
             return [['#markup' => $empty_behavior['text']]];
         } else {
             return [];
         }
     }
     // Let the widget plugin render the facet.
     /** @var \Drupal\facets\Widget\WidgetInterface $widget */
     $widget = $this->widgetPluginManager->createInstance($facet->getWidget());
     return [$widget->build($facet)];
 }