Skip to content

Instantly share code, notes, and snippets.

@hussainweb
Created December 12, 2019 23:43
Show Gist options
  • 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

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