-
-
Save StryKaizer/ae1cb9abc4844a9e7ac12317a9d84a78 to your computer and use it in GitHub Desktop.
<?php | |
namespace Drupal\yourmodule\Plugin\views\filter; | |
use Drupal\Core\Entity\Element\EntityAutocomplete; | |
use Drupal\Core\Entity\EntityStorageInterface; | |
use Drupal\Core\Form\FormStateInterface; | |
use Drupal\node\Entity\Node; | |
use Drupal\node\NodeStorageInterface; | |
use Drupal\views\ViewExecutable; | |
use Drupal\views\Plugin\views\display\DisplayPluginBase; | |
use Drupal\views\Plugin\views\filter\ManyToOne; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
/** | |
* Filter by node id. | |
* | |
* @ingroup views_filter_handlers | |
* | |
* @ViewsFilter("node_index_nid") | |
*/ | |
class NodeIndexNid extends ManyToOne { | |
// Stores the exposed input for this filter. | |
public $validated_exposed_input = NULL; | |
/** | |
* NodeType storage handler. | |
* | |
* @var \Drupal\Core\Entity\EntityStorageInterface | |
*/ | |
protected $nodeTypeStorage; | |
/** | |
* The node storage. | |
* | |
* @var \Drupal\node\NodeStorageInterface | |
*/ | |
protected $nodeStorage; | |
/** | |
* Constructs a NodeIndexNid 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 mixed $plugin_definition | |
* The plugin implementation definition. | |
* @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage | |
* The node storage. | |
* @param \Drupal\node\NodeStorageInterface $node_storage | |
* The node storage. | |
*/ | |
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $node_type_storage, NodeStorageInterface $node_storage) { | |
parent::__construct($configuration, $plugin_id, $plugin_definition); | |
$this->nodeTypeStorage = $node_type_storage; | |
$this->nodeStorage = $node_storage; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
return new static( | |
$configuration, | |
$plugin_id, | |
$plugin_definition, | |
$container->get('entity.manager')->getStorage('node_type'), | |
$container->get('entity.manager')->getStorage('node') | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { | |
parent::init($view, $display, $options); | |
if (!empty($this->definition['node'])) { | |
$this->options['bundle'] = $this->definition['node']; | |
} | |
} | |
public function hasExtraOptions() { | |
return TRUE; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getValueOptions() { | |
return $this->valueOptions; | |
} | |
protected function defineOptions() { | |
$options = parent::defineOptions(); | |
$options['type'] = array('default' => 'textfield'); | |
$options['limit'] = array('default' => TRUE); | |
$options['bundle'] = array('default' => ''); | |
$options['error_message'] = array('default' => TRUE); | |
return $options; | |
} | |
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) { | |
$bundles = $this->nodeTypeStorage->loadMultiple(); | |
$options = array(); | |
foreach ($bundles as $bundle) { | |
$options[$bundle->id()] = $bundle->label(); | |
} | |
if ($this->options['limit']) { | |
// We only do this when the form is displayed. | |
if (empty($this->options['bundle'])) { | |
$first_bundle = reset($bundles); | |
$this->options['bundle'] = $first_bundle->id(); | |
} | |
if (empty($this->definition['bundle'])) { | |
$form['bundle'] = array( | |
'#type' => 'radios', | |
'#title' => $this->t('Bundle'), | |
'#options' => $options, | |
'#description' => $this->t('Select which bundle to show nodes for in the regular options.'), | |
'#default_value' => $this->options['bundle'], | |
); | |
} | |
} | |
$form['type'] = array( | |
'#type' => 'radios', | |
'#title' => $this->t('Selection type'), | |
'#options' => array( | |
'select' => $this->t('Dropdown'), | |
'textfield' => $this->t('Autocomplete') | |
), | |
'#default_value' => $this->options['type'], | |
); | |
} | |
protected function valueForm(&$form, FormStateInterface $form_state) { | |
$bundle = $this->nodeTypeStorage->load($this->options['bundle']); | |
if (empty($bundle) && $this->options['limit']) { | |
$form['markup'] = array( | |
'#markup' => '<div class="js-form-item form-item">' . $this->t('An invalid type is selected. Please change it in the options.') . '</div>', | |
); | |
return; | |
} | |
if ($this->options['type'] == 'textfield') { | |
$nodes = $this->value ? Node::loadMultiple(($this->value)) : array(); | |
$form['value'] = array( | |
'#title' => $this->options['limit'] ? $this->t('Select nodes from bundle @bundle', array('@bundle' => $bundle->label())) : $this->t('Select content'), | |
'#type' => 'textfield', | |
'#default_value' => EntityAutocomplete::getEntityLabels($nodes), | |
); | |
if ($this->options['limit']) { | |
$form['value']['#type'] = 'entity_autocomplete'; | |
$form['value']['#target_type'] = 'node'; | |
$form['value']['#selection_settings']['target_bundles'] = array($bundle->id()); | |
$form['value']['#tags'] = TRUE; | |
$form['value']['#process_default_value'] = FALSE; | |
} | |
} | |
else { | |
$options = array(); | |
$query = \Drupal::entityQuery('node') | |
// @todo Sorting on bundle properties - | |
->sort('title') | |
->addTag('node_access'); | |
if ($this->options['limit']) { | |
$query->condition('type', $bundle->id()); | |
} | |
$nodes = Node::loadMultiple($query->execute()); | |
foreach ($nodes as $node) { | |
$options[$node->id()] = \Drupal::entityManager() | |
->getTranslationFromContext($node) | |
->label(); | |
} | |
$default_value = (array) $this->value; | |
if ($exposed = $form_state->get('exposed')) { | |
$identifier = $this->options['expose']['identifier']; | |
if (!empty($this->options['expose']['reduce'])) { | |
$options = $this->reduceValueOptions($options); | |
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) { | |
$default_value = array(); | |
} | |
} | |
if (empty($this->options['expose']['multiple'])) { | |
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) { | |
$default_value = 'All'; | |
} | |
elseif (empty($default_value)) { | |
$keys = array_keys($options); | |
$default_value = array_shift($keys); | |
} | |
// Due to #1464174 there is a chance that array('') was saved in the admin ui. | |
// Let's choose a safe default value. | |
elseif ($default_value == array('')) { | |
$default_value = 'All'; | |
} | |
else { | |
$copy = $default_value; | |
$default_value = array_shift($copy); | |
} | |
} | |
} | |
$form['value'] = array( | |
'#type' => 'select', | |
'#title' => $this->options['limit'] ? $this->t('Select nodes from bundle @bundle', array('@bundle' => $bundle->label())) : $this->t('Select content'), | |
'#multiple' => TRUE, | |
'#options' => $options, | |
'#size' => min(9, count($options)), | |
'#default_value' => $default_value, | |
); | |
$user_input = $form_state->getUserInput(); | |
if ($exposed && isset($identifier) && !isset($user_input[$identifier])) { | |
$user_input[$identifier] = $default_value; | |
$form_state->setUserInput($user_input); | |
} | |
} | |
if (!$form_state->get('exposed')) { | |
// Retain the helper option | |
$this->helper->buildOptionsForm($form, $form_state); | |
// Show help text if not exposed to end users. | |
$form['value']['#description'] = t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".'); | |
} | |
} | |
protected function valueValidate($form, FormStateInterface $form_state) { | |
// We only validate if they've chosen the text field style. | |
if ($this->options['type'] != 'textfield') { | |
return; | |
} | |
$tids = array(); | |
if ($values = $form_state->getValue(array('options', 'value'))) { | |
foreach ($values as $value) { | |
$tids[] = $value['target_id']; | |
} | |
} | |
$form_state->setValue(array('options', 'value'), $tids); | |
} | |
public function acceptExposedInput($input) { | |
if (empty($this->options['exposed'])) { | |
return TRUE; | |
} | |
// We need to know the operator, which is normally set in | |
// \Drupal\views\Plugin\views\filter\FilterPluginBase::acceptExposedInput(), | |
// before we actually call the parent version of ourselves. | |
if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) { | |
$this->operator = $input[$this->options['expose']['operator_id']]; | |
} | |
// If view is an attachment and is inheriting exposed filters, then assume | |
// exposed input has already been validated | |
if (!empty($this->view->is_attachment) && $this->view->display_handler->usesExposed()) { | |
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']]; | |
} | |
// If we're checking for EMPTY or NOT, we don't need any input, and we can | |
// say that our input conditions are met by just having the right operator. | |
if ($this->operator == 'empty' || $this->operator == 'not empty') { | |
return TRUE; | |
} | |
// If it's non-required and there's no value don't bother filtering. | |
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) { | |
return FALSE; | |
} | |
$rc = parent::acceptExposedInput($input); | |
if ($rc) { | |
// If we have previously validated input, override. | |
if (isset($this->validated_exposed_input)) { | |
$this->value = $this->validated_exposed_input; | |
} | |
} | |
return $rc; | |
} | |
public function validateExposed(&$form, FormStateInterface $form_state) { | |
if (empty($this->options['exposed'])) { | |
return; | |
} | |
$identifier = $this->options['expose']['identifier']; | |
// We only validate if they've chosen the text field style. | |
if ($this->options['type'] != 'textfield') { | |
if ($form_state->getValue($identifier) != 'All') { | |
$this->validated_exposed_input = (array) $form_state->getValue($identifier); | |
} | |
return; | |
} | |
if (empty($this->options['expose']['identifier'])) { | |
return; | |
} | |
if ($values = $form_state->getValue($identifier)) { | |
foreach ($values as $value) { | |
$this->validated_exposed_input[] = $value['target_id']; | |
} | |
} | |
} | |
protected function valueSubmit($form, FormStateInterface $form_state) { | |
// prevent array_filter from messing up our arrays in parent submit. | |
} | |
public function buildExposeForm(&$form, FormStateInterface $form_state) { | |
parent::buildExposeForm($form, $form_state); | |
if ($this->options['type'] != 'select') { | |
unset($form['expose']['reduce']); | |
} | |
$form['error_message'] = array( | |
'#type' => 'checkbox', | |
'#title' => $this->t('Display error message'), | |
'#default_value' => !empty($this->options['error_message']), | |
); | |
} | |
public function adminSummary() { | |
// set up $this->valueOptions for the parent summary | |
$this->valueOptions = array(); | |
if ($this->value) { | |
$this->value = array_filter($this->value); | |
$nodes = Node::loadMultiple($this->value); | |
foreach ($nodes as $node) { | |
$this->valueOptions[$node->id()] = \Drupal::entityManager() | |
->getTranslationFromContext($node) | |
->label(); | |
} | |
} | |
return parent::adminSummary(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCacheContexts() { | |
$contexts = parent::getCacheContexts(); | |
// The result potentially depends on term access and so is just cacheable | |
// per user. | |
// @todo See https://www.drupal.org/node/2352175. | |
$contexts[] = 'user'; | |
return $contexts; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function calculateDependencies() { | |
$dependencies = parent::calculateDependencies(); | |
$bundle = $this->nodeTypeStorage->load($this->options['bundle']); | |
$dependencies[$bundle->getConfigDependencyKey()][] = $bundle->getConfigDependencyName(); | |
foreach ($this->nodeStorage->loadMultiple($this->options['value']) as $term) { | |
$dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName(); | |
} | |
return $dependencies; | |
} | |
} |
Below is based on SO Post:
You need to place this file in a custom module at yourmodule/src/Plugin/views/filter/NodeIndexNid.php
, and also implement hook_views_data_alter
in your yourmodule.module
file, like this:
/**
* Implements hook_field_views_data_alter().
*
* Views integration for entity reference fields which reference nodes.
* Adds a term relationship to the default field data.
*
* @see views_field_default_views_data()
*/
function yourmodule_field_views_data_alter(array &$data, FieldStorageConfigInterface $field_storage) {
if ($field_storage->getType() == 'entity_reference' && $field_storage->getSetting('target_type') == 'node') {
foreach ($data as $table_name => $table_data) {
foreach ($table_data as $field_name => $field_data) {
if (isset($field_data['filter']) && $field_name != 'delta') {
$data[$table_name][$field_name]['filter']['id'] = 'node_index_nid';
}
}
}
}
}
Hi,
I have done the suggested changes in the custom module as below, but could not manage to get the option of select list in the exposed filters (please note that I am also using BEF modules):
modules\custom\entitydropdown\config\schema\entitydropdown.schema.yml
modules\custom\entitydropdown\entitydropdown.info.yml
modules\custom\entitydropdown\entitydropdown.module
modules\custom\entitydropdown\src\Plugin\views\filter\NodeIndexNid.php
With respect to code level I have not changed anything and utilizing as is uploded in github (namespace/hook name has been updated with appropriate module name i.e. entitydropdown).
Are you able to suggest please if I am missing any configurations ?
Thanks
I have implemented @ehsan-yaqubi comment and I encountered an error when I use FieldStorageConfigInterface, the error is suggesting me to use FieldStorageConfig instead:
use Drupal\field\Entity\FieldStorageConfig;
/**
* Implements hook_field_views_data_alter().
*
* Views integration for entity reference fields which reference nodes.
* Adds a term relationship to the default field data.
*
* @see views_field_default_views_data()
*/
function custom_node_view_filter_field_views_data_alter(array &$data, FieldStorageConfig $field_storage) {
if ($field_storage->getType() == 'entity_reference' && $field_storage->getSetting('target_type') == 'node') {
foreach ($data as $table_name => $table_data) {
foreach ($table_data as $field_name => $field_data) {
if (isset($field_data['filter']) && $field_name != 'delta') {
$data[$table_name][$field_name]['filter']['id'] = 'node_index_nid';
}
}
}
}
}
I too can't get it to work. I keep getting the following error message when trying to add a node reference field as exposed filter. I can't find how to fix it. It seems something is missing in the "definition", but I know too little about Views plugins to fix it at this point.
ErrorException: Notice: Undefined index: original_configuration in Drupal\views\Plugin\views\filter\Broken->buildOptionsForm() (line 56 of core/modules/views/src/Plugin/views/BrokenHandlerTrait.php).
Drupal\views_ui\Form\Ajax\ConfigHandler->buildForm(Array, Object)
call_user_func_array(Array, Array) (Line: 531)
Drupal\Core\Form\FormBuilder->retrieveForm('views_ui_config_item_form', Object) (Line: 278)
Drupal\Core\Form\FormBuilder->buildForm('Drupal\views_ui\Form\Ajax\ConfigHandler', Object) (Line: 215)
Drupal\views_ui\Form\Ajax\ViewsFormBase->Drupal\views_ui\Form\Ajax\{closure}() (Line: 564)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 217)
Drupal\views_ui\Form\Ajax\ViewsFormBase->ajaxFormWrapper('Drupal\views_ui\Form\Ajax\ConfigHandler', Object) (Line: 150)
Drupal\views_ui\Form\Ajax\ViewsFormBase->getForm(Object, 'index', 'ajax') (Line: 36)
Drupal\views_ui\Form\Ajax\AddHandler->getForm(Object, 'index', 'ajax', 'filter')
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 564)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 320)
Drupal\cleantalk\EventSubscriber\BootSubscriber->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 709)
Drupal\Core\DrupalKernel->handle(Object) (Line: 28)
I got it working in Drupal 10 - Thank you!!
Here is a diff showing the changes I had to make
Note also line 183 \Drupal::entityManager()->getTranslationFromContext($node)
should now be \Drupal::EntityRepository()->getTranslationFromContext($node)
but I could not get that to work so I'm just getting the untranslated Title.
0,71c71,72 < $container->get('entity.manager')->getStorage('node_type'), < $container->get('entity.manager')->getStorage('node') --- > $container->get('entity_type.manager')->getStorage('node_type'), > $container->get('entity_type.manager')->getStorage('node') 174a176 > ->accessCheck(TRUE) 180,183c182,183 < foreach ($nodes as $node) { < $options[$node->id()] = \Drupal::entityManager() < ->getTranslationFromContext($node) < ->label(); --- > foreach ($nodes as $node) { > $options[$node->id()] = $node->getTitle(); 217a218 >
You will need to add a schema file to declare the configuration property of this filter if you wish to save it with a default value. If not you will reach an exception when saving the view.
+/yourmodule/config/schema/yourmodule.schema.yml