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/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 254793ba..8c425225 100644 --- a/src/Form/Expression/ActionContainerForm.php +++ b/src/Form/Expression/ActionContainerForm.php @@ -34,36 +34,80 @@ 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'] = [ - '#theme' => 'table', - '#caption' => $this->t('Actions'), - '#header' => [$this->t('Elements'), $this->t('Operations')], - '#empty' => t('None'), + $form['action_table_container']['action_table'] = [ + '#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_container']['action_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 column. + $rules_ui_handler = $this->getRulesUiHandler(); + $row['operations'] = [ + 'data' => [ + '#type' => 'operations', + '#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('action_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..ebc2205a 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,66 +24,365 @@ 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; + + /** + * The expression plugin manager service. + * + * @var \Drupal\rules\Engine\ExpressionManager + */ + protected $expressionManager; + + /** * 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(); + $this->expressionManager = \Drupal::service('plugin.manager.rules_expression'); } /** * {@inheritdoc} */ - public function form(array $form, FormStateInterface $form_state) { - $form['conditions'] = [ - '#type' => 'container', - ]; - - $form['conditions']['table'] = [ - '#theme' => 'table', - '#caption' => $this->t('Conditions'), - '#header' => [$this->t('Elements'), $this->t('Operations')], - '#empty' => t('None'), - ]; + 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', + ]; - 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(), - ]), - ], - ], + $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' => 'match', + 'relationship' => 'parent', + 'group' => 'condition-parent', + 'subgroup' => 'condition-parent', + 'source' => 'condition-id', + ], + [ + '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) { + $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); + } + + $this->buildFooter($form, $form_state); } + return $form; + } + + /** + * 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]; - // @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', - ]), + // 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']], ]; - return $form; + // Operations column. + $rules_ui_handler = $this->getRulesUiHandler(); + $row['operations'] = [ + 'data' => [ + '#type' => 'operations', + '#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(); + + $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); + } + } + } + + /** + * 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' => '