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

2664280 Select lists in action & condition configuration forms #496

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
23 changes: 23 additions & 0 deletions src/Context/ContextDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ContextDefinition extends ContextDefinitionCore implements ContextDefiniti
'constraints' => 'constraints',
'allow_null' => 'allowNull',
'assignment_restriction' => 'assignmentRestriction',
'list_options_callback' => 'listOptionsCallback',
];

/**
Expand All @@ -43,6 +44,13 @@ class ContextDefinition extends ContextDefinitionCore implements ContextDefiniti
*/
protected $assignmentRestriction = NULL;

/**
* Name of callback function to generate options for select list.
*
* @var string|null
*/
protected $listOptionsCallback = NULL;

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -117,4 +125,19 @@ public function setAssignmentRestriction($restriction) {
return $this;
}

/**
* {@inheritdoc}
*/
public function getListOptionsCallback() {
return $this->listOptionsCallback;
}

/**
* {@inheritdoc}
*/
public function setListOptionsCallback($callback) {
$this->listOptionsCallback = $callback;
return $this;
}

}
22 changes: 22 additions & 0 deletions src/Context/ContextDefinitionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public function getAssignmentRestriction();
* be provided as input values, ASSIGNMENT_RESTRICTION_SELECTOR for contexts
* that must be provided as data selectors or NULL if there is no
* restriction for this context.
*
* @return $this
*/
public function setAssignmentRestriction($restriction);

Expand All @@ -68,4 +70,24 @@ public function setAssignmentRestriction($restriction);
*/
public function toArray();

/**
* Retrieves the select options callback.
*
* @return string|null
* The name of the callback function to be used to generate options for a
* select list in the UI.
*/
public function getListOptionsCallback();

