Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added action plugin: Fetch entities by view #467

Open
wants to merge 2 commits into
base: 8.x-3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/Plugin/RulesAction/EntityFetchByView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace Drupal\rules\Plugin\RulesAction;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\rules\Core\RulesActionBase;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides a generic 'Fetch entities by view' action.
*
* @RulesAction(
* id = "rules_entity_fetch_by_view",
* deriver = "Drupal\rules\Plugin\RulesAction\EntityFetchByViewDeriver",
* category = @Translation("Entity")
* )
*/
class EntityFetchByView extends RulesActionBase implements ContainerFactoryPluginInterface {

/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;

/**
* Constructs an EntityFetchByView 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 $entity_type_manager
* The entity type manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->viewStorage = $entity_type_manager->getStorage('view');
}

/**
* {@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')
);
}

/**
* {@inheritdoc}
*/
public function execute() {

$view_id = $this->pluginDefinition['view_id'];
$display_id = $this->pluginDefinition['display_id'];

// Fetch the list of available contexts.
$contexts = $this->getContexts();

// Pull values out of contexts.
$contexts = array_map(function ($context) {
return $context->getContextData()->getValue();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be nice done by implementing doExecute() instead.

}, $contexts);

// Convert entities into entity ids.
$contexts = array_map(function ($context) {
return $context instanceof EntityInterface ? $context->id() : $context;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Mabye we should add some support to allow ids to be required only.

}, $contexts);

// Request the views executable for the current display.
$view = $this->viewStorage->load($view_id)->getExecutable();
$view->setDisplay($display_id);

$arguments = [];

// Reverse- loop through the views contextual arguments and skip empty
// arguments until the first defined one.
foreach (array_reverse(array_keys($view->display_handler->getOption('arguments'))) as $arg) {
if ($contexts[$arg] == '' && count($arguments) == 0) {
continue;
}
$arguments[$arg] = $contexts[$arg];
}

// Execute the view and pass the result as provided value.
$view->setArguments($arguments);
$entities = $view->render($this->pluginDefinition['display_id']) ?: [];
$this->setProvidedValue('entity_fetched', $entities);
}
}
121 changes: 121 additions & 0 deletions src/Plugin/RulesAction/EntityFetchByViewDeriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Drupal\rules\Plugin\RulesAction;

use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\rules\Context\ContextDefinition;
use Drupal\rules\Plugin\views\display\Rules;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Derives EntityFetchByView plugin definitions from views configurations.
*
* @see EntityFetchByView
*/
class EntityFetchByViewDeriver extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;

/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewsStorage;

/**
* Array mapping table names to entity types.
*
* @var \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected $entityTables = [];

/**
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* @param string $base_plugin_id
* @return static
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}

/**
* EntityFetchByViewDeriver constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->viewsStorage = $entity_type_manager->getStorage('view');
$this->stringTranslation = $string_translation;
}

/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {

// Build a lookup dictionary of table names pointing to corresponding
// entity types. Used to determine which entity type is the result of a
// given view.
$entity_types = [];
foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
if ($base_table = $entity_type->getBaseTable()) {
$entity_types[$base_table] = $entity_type;
}

if ($data_table = $entity_type->getDataTable()) {
$entity_types[$data_table] = $entity_type;
}
}

foreach (Views::getApplicableViews('rules') as $data) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ouch, is there no method on the storage we can use?

list($view_id, $display_id) = $data;

// Fetch the current view applicable view and get it's base table.
/** @var $view \Drupal\views\Entity\View */
$view = $this->viewsStorage->load($view_id);
$table = $view->get('base_table');

/** @var $entity_type \Drupal\Core\Entity\EntityTypeInterface */
if ($entity_type = $entity_types[$table] ?: FALSE) {
// Proceed only, if the view is based on an entity.
// Prepare views executable and display.
$views_executable = $view->getExecutable();
$views_executable->setDisplay($display_id);
$display = $views_executable->getDisplay();

// Build the list of derivative definitions if the display is of type
// "Rules".
if ($display instanceof Rules) {
$this->derivatives[$view_id . ':' . $display_id]= [
'label' => $this->t('Fetch entities from @view - @display', [
'@view' => $view_id,
'@display' => $display->display['display_title'],
]),
'view_id' => $view_id,
'display_id' => $display_id,
'context' => $display->getRulesContext(),
'provides' => [
'entity_fetched' => ContextDefinition::create("entity:" . $entity_type->id())
->setLabel($entity_type->getLabel())
->setMultiple(TRUE)
],
] + $base_plugin_definition;
}
}
}
return $this->derivatives;
}
}
143 changes: 143 additions & 0 deletions src/Plugin/views/display/Rules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
namespace Drupal\rules\Plugin\views\display;

use Drupal\rules\Context\ContextDefinition;
use Drupal\views\Annotation\ViewsDisplay;
use Drupal\views\Plugin\views\display\DisplayPluginBase;

/**
* @ViewsDisplay(
* id = "rules",
* title = @Translation("Rules"),
* admin = @Translation("Rules entity source"),
* help = @Translation("Provide views results to rules workflows."),
* theme = "views_view",
* register_theme = FALSE,
* uses_menu_links = FALSE,
* rules = TRUE
* )
*/
class Rules extends DisplayPluginBase {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call this "Rules context provider" ? Theoretically this could be used by other modules also.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, maybe does Ctools already have something in that direction we could use instead?

/**
* {@inheritdoc}
*/
protected $usesAJAX = FALSE;

/**
* {@inheritdoc}
*/
protected $usesPager = FALSE;

/**
* {@inheritdoc}
*/
protected $usesAttachments = FALSE;

/**
* {@inheritdoc}
*/
protected $usesAreas = FALSE;

/**
* {@inheritdoc}
*/
protected $usesMore = FALSE;

/**
* {@inheritdoc}
*/
protected $usesOptions = FALSE;

/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();

// Force the style plugin to 'entity_reference_style' and the row plugin to
// 'fields'.
$options['style']['contains']['type'] = array('default' => 'rules');
$options['defaults']['default']['style'] = FALSE;

// Set the display title to an empty string (not used in this display type).
$options['title']['default'] = '';
$options['defaults']['default']['title'] = FALSE;

return $options;
}

/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::optionsSummary().
*
* Disable 'cache' and 'title' so it won't be changed.
*/
public function optionsSummary(&$categories, &$options) {
parent::optionsSummary($categories, $options);
unset($options['title']);
}

/**
* {@inheritdoc}
*/
public function getType() {
return 'rules';
}

/**
* {@inheritdoc}
*/
public function execute() {
return $this->view->render($this->display['id']);
}

/**
* {@inheritdoc}
*/
public function render() {
if (!empty($this->view->result) && $this->view->style_plugin->evenEmpty()) {
return $this->view->style_plugin->render($this->view->result);
}
return '';
}

/**
* {@inheritdoc}
*/
public function usesExposed() {
return FALSE;
}

/**
* Build a list of rules context definitions based on the defined views
* contextual arguments.
*
* @return \Drupal\rules\Context\ContextDefinitionInterface[]
*/
public function getRulesContext() {
$context = [];

foreach ($this->getOption('arguments') as $argument_name => $argument) {
// Use the admin title as context label if possible.
$label = $argument['admin_label'] ?: $argument_name;

// If the view is configured to display all items or has a configured
// default value for this argument, don't mark the context as required.
$required = !in_array($argument['default_action'], ['ignore', 'default']);

// Default type for arguments is string.
$type = 'string';

// Check if views argument validation is configured for a specific entity
// type. Use this type as context type definition.
if (strpos($argument['validate']['type'], 'entity:') !== FALSE) {
$type = $argument['validate']['type'];
}

$context[$argument_name] = ContextDefinition::create($type)
->setLabel($label)
->setRequired($required);
}

return $context;
}
}
Loading