-
Notifications
You must be signed in to change notification settings - Fork 123
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
base: 8.x-3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); | ||
}, $contexts); | ||
|
||
// Convert entities into entity ids. | ||
$contexts = array_map(function ($context) { | ||
return $context instanceof EntityInterface ? $context->id() : $context; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
There was a problem hiding this comment.
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.