/**
* Sets the select options callback.
*
* @param string $name
* The name of the callback function to be used to generate options for a
* select list in the UI.
*
* @return $this
*/
public function setListOptionsCallback($name);

}
2 changes: 2 additions & 0 deletions src/Form/Expression/ActionForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public function form(array $form, FormStateInterface $form_state) {

$form['context']['#tree'] = TRUE;
foreach ($context_definitions as $context_name => $context_definition) {
$list_callback = $context_definition->getListOptionsCallback();
$configuration['list_options'] = empty($list_callback) ? NULL : $action->$list_callback();
$form = $this->buildContextForm($form, $form_state, $context_name, $context_definition, $configuration);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Form/Expression/ConditionForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public function form(array $form, FormStateInterface $form_state) {

$form['context']['#tree'] = TRUE;
foreach ($context_definitions as $context_name => $context_definition) {
$list_callback = $context_definition->getListOptionsCallback();
$configuration['list_options'] = empty($list_callback) ? NULL : $condition->$list_callback();
$form = $this->buildContextForm($form, $form_state, $context_name, $context_definition, $configuration);
}

Expand Down
28 changes: 23 additions & 5 deletions src/Form/Expression/ContextFormTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ public function buildContextForm(array $form, FormStateInterface $form_state, $c
else {
$default_value = $context_definition->getDefaultValue();
}
// Set initial values for the input field.
$form['context'][$context_name]['setting'] = [
'#type' => 'textfield',
'#title' => $title,
'#required' => $context_definition->isRequired(),
'#default_value' => $default_value,
'#multiple' => $context_definition->isMultiple(),
];

$element = &$form['context'][$context_name]['setting'];

// Modify the element if doing data selection.
if ($mode == 'selector') {
$element['#description'] = $this->t("The data selector helps you drill down into the data available to Rules. <em>To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type').</em> More useful tips about data selection is available in <a href=':url'>the online documentation</a>.", [
':url' => 'https://www.drupal.org/node/1300042',
Expand All @@ -70,16 +73,25 @@ public function buildContextForm(array $form, FormStateInterface $form_state, $c
$element['#attributes']['data-autocomplete-path'] = $url->toString();
$element['#attached']['library'][] = 'rules/rules.autocomplete';
}
// Modify the element if it is a selection list.
elseif (!empty($configuration['list_options'])) {
$element['#type'] = 'select';
$element['#options'] = $configuration['list_options'];
$element['#description'] = $element['#multiple'] ? $this->t('Select any number of values.') : $this->t('Select a value from the list.');
}
// Modify the element to allow multiple text entries using textarea.
elseif ($context_definition->isMultiple()) {
$element['#type'] = 'textarea';
// @todo get a description for possible values that can be filled in.
$element['#description'] = $this->t('Enter one value per line for this multi-valued context.');

// Glue the list of values together as one item per line in the text area.
if (is_array($default_value)) {
$element['#default_value'] = implode("\n", $default_value);
}
}
// The element is a simple single entry text field.
else {
$element['#description'] = $this->t('Enter the value directly.');
}

$value = $mode == 'selector' ? $this->t('Switch to the direct input mode') : $this->t('Switch to data selection');
$form['context'][$context_name]['switch_button'] = [
Expand Down Expand Up @@ -113,10 +125,16 @@ protected function getContextConfigFromFormValues(FormStateInterface $form_state
$context_config->map($context_name, $value['setting']);
}
else {
// Each line of the textarea is one value for multiple contexts.
if ($context_definitions[$context_name]->isMultiple()) {
$values = explode("\n", $value['setting']);
$context_config->setValue($context_name, $values);
if (!empty($context_definitions[$context_name]->getListOptionsCallback())) {
// This is a select list with multiple values allowed.
$context_config->setValue($context_name, array_keys($value['setting']));
}
else {
// Each line of the textarea is one value for multiple contexts.
$values = explode("\n", $value['setting']);
$context_config->setValue($context_name, $values);
}
}
else {
$context_config->setValue($context_name, $value['setting']);
Expand Down
68 changes: 66 additions & 2 deletions src/Plugin/Condition/EntityHasField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace Drupal\rules\Plugin\Condition;

use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\rules\Core\RulesConditionBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides a 'Entity has field' condition.
Expand All @@ -19,14 +22,51 @@
* ),
* "field" = @ContextDefinition("string",
* label = @Translation("Field"),
* description = @Translation("The name of the field to check for.")
* description = @Translation("The name of the field to check for."),
* list_options_callback = "fieldListOptions"
* )
* }
* )
*
* @todo: Add access callback information from Drupal 7.
*/
class EntityHasField extends RulesConditionBase {
class EntityHasField extends RulesConditionBase implements ContainerFactoryPluginInterface {

/**
* The entity_field.manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;

/**
* Constructs an EntityHasField 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\EntityFieldManagerInterface $entity_field_manager
* The entity_field.manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityFieldManager = $entity_field_manager;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_field.manager')
);
}

/**
* Checks if a given entity has a given field.
Expand All @@ -43,4 +83,28 @@ protected function doEvaluate(FieldableEntityInterface $entity, $field) {
return $entity->hasField($field);
}

/**
* Returns all the available fields in the system.
*
* @return array
* An array of field names keyed on the field name.
*/
public function fieldListOptions() {
$options = [];

// Load all the fields in the system.
$fields = $this->entityFieldManager->getFieldMap();

// Add each field to our options array.
foreach ($fields as $entity_fields) {
foreach ($entity_fields as $field_name => $field) {
$options[$field_name] = $field_name;
}
}
// Sort the field names for ease of locating and selecting.
asort($options);

return $options;
}

}
100 changes: 97 additions & 3 deletions src/Plugin/Condition/EntityIsOfBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace Drupal\rules\Plugin\Condition;

use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\rules\Core\RulesConditionBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides an 'Entity is of bundle' condition.
Expand All @@ -20,18 +24,56 @@
* ),
* "type" = @ContextDefinition("string",
* label = @Translation("Type"),
* description = @Translation("The type of the evaluated entity.")
* description = @Translation("The type of the evaluated entity."),
* list_options_callback = "entityTypesListOptions"
* ),
* "bundle" = @ContextDefinition("string",
* label = @Translation("Bundle"),
* description = @Translation("The bundle of the evaluated entity.")
* description = @Translation("The bundle of the evaluated entity."),
* list_options_callback = "bundleListOptions"
* )
* }
* )
*
* @todo: Add access callback information from Drupal 7?
*/
class EntityIsOfBundle extends RulesConditionBase {
class EntityIsOfBundle extends RulesConditionBase implements ContainerFactoryPluginInterface {

/**
* The entity_type.manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* Constructs an EntityIsOfBundle 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;
}

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

/**
* Check if a provided entity is of a specific type and bundle.
Expand Down Expand Up @@ -69,4 +111,56 @@ public function assertMetadata(array $selected_data) {
return $changed_definitions;
}

/**
* Returns an array of entity types that exist in the system.
*
* @return array
* An array of entity types keyed on the entity type machine name.
*/
public function entityTypesListOptions() {
$options = [];

$entity_types = $this->entityTypeManager->getDefinitions();

foreach ($entity_types as $entity_type) {
if (!$entity_type instanceof ContentEntityTypeInterface) {
continue;
}

$options[$entity_type->id()] = $entity_type->getLabel();
// If the id differs from the label add the id in brackets for clarity.
if (strtolower(str_replace('_', ' ', $entity_type->id())) != strtolower($entity_type->getLabel())) {
$options[$entity_type->id()] .= ' (' . $entity_type->id() . ')';
}
}

return $options;
}

/**
* Returns an array of entity bundles options.
*
* @return array
* An array of bundles keyed on the bundle machine name.
*/
public function bundleListOptions() {
$options = [];

$entity_types = $this->entityTypeManager->getDefinitions();

foreach ($entity_types as $entity_type) {
if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) {
$options[$entity->id()] = $entity->label();
// If the id differs from the label add the id in brackets.
if (strtolower(str_replace('_', ' ', $entity->id())) != strtolower($entity->label())) {
$options[$entity->id()] .= ' (' . $entity->id() . ')';
}
}
}
}

return $options;
}

}
Loading