Skip to content

Instantly share code, notes, and snippets.

@hussainweb
Created December 12, 2019 23:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save hussainweb/960b43f7b32ba54942ffcb85ca453996 to your computer and use it in GitHub Desktop.
Save hussainweb/960b43f7b32ba54942ffcb85ca453996 to your computer and use it in GitHub Desktop.
/web/modules/custom/axl_ks_topics/src/Plugin/EntityReferenceSelection/TopicsSelection.php
<?php
namespace Drupal\axl_ks_topics\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an entity reference selection for topics.
*
* @EntityReferenceSelection(
* id = "axl_ks_topics",
* label = @Translation("Unscheduled topics for an event"),
* entity_types = {"node"},
* group = "axl_ks_topics",
* weight = 0
* )
*/
class TopicsSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Active database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a new selection 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\EntityTypeManagerInterface $entityTypeManager
* The entity manager service.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entityTypeManager;
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('database')
);
}
/**
* Builds an EntityQuery to get referenceable topic entities.
*
* @param string|null $match
* Text to match the label against.
* @param string $match_operator
* The operation the matching should be done with.
* @param int $eventId
* The current enitity id.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The query object that can query the given entity type.
*/
protected function buildEntityQueryForTopics($match = NULL, $match_operator = 'CONTAINS', int $eventId = 0): QueryInterface {
$query = $this->entityTypeManager->getStorage('node')->getQuery();
$query->condition('type', 'topic');
$topics = $this->getReferenceableTopics($eventId);
if (!$topics) {
// If there are no topics, force the query to return an empty result.
$query->condition('nid', NULL, '=');
return $query;
}
$query->condition('nid', $topics, 'IN');
if (isset($match)) {
$query->condition('title', $match, $match_operator);
}
// Add entity-access tag.
$query->addTag('node_access');
// Add the Selection handler for system_query_entity_reference_alter().
$query->addTag('entity_reference');
$query->addMetaData('entity_reference_selection_handler', $this);
return $query;
}
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$query = $this->buildEntityQueryForTopics($match, $match_operator, $this->getParentEntityId());
if ($limit > 0) {
$query->range(0, $limit);
}
$result = $query->execute();
if (empty($result)) {
return [];
}
$options = [];
$entities = $this->entityTypeManager->getStorage('node')->loadMultiple($result);
$termStorage = $this->entityTypeManager->getStorage('taxonomy_term');
$userStorage = $this->entityTypeManager->getStorage('user');
foreach ($entities as $entity_id => $entity) {
$topic_type = $termStorage->load($entity->get('field_topic_type')->target_id);
$presenter = $userStorage->load($entity->get('field_topic_presenters')->target_id);
$duration = $entity->get('field_topic_duration')->value;
$options['topic'][$entity_id] = Html::escape(sprintf("%s - %s (%d mins, %s)", $entity->label(), $presenter->label(), $duration, $topic_type->label()));
}
return $options;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$query = $this->buildEntityQueryForTopics($match, $match_operator, $this->getParentEntityId());
return $query
->count()
->execute();
}
/**
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
$result = [];
if ($ids) {
$query = $this->buildEntityQueryForTopics(NULL, NULL, $this->getParentEntityId());
$result = $query
->condition('nid', $ids, 'IN')
->execute();
}
return $result;
}
/**
* Gets the list of topic referenceable entities.
*
* @param int $eventId
* The event ID for which topics are allowed to be referenced.
*
* @return mixed
* The query result.
*/
protected function getReferenceableTopics(int $eventId) {
// @todo: Convert this to a dynamic query.
$eventTypeTid = $this->getEventTypeTermId();
if ($eventTypeTid) {
$eventTypeSql = " AND topic_type.field_topic_type_target_id = '$eventTypeTid' ";
}
$sql = <<<SQL
SELECT topic.nid FROM {node_field_data} topic
LEFT JOIN {node__field_topic_type} topic_type ON topic_type.entity_id = topic.nid
LEFT JOIN {node__field_event_topics} event_topic ON event_topic.field_event_topics_target_id = topic.nid
WHERE topic.type = 'topic'
$eventTypeSql
AND (event_topic.field_event_topics_target_id IS NULL OR event_topic.entity_id = :event_id)
SQL;
$result = $this->database->query($sql, [':event_id' => $eventId]);
$nids = $result->fetchCol();
return $nids;
}
/**
* Get the entity ID of the node with the ER plugin.
*
* @return int
* Entity ID of the parent or 0 if there is no stored entity yet.
*/
protected function getParentEntityId(): int {
$config = $this->getConfiguration();
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $config['entity'];
return $entity ? (int) $entity->id() : 0;
}
/**
* Get the term ID for the event type of the currently loaded event.
*
* @return int|null
* Event type term-id of the current entity.
*/
protected function getEventTypeTermId(): ?int {
$config = $this->getConfiguration();
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $config['entity'];
if (!$entity) {
return NULL;
}
$event_topic_type = $entity->get('field_event_topic_type')->getValue();
return $event_topic_type[0]['target_id'] ?? NULL;
}
}
@behlhrsh
Copy link

NIce code, But following this on my local system is not working. My requirement was to filter out unpublished nodes, but my plugin is not getting called.

@hussainweb
Copy link
Author

NIce code, But following this on my local system is not working. My requirement was to filter out unpublished nodes, but my plugin is not getting called.

@behlhrsh, since this plugin is something that's visible when configuring the entity reference field. You should first check there. Try to read the explanation at https://www.axelerant.com/resources/team-blog/writing-entity-reference-selection-plugin again if it doesn't help.

@behlhrsh
Copy link

behlhrsh commented Oct 16, 2020

Hi @hussainweb,
My requirement was different I had to filter out the unpublished nodes on all fields using entity reference.

  1. Either use views option as mentioned.But, there are multiple fields & changing configs for all fields is not possible.
  2. Alter/override the NodeSelection plugin. SO, I created new plugin & changed the handler for all fields(entity_autocomplete) to pick my new plugin and it worked.

Thanks, for the great Article, It was of good help.

Harsh

@behlhrsh
Copy link

behlhrsh commented Oct 24, 2020

For Ref:

Create a custom plugin, and override default Plugin.

`<?php

namespace Drupal\my_custom\Plugin\EntityReferenceSelection;

use Drupal\node\Plugin\EntityReferenceSelection\NodeSelection;
use Drupal\node\NodeInterface;

/**

  • Provides an entity reference selection for topics.
  • @EntityReferenceSelection(
  • id = "default:my_custom",
  • label = @translation("Remove unpublished node"),
  • entity_types = {"node"},
  • group = "default",
  • weight = 10
  • )
    */
    class CustomNodeSelection extends NodeSelection {

/**

  • {@inheritdoc}
    */
    protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
    $query = parent::buildEntityQuery($match, $match_operator);
    $query->condition('status', NodeInterface::PUBLISHED);
    return $query;
    }

}
`

& then,

`/**

  • Implements hook_field_widget_form_alter().
    */
    function my_custom_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
    if ($context['items']->getFieldDefinition()->getType() == 'entity_reference' && $context['items']->getFieldDefinition()->getSettings()['handler'] == 'default:node') {
    if ($element['target_id']['#type'] == 'entity_autocomplete') {
    $element['target_id']['#selection_handler'] = 'default:my_custom';
    }
    }
    }
    `
    I hope this will help someone. Please refer attached files.

Thanks
Harsh

@josephr5000
Copy link

Yes, some years later, but your article was really helpful. I have one key question:

Like you, I need context for my use case to work. I see you pull that from $context['entity'], but where does that value get set? Who puts that convenient entity into $context so you can read it?

Many thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment