From d58ca7556dc167a0095cb0ffbe1a1172903e582f Mon Sep 17 00:00:00 2001 From: Daniel Sasser Date: Tue, 4 Oct 2016 13:25:59 -0700 Subject: [PATCH 1/4] #2799361 - Add drag and drop to actions and conditions from #2648334. --- config/schema/rules.expression.schema.yml | 18 +++ src/Engine/ExpressionBase.php | 42 +++++++ src/Engine/ExpressionInterface.php | 31 +++++ src/Form/Expression/ActionContainerForm.php | 108 ++++++++++++++---- .../Expression/ConditionContainerForm.php | 107 +++++++++++++---- src/Form/Expression/RuleForm.php | 10 ++ src/Form/ReactionRuleEditForm.php | 2 + src/Plugin/RulesExpression/ActionSet.php | 10 ++ src/Plugin/RulesExpression/RulesAnd.php | 10 ++ src/Plugin/RulesExpression/RulesLoop.php | 11 ++ src/Plugin/RulesExpression/RulesOr.php | 10 ++ 11 files changed, 316 insertions(+), 43 deletions(-) diff --git a/config/schema/rules.expression.schema.yml b/config/schema/rules.expression.schema.yml index ab6d4068..1af97121 100644 --- a/config/schema/rules.expression.schema.yml +++ b/config/schema/rules.expression.schema.yml @@ -7,6 +7,9 @@ rules_expression: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' rules_expression.rules_condition: type: rules_expression @@ -18,6 +21,9 @@ rules_expression.rules_condition: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' condition_id: type: string label: 'Condition plugin ID' @@ -47,6 +53,9 @@ rules_expression.rules_action: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' action_id: type: string label: 'Action plugin ID' @@ -73,6 +82,9 @@ rules_expression.rules_and: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' negate: type: boolean label: 'Negate' @@ -92,6 +104,9 @@ rules_expression.rules_action_set: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' actions: type: sequence label: 'Actions' @@ -108,6 +123,9 @@ rules_expression.rules_rule: uuid: type: string label: 'UUID' + weight: + type: integer + label: 'Weight' conditions: type: rules_expression.[id] label: 'Conditions' diff --git a/src/Engine/ExpressionBase.php b/src/Engine/ExpressionBase.php index 7eb6d295..3f2b0df4 100644 --- a/src/Engine/ExpressionBase.php +++ b/src/Engine/ExpressionBase.php @@ -37,6 +37,13 @@ abstract class ExpressionBase extends PluginBase implements ExpressionInterface */ protected $uuid; + /** + * The weight of this expression. + * + * @var integer + */ + protected $weight; + /** * Constructor. * @@ -71,6 +78,7 @@ public function getConfiguration() { return [ 'id' => $this->getPluginId(), 'uuid' => $this->uuid, + 'weight' => $this->weight, ] + $this->configuration; } @@ -82,6 +90,13 @@ public function setConfiguration(array $configuration) { if (isset($configuration['uuid'])) { $this->uuid = $configuration['uuid']; } + if (isset($configuration['weight'])) { + $this->weight = $configuration['weight']; + } + else { + $this->weight = 0; + } + return $this; } @@ -148,4 +163,31 @@ public function setUuid($uuid) { $this->uuid = $uuid; } + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ + public function setWeight($weight) { + $this->weight = $weight; + } + + /** + * {@inheritdoc} + */ + public function expressionSortHelper(ExpressionInterface $a, ExpressionInterface $b) { + $a_weight = $a->getWeight(); + $b_weight = $b->getWeight(); + if ($a_weight == $b_weight) { + return 0; + } + + return ($a_weight < $b_weight) ? -1 : 1; + } + } diff --git a/src/Engine/ExpressionInterface.php b/src/Engine/ExpressionInterface.php index a3c43b60..5c4875cb 100644 --- a/src/Engine/ExpressionInterface.php +++ b/src/Engine/ExpressionInterface.php @@ -79,6 +79,37 @@ public function getUuid(); */ public function setUuid($uuid); + /** + * Returns the weight of this expression. + * + * @return int + * The weight of this expression. + */ + public function getWeight(); + + /** + * Sets the weight of this expression in an expression tree. + * + * @param int $weight + * The weight to set. + */ + public function setWeight($weight); + + /** + * Sorts an array of expressions by 'weight' property. + * + * Callback for uasort(). + * + * @param \Drupal\rules\Engine\ExpressionInterface $a + * First item for comparison. + * @param \Drupal\rules\Engine\ExpressionInterface $b + * Second item for comparison. + * + * @return int + * The comparison result for uasort(). + */ + public function expressionSortHelper(ExpressionInterface $a, ExpressionInterface $b); + /** * Verifies that this expression is configured correctly. * diff --git a/src/Form/Expression/ActionContainerForm.php b/src/Form/Expression/ActionContainerForm.php index 254793ba..9eb0a69e 100644 --- a/src/Form/Expression/ActionContainerForm.php +++ b/src/Form/Expression/ActionContainerForm.php @@ -39,31 +39,75 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['action_table']['table'] = [ - '#theme' => 'table', - '#caption' => $this->t('Actions'), - '#header' => [$this->t('Elements'), $this->t('Operations')], - '#empty' => t('None'), + '#type' => 'table', + '#header' => [ + $this->t('Elements'), + $this->t('Weight'), + [ + 'data' => $this->t('Operations'), + 'colspan' => 3, + ], + ], + '#attributes' => [ + 'id' => 'rules_actions_table', + ], + '#tabledrag' => [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'action-weight', + ], + ], ]; + $form['action_table']['table']['#empty'] = $this->t('None'); + + // Get hold of actions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $actions = []; foreach ($this->actionSet as $action) { - $form['action_table']['table']['#rows'][] = [ - 'element' => $action->getLabel(), - 'operations' => [ - 'data' => [ - '#type' => 'dropbutton', - '#links' => [ - 'edit' => [ - 'title' => $this->t('Edit'), - 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.edit', [ - 'uuid' => $action->getUuid(), - ]), - ], - 'delete' => [ - 'title' => $this->t('Delete'), - 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.delete', [ - 'uuid' => $action->getUuid(), - ]), - ], + $actions[] = $action; + } + + // Sort actions by weight. + @uasort($actions, [$this->actionSet, 'expressionSortHelper']); + + foreach ($actions as $action) { + /* @var $action \Drupal\rules\Engine\ExpressionInterface */ + $uuid = $action->getUuid(); + $row = &$form['action_table']['table'][$uuid]; + + // TableDrag: Mark the table row as draggable. + $row['#attributes']['class'][] = 'draggable'; + + // TableDrag: Sort the table row according to its existing weight. + $row['#weight'] = $action->getWeight(); + $row['title'] = ['#markup' => $action->getLabel()]; + + $row['weight'] = [ + '#type' => 'weight', + '#delta' => 50, + '#default_value' => $action->getWeight(), + '#attributes' => ['class' => ['action-weight']], + ]; + + // Operations (dropbutton) column. + $rules_ui_handler = $this->getRulesUiHandler(); + $row['operations'] = [ + 'data' => [ + '#type' => 'dropbutton', + '#links' => [ + 'edit' => [ + 'title' => $this->t('Edit'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.edit', [ + 'uuid' => $uuid, + ]), + ], + 'delete' => [ + 'title' => $this->t('Delete'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.delete', [ + 'uuid' => $uuid, + ]), ], ], ], @@ -85,4 +129,24 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValue('table'); + $component = $this->getRulesUiHandler()->getComponent(); + /* @var $rule_expression \Drupal\rules\Plugin\RulesExpression\Rule */ + $rule_expression = $component->getExpression(); + + if ($values) { + foreach ($values as $uuid => $expression) { + $action = $rule_expression->getExpression($uuid); + $action->setWeight($expression['weight']); + $action->setConfiguration($action->getConfiguration()); + } + } + + $this->getRulesUiHandler()->updateComponent($component); + } + } diff --git a/src/Form/Expression/ConditionContainerForm.php b/src/Form/Expression/ConditionContainerForm.php index 6a23fbba..029c9244 100644 --- a/src/Form/Expression/ConditionContainerForm.php +++ b/src/Form/Expression/ConditionContainerForm.php @@ -39,31 +39,76 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['conditions']['table'] = [ - '#theme' => 'table', + '#type' => 'table', '#caption' => $this->t('Conditions'), - '#header' => [$this->t('Elements'), $this->t('Operations')], - '#empty' => t('None'), + '#header' => [ + $this->t('Elements'), + $this->t('Weight'), + [ + 'data' => $this->t('Operations'), + 'colspan' => 3, + ], + ], + '#attributes' => [ + 'id' => 'rules_conditions_table', + ], + '#tabledrag' => [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'condition-weight', + ], + ], ]; + $form['conditions']['table']['#empty'] = $this->t('None'); + + // Get hold of conditions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $conditions = []; foreach ($this->conditionContainer as $condition) { - $form['conditions']['table']['#rows'][] = [ - 'element' => $condition->getLabel(), - 'operations' => [ - 'data' => [ - '#type' => 'dropbutton', - '#links' => [ - 'edit' => [ - 'title' => $this->t('Edit'), - 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.edit', [ - 'uuid' => $condition->getUuid(), - ]), - ], - 'delete' => [ - 'title' => $this->t('Delete'), - 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.delete', [ - 'uuid' => $condition->getUuid(), - ]), - ], + $conditions[] = $condition; + } + + // Sort conditions by weight. + @uasort($conditions, [$this->conditionContainer, 'expressionSortHelper']); + + foreach ($conditions as $condition) { + /* @var $condition \Drupal\rules\Engine\ExpressionInterface */ + $uuid = $condition->getUuid(); + $row = &$form['conditions']['table'][$uuid]; + + // TableDrag: Mark the table row as draggable. + $row['#attributes']['class'][] = 'draggable'; + + // TableDrag: Sort the table row according to its weight. + $row['#weight'] = $condition->getWeight(); + $row['title'] = ['#markup' => $condition->getLabel()]; + + $row['weight'] = [ + '#type' => 'weight', + '#delta' => 50, + '#default_value' => $condition->getWeight(), + '#attributes' => ['class' => ['condition-weight']], + ]; + + // Operations (dropbutton) column. + $rules_ui_handler = $this->getRulesUiHandler(); + $row['operations'] = [ + 'data' => [ + '#type' => 'dropbutton', + '#links' => [ + 'edit' => [ + 'title' => $this->t('Edit'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.edit', [ + 'uuid' => $condition->getUuid(), + ]), + ], + 'delete' => [ + 'title' => $this->t('Delete'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.delete', [ + 'uuid' => $condition->getUuid(), + ]), ], ], ], @@ -85,4 +130,24 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValue('table'); + $component = $this->getRulesUiHandler()->getComponent(); + /* @var $rule_expression \Drupal\rules\Plugin\RulesExpression\Rule */ + $rule_expression = $component->getExpression(); + + if ($values) { + foreach ($values as $uuid => $expression) { + $action = $rule_expression->getExpression($uuid); + $action->setWeight($expression['weight']); + $action->setConfiguration($action->getConfiguration()); + } + } + + $this->getRulesUiHandler()->updateComponent($component); + } + } diff --git a/src/Form/Expression/RuleForm.php b/src/Form/Expression/RuleForm.php index 49c1635f..67cad998 100644 --- a/src/Form/Expression/RuleForm.php +++ b/src/Form/Expression/RuleForm.php @@ -41,4 +41,14 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $conditions = $this->rule->getConditions(); + $conditions->getFormHandler()->submitForm($form, $form_state); + $actions = $this->rule->getActions(); + $actions->getFormHandler()->submitForm($form, $form_state); + } + } diff --git a/src/Form/ReactionRuleEditForm.php b/src/Form/ReactionRuleEditForm.php index 1ef7235a..efae6581 100644 --- a/src/Form/ReactionRuleEditForm.php +++ b/src/Form/ReactionRuleEditForm.php @@ -113,6 +113,8 @@ protected function actions(array $form, FormStateInterface $form_state) { */ public function save(array $form, FormStateInterface $form_state) { $this->rulesUiHandler->getForm()->submitForm($form, $form_state); + $component = $this->rulesUiHandler->getComponent(); + $this->entity->updateFromComponent($component); // Persist changes by saving the entity. parent::save($form, $form_state); diff --git a/src/Plugin/RulesExpression/ActionSet.php b/src/Plugin/RulesExpression/ActionSet.php index 3284fa9b..9fae3797 100644 --- a/src/Plugin/RulesExpression/ActionSet.php +++ b/src/Plugin/RulesExpression/ActionSet.php @@ -27,7 +27,17 @@ protected function allowsMetadataAssertions() { * {@inheritdoc} */ public function executeWithState(ExecutionStateInterface $state) { + // Get hold of actions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $actions = []; foreach ($this->actions as $action) { + $actions[] = $action; + } + + // Sort actions by weight. + @uasort($actions, [$this->actions, 'expressionSortHelper']); + foreach ($actions as $action) { + /* @var $action \Drupal\rules\Engine\ExpressionInterface */ $action->executeWithState($state); } } diff --git a/src/Plugin/RulesExpression/RulesAnd.php b/src/Plugin/RulesExpression/RulesAnd.php index f1e5b61f..1d509a51 100644 --- a/src/Plugin/RulesExpression/RulesAnd.php +++ b/src/Plugin/RulesExpression/RulesAnd.php @@ -32,7 +32,17 @@ public function isEmpty() { * {@inheritdoc} */ public function evaluate(ExecutionStateInterface $state) { + // Get hold of conditions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $conditions = []; foreach ($this->conditions as $condition) { + $conditions[] = $condition; + } + + // Sort conditions by weight. + @uasort($conditions, [$this->conditions, 'expressionSortHelper']); + foreach ($conditions as $condition) { + /* @var $condition \Drupal\rules\Engine\ExpressionInterface */ if (!$condition->executeWithState($state)) { return FALSE; } diff --git a/src/Plugin/RulesExpression/RulesLoop.php b/src/Plugin/RulesExpression/RulesLoop.php index 892eb861..b0e2150f 100644 --- a/src/Plugin/RulesExpression/RulesLoop.php +++ b/src/Plugin/RulesExpression/RulesLoop.php @@ -38,7 +38,18 @@ public function executeWithState(ExecutionStateInterface $state) { foreach ($list_data as $item) { $state->setVariableData($list_item_name, $item); + + // Get hold of actions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $actions = []; foreach ($this->actions as $action) { + $actions[] = $action; + } + + // Sort actions by weight. + @uasort($actions, [$this->actions, 'expressionSortHelper']); + foreach ($actions as $action) { + /* @var $action \Drupal\rules\Engine\ExpressionInterface */ $action->executeWithState($state); } } diff --git a/src/Plugin/RulesExpression/RulesOr.php b/src/Plugin/RulesExpression/RulesOr.php index a599161d..7eedca9b 100644 --- a/src/Plugin/RulesExpression/RulesOr.php +++ b/src/Plugin/RulesExpression/RulesOr.php @@ -19,7 +19,17 @@ class RulesOr extends ConditionExpressionContainer { * {@inheritdoc} */ public function evaluate(ExecutionStateInterface $state) { + // Get hold of conditions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $conditions = []; foreach ($this->conditions as $condition) { + $conditions[] = $condition; + } + + // Sort conditions by weight. + @uasort($conditions, [$this->conditions, 'expressionSortHelper']); + foreach ($conditions as $condition) { + /* @var $condition \Drupal\rules\Engine\ExpressionInterface */ if ($condition->executeWithState($state)) { return TRUE; } From ef2f28ca5d0641c2b03b5c6e957875896e28e0f8 Mon Sep 17 00:00:00 2001 From: Daniel Sasser Date: Tue, 4 Oct 2016 14:47:57 -0700 Subject: [PATCH 2/4] #2799361 - Create rules_and and rules_or condition sets (expressions) in the UI. --- src/Form/EditExpressionForm.php | 2 +- src/Form/Expression/ActionContainerForm.php | 8 +- .../Expression/ConditionContainerForm.php | 405 ++++++++++++++---- src/Plugin/RulesExpression/RulesOr.php | 3 +- 4 files changed, 323 insertions(+), 95 deletions(-) diff --git a/src/Form/EditExpressionForm.php b/src/Form/EditExpressionForm.php index f754b462..b281ac8b 100644 --- a/src/Form/EditExpressionForm.php +++ b/src/Form/EditExpressionForm.php @@ -67,7 +67,7 @@ public function buildForm(array $form, FormStateInterface $form_state, RulesUiHa throw new NotFoundHttpException(); } $form_handler = $expression->getFormHandler(); - $form = $form_handler->form($form, $form_state); + $form = $form_handler->form($form, $form_state, ['init' => TRUE]); return $form; } diff --git a/src/Form/Expression/ActionContainerForm.php b/src/Form/Expression/ActionContainerForm.php index 9eb0a69e..a9fee3d2 100644 --- a/src/Form/Expression/ActionContainerForm.php +++ b/src/Form/Expression/ActionContainerForm.php @@ -34,11 +34,11 @@ public function __construct(ActionExpressionContainerInterface $action_set) { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - $form['action_table'] = [ + $form['action_table_container'] = [ '#type' => 'container', ]; - $form['action_table']['table'] = [ + $form['action_table_container']['action_table'] = [ '#type' => 'table', '#header' => [ $this->t('Elements'), @@ -75,7 +75,7 @@ public function form(array $form, FormStateInterface $form_state) { foreach ($actions as $action) { /* @var $action \Drupal\rules\Engine\ExpressionInterface */ $uuid = $action->getUuid(); - $row = &$form['action_table']['table'][$uuid]; + $row = &$form['action_table_container']['action_table'][$uuid]; // TableDrag: Mark the table row as draggable. $row['#attributes']['class'][] = 'draggable'; @@ -133,7 +133,7 @@ public function form(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $values = $form_state->getValue('table'); + $values = $form_state->getValue('action_table'); $component = $this->getRulesUiHandler()->getComponent(); /* @var $rule_expression \Drupal\rules\Plugin\RulesExpression\Rule */ $rule_expression = $component->getExpression(); diff --git a/src/Form/Expression/ConditionContainerForm.php b/src/Form/Expression/ConditionContainerForm.php index 029c9244..312820fc 100644 --- a/src/Form/Expression/ConditionContainerForm.php +++ b/src/Form/Expression/ConditionContainerForm.php @@ -6,6 +6,7 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\rules\Ui\RulesUiHandlerTrait; use Drupal\rules\Engine\ConditionExpressionContainerInterface; +use Drupal\rules\Engine\RulesComponent; /** * Form view structure for Rules condition containers. @@ -23,111 +24,265 @@ class ConditionContainerForm implements ExpressionFormInterface { */ protected $conditionContainer; + /** + * The rules ui handler. + * + * @var \Drupal\rules\Ui\RulesUiHandlerInterface|null + */ + protected $rulesUiHandler; + + /** + * The rules component. + * + * @var \Drupal\rules\Engine\RulesComponent + */ + protected $component; + + /** + * The rule as an expression. + * + * @var \Drupal\rules\Engine\ExpressionInterface + */ + protected $ruleExpression; + /** * Creates a new object of this class. */ public function __construct(ConditionExpressionContainerInterface $condition_container) { $this->conditionContainer = $condition_container; + $this->rulesUiHandler = $this->getRulesUiHandler(); + $this->component = $this->rulesUiHandler->getComponent(); + $this->ruleExpression = $this->component->getExpression(); } /** * {@inheritdoc} */ - public function form(array $form, FormStateInterface $form_state) { - $form['conditions'] = [ - '#type' => 'container', - ]; + public function form(array $form, FormStateInterface $form_state, $options = []) { + // By default skip. + if (!empty($options['init'])) { + /* @var $config \Drupal\rules\Entity\ReactionRuleConfig */ + $config = $this->rulesUiHandler->getConfig(); + $form['init_help'] = array( + '#type' => 'container', + '#id' => 'rules-plugin-add-help', + 'content' => array( + '#markup' => t('You are about to add a new @plugin to the @config-plugin %label. Use indentation to make conditions a part of this logic group. See the online documentation for more information on condition sets.', + array( + '@plugin' => $this->conditionContainer->getLabel(), + '@config-plugin' => $config->bundle(), + '%label' => $config->label(), + '@url' => 'http://drupal.org/node/1300034', + ) + ), + ), + 'submit' => [ + '#type' => 'submit', + '#value' => 'Continue', + ], + ); + } + else { + $form['conditions'] = [ + '#type' => 'container', + ]; - $form['conditions']['table'] = [ - '#type' => 'table', - '#caption' => $this->t('Conditions'), - '#header' => [ - $this->t('Elements'), - $this->t('Weight'), - [ - 'data' => $this->t('Operations'), - 'colspan' => 3, + $form['conditions']['table'] = [ + '#type' => 'table', + '#caption' => $this->t('Conditions'), + '#header' => [ + $this->t('Elements'), + [ + 'data' => $this->t('Elements'), + ], + [ + 'data' => $this->t('Operations'), + 'colspan' => 3, + ], ], - ], - '#attributes' => [ - 'id' => 'rules_conditions_table', - ], - '#tabledrag' => [ - [ - 'action' => 'order', - 'relationship' => 'sibling', - 'group' => 'condition-weight', + '#attributes' => [ + 'id' => 'rules_conditions_table', ], - ], - ]; - - $form['conditions']['table']['#empty'] = $this->t('None'); - - // Get hold of conditions. - // @todo See if we can add getExpressions method of ExpressionContainerBase. - $conditions = []; - foreach ($this->conditionContainer as $condition) { - $conditions[] = $condition; - } + '#tabledrag' => [ + [ + 'action' => 'match', + 'relationship' => 'parent', + 'group' => 'condition-parent', + 'subgroup' => 'condition-parent', + 'source' => 'condition-id', + ], + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'condition-weight', + ], + ], + ]; - // Sort conditions by weight. - @uasort($conditions, [$this->conditionContainer, 'expressionSortHelper']); + $form['conditions']['table']['#empty'] = $this->t('None'); - foreach ($conditions as $condition) { - /* @var $condition \Drupal\rules\Engine\ExpressionInterface */ - $uuid = $condition->getUuid(); - $row = &$form['conditions']['table'][$uuid]; + // Get hold of conditions. + // @todo See if we can add getExpressions method of ExpressionContainerBase. + $conditions = []; + foreach ($this->conditionContainer as $condition) { + $conditions[] = $condition; + } + $depth = 0; + // Sort conditions by weight. + @uasort($conditions, [$this->conditionContainer, 'expressionSortHelper']); + foreach ($conditions as $condition) { + $this->buildRow($form, $form_state, $condition->getUuid(), $depth); + } - // TableDrag: Mark the table row as draggable. - $row['#attributes']['class'][] = 'draggable'; + $this->buildFooter($form, $form_state); + } + return $form; + } - // TableDrag: Sort the table row according to its weight. - $row['#weight'] = $condition->getWeight(); - $row['title'] = ['#markup' => $condition->getLabel()]; + /** + * Build condition container table rows. + * + * @param array $form + * The form array. + * @param FormStateInterface $form_state + * The current form state. + * @param string $condition + * A condition expression uuid to add to the form table. + * @param int $depth + * The depth of the condition within the condition tree. + * @param mixed $parent + * The parent expression, if it is set. + */ + private function buildRow(&$form, FormStateInterface &$form_state, $condition, $depth, $parent = NULL) { + $uuid = $condition; + /** @var \Drupal\rules\Plugin\RulesExpression\RulesCondition $condition */ + $condition = $this->ruleExpression->getExpression($condition); + $row = &$form['conditions']['table'][$uuid]; - $row['weight'] = [ - '#type' => 'weight', - '#delta' => 50, - '#default_value' => $condition->getWeight(), - '#attributes' => ['class' => ['condition-weight']], - ]; + // TableDrag: Mark the table row as draggable. + $row['#attributes']['class'][] = 'draggable'; + $row['title'] = [ + [ + '#theme' => 'indentation', + '#size' => $depth, + ], + [ + '#markup' => $condition->getLabel(), + ], + ]; + $row['weight'] = [ + '#type' => 'weight', + '#delta' => 50, + '#default_value' => $condition->getWeight(), + '#attributes' => ['class' => ['condition-weight']], + ]; - // Operations (dropbutton) column. - $rules_ui_handler = $this->getRulesUiHandler(); - $row['operations'] = [ - 'data' => [ - '#type' => 'dropbutton', - '#links' => [ - 'edit' => [ - 'title' => $this->t('Edit'), - 'url' => $rules_ui_handler->getUrlFromRoute('expression.edit', [ - 'uuid' => $condition->getUuid(), - ]), - ], - 'delete' => [ - 'title' => $this->t('Delete'), - 'url' => $rules_ui_handler->getUrlFromRoute('expression.delete', [ - 'uuid' => $condition->getUuid(), - ]), - ], + // Operations (dropbutton) column. + $rules_ui_handler = $this->getRulesUiHandler(); + $row['operations'] = [ + 'data' => [ + '#type' => 'dropbutton', + '#links' => [ + 'edit' => [ + 'title' => $this->t('Edit'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.edit', [ + 'uuid' => $condition->getUuid(), + ]), + ], + 'delete' => [ + 'title' => $this->t('Delete'), + 'url' => $rules_ui_handler->getUrlFromRoute('expression.delete', [ + 'uuid' => $condition->getUuid(), + ]), ], ], - ]; + ], + ]; + $row['parent'] = [ + '#type' => 'hidden', + '#default_value' => $parent ? $parent->getUuid() : 0, + '#attributes' => ['class' => ['condition-parent']], + ]; + if ($condition->getPluginId() === 'rules_condition') { + $row['#attributes']['class'][] = 'tabledrag-leaf'; } + // TableDrag: Sort the table row according to its weight. + $row['#weight'] = $condition->getWeight(); - // @todo Put this into the table as last row and style it like it was in - // Drupal 7 Rules. - $form['add_condition'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add condition'), - 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.add', [ - 'expression_id' => 'rules_condition', - ]), - ], + $row['id'] = [ + '#type' => 'hidden', + '#value' => $uuid, + '#attributes' => ['class' => ['condition-id']], ]; + $row['plugin_id'] = [ + '#type' => 'hidden', + '#default_value' => $condition->getPluginId(), + ]; + $configuration = $condition->getConfiguration(); + if (!empty($configuration['conditions'])) { + $depth++; + foreach ($configuration['conditions'] as $child_condition) { + $this->buildRow($form, $form_state, $child_condition['uuid'], $depth, $condition); + } + } + } - return $form; + /** + * Build condition container table footer. + * + * @param array $form + * The form array. + * @param FormStateInterface $form_state + * The current form state. + */ + private function buildFooter(&$form, FormStateInterface &$form_state) { + $footer = array( + '#prefix' => '', + 'links' => array( + array( + '#theme' => 'menu_local_action', + '#attribues' => ['classes' => ['action-links']], + '#link' => [ + 'title' => $this->t('Add condition'), + 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.add', [ + 'expression_id' => 'rules_condition', + ]), + ], + ), + array( + '#theme' => 'menu_local_action', + '#link' => [ + 'title' => $this->t('Add AND'), + 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.add', [ + 'expression_id' => 'rules_and', + ]), + ], + ), + array( + '#theme' => 'menu_local_action', + '#link' => [ + 'title' => $this->t('Add OR'), + 'url' => $this->getRulesUiHandler()->getUrlFromRoute('expression.add', [ + 'expression_id' => 'rules_or', + ]), + ], + ), + ), + ); + + // Table footer. + $form['conditions']['table']['#footer'] = [ + array( + 'class' => array('footer-class'), + 'data' => array( + array( + 'data' => $footer, + 'colspan' => 2, + ), + ), + ), + ]; } /** @@ -135,19 +290,91 @@ public function form(array $form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { $values = $form_state->getValue('table'); - $component = $this->getRulesUiHandler()->getComponent(); - /* @var $rule_expression \Drupal\rules\Plugin\RulesExpression\Rule */ - $rule_expression = $component->getExpression(); - if ($values) { - foreach ($values as $uuid => $expression) { - $action = $rule_expression->getExpression($uuid); - $action->setWeight($expression['weight']); - $action->setConfiguration($action->getConfiguration()); + $elements = $this->mirrorElements($values); + $tree = $this->buildElementTree($values, $elements, 'parent', 'id'); + $this->saveElementTree($tree, $elements); + // Remove the original expressions since we've cloned them to a new tree. + foreach ($values as $uuid => $old_expression) { + $this->ruleExpression->deleteExpression($uuid); + } + $this->getRulesUiHandler()->updateComponent($this->component); + } + } + + /** + * Build a tree of values from a flat array. + * + * @param array $values + * An associative array. + * @param string $pidKey + * The parent id key in the values. + * @param string $idKey + * The id key in the values. + * + * @return mixed + * A heirarchical tree of values. + */ + private function buildElementTree($values, &$elements, $pidKey, $idKey = NULL) { + $grouped = array(); + foreach ($values as $sub) { + $grouped[$sub[$pidKey]][] = $sub; + } + + $fnBuilder = function ($siblings) use (&$fnBuilder, &$elements, $grouped, $idKey, $elements) { + foreach ($siblings as $k => $sibling) { + $id = $sibling[$idKey]; + if (isset($grouped[$id])) { + $sibling['conditions'] = $fnBuilder($grouped[$id]); + foreach ($sibling['conditions'] as $condition) { + // Tell the child who the parent is by setting its root. + $child = $elements[$condition[$idKey]]; + // Add the child to the parent object. + $elements[$id]->addExpressionObject($child); + } + } + $siblings[$k] = $sibling; } + return $siblings; + }; + $tree = $fnBuilder($grouped[0]); + return $tree; + } + + /** + * Creates "clones" of submitted elements for use in a new condition tree. + * + * @param array $values + * The array of form values. + * + * @return array|mixed + * The derived condition expression elements. + */ + private function mirrorElements($values) { + $elements = NULL; + $expressionManager = \Drupal::service('plugin.manager.rules_expression'); + foreach ($values as $uuid => $element) { + $condition = $this->ruleExpression->getExpression($element['id']); + $configuration = $condition->getConfiguration(); + unset($configuration['uuid']); + $configuration['weight'] = $element['weight']; + $elements[$uuid] = $expressionManager->createInstance($condition->getPluginId(), $configuration); } - $this->getRulesUiHandler()->updateComponent($component); + return $elements; + } + + /** + * Process and save expression condition tree. + * + * @param array $tree + * An associative array of expression elements. + */ + private function saveElementTree($tree, &$elements) { + foreach ($tree as $branch) { + $this->ruleExpression->addExpressionObject($elements[$branch['id']]); + } + $this->rulesUiHandler->updateComponent($this->component); } } diff --git a/src/Plugin/RulesExpression/RulesOr.php b/src/Plugin/RulesExpression/RulesOr.php index 7eedca9b..71ad0112 100644 --- a/src/Plugin/RulesExpression/RulesOr.php +++ b/src/Plugin/RulesExpression/RulesOr.php @@ -10,7 +10,8 @@ * * @RulesExpression( * id = "rules_or", - * label = @Translation("Condition set (OR)") + * label = @Translation("Condition set (OR)"), + * form_class = "\Drupal\rules\Form\Expression\ConditionContainerForm" * ) */ class RulesOr extends ConditionExpressionContainer { From 75b4cc92afee90370cf4bd731d42d053d05e286f Mon Sep 17 00:00:00 2001 From: Daniel Sasser Date: Wed, 5 Oct 2016 15:55:49 -0700 Subject: [PATCH 3/4] WDC-16595 Updates based on code review; - Replaced dropbutton with operation elements - Used new :placeholder for url placeholder values - Move expressionManager to class property --- src/Form/Expression/ActionContainerForm.php | 4 ++-- .../Expression/ConditionContainerForm.php | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Form/Expression/ActionContainerForm.php b/src/Form/Expression/ActionContainerForm.php index a9fee3d2..8c425225 100644 --- a/src/Form/Expression/ActionContainerForm.php +++ b/src/Form/Expression/ActionContainerForm.php @@ -91,11 +91,11 @@ public function form(array $form, FormStateInterface $form_state) { '#attributes' => ['class' => ['action-weight']], ]; - // Operations (dropbutton) column. + // Operations column. $rules_ui_handler = $this->getRulesUiHandler(); $row['operations'] = [ 'data' => [ - '#type' => 'dropbutton', + '#type' => 'operations', '#links' => [ 'edit' => [ 'title' => $this->t('Edit'), diff --git a/src/Form/Expression/ConditionContainerForm.php b/src/Form/Expression/ConditionContainerForm.php index 312820fc..ebc2205a 100644 --- a/src/Form/Expression/ConditionContainerForm.php +++ b/src/Form/Expression/ConditionContainerForm.php @@ -45,6 +45,14 @@ class ConditionContainerForm implements ExpressionFormInterface { */ protected $ruleExpression; + /** + * The expression plugin manager service. + * + * @var \Drupal\rules\Engine\ExpressionManager + */ + protected $expressionManager; + + /** * Creates a new object of this class. */ @@ -53,6 +61,7 @@ public function __construct(ConditionExpressionContainerInterface $condition_con $this->rulesUiHandler = $this->getRulesUiHandler(); $this->component = $this->rulesUiHandler->getComponent(); $this->ruleExpression = $this->component->getExpression(); + $this->expressionManager = \Drupal::service('plugin.manager.rules_expression'); } /** @@ -67,12 +76,12 @@ public function form(array $form, FormStateInterface $form_state, $options = []) '#type' => 'container', '#id' => 'rules-plugin-add-help', 'content' => array( - '#markup' => t('You are about to add a new @plugin to the @config-plugin %label. Use indentation to make conditions a part of this logic group. See the online documentation for more information on condition sets.', + '#markup' => t('You are about to add a new @plugin to the @config-plugin %label. Use indentation to make conditions a part of this logic group. See the online documentation for more information on condition sets.', array( '@plugin' => $this->conditionContainer->getLabel(), '@config-plugin' => $config->bundle(), '%label' => $config->label(), - '@url' => 'http://drupal.org/node/1300034', + ':url' => 'http://drupal.org/node/1300034', ) ), ), @@ -177,11 +186,11 @@ private function buildRow(&$form, FormStateInterface &$form_state, $condition, $ '#attributes' => ['class' => ['condition-weight']], ]; - // Operations (dropbutton) column. + // Operations column. $rules_ui_handler = $this->getRulesUiHandler(); $row['operations'] = [ 'data' => [ - '#type' => 'dropbutton', + '#type' => 'operations', '#links' => [ 'edit' => [ 'title' => $this->t('Edit'), @@ -313,7 +322,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * The id key in the values. * * @return mixed - * A heirarchical tree of values. + * A hierarchical tree of values. */ private function buildElementTree($values, &$elements, $pidKey, $idKey = NULL) { $grouped = array(); @@ -352,13 +361,12 @@ private function buildElementTree($values, &$elements, $pidKey, $idKey = NULL) { */ private function mirrorElements($values) { $elements = NULL; - $expressionManager = \Drupal::service('plugin.manager.rules_expression'); foreach ($values as $uuid => $element) { $condition = $this->ruleExpression->getExpression($element['id']); $configuration = $condition->getConfiguration(); unset($configuration['uuid']); $configuration['weight'] = $element['weight']; - $elements[$uuid] = $expressionManager->createInstance($condition->getPluginId(), $configuration); + $elements[$uuid] = $this->expressionManager->createInstance($condition->getPluginId(), $configuration); } return $elements; From ac10aef59d696b354ec4ebb4d6fb363aa0940561 Mon Sep 17 00:00:00 2001 From: Daniel Sasser Date: Wed, 23 Nov 2016 11:43:05 -0800 Subject: [PATCH 4/4] Issue #2799361 by coderwan: Ensure component config entity is updated from component changes during save. --- src/Form/RulesComponentEditForm.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Form/RulesComponentEditForm.php b/src/Form/RulesComponentEditForm.php index 8ffd6150..a5472286 100644 --- a/src/Form/RulesComponentEditForm.php +++ b/src/Form/RulesComponentEditForm.php @@ -72,6 +72,8 @@ protected function actions(array $form, FormStateInterface $form_state) { */ public function save(array $form, FormStateInterface $form_state) { $this->rulesUiHandler->getForm()->submitForm($form, $form_state); + $component = $this->rulesUiHandler->getComponent(); + $this->entity->updateFromComponent($component); // Persist changes by saving the entity. parent::save($form, $form_state);