1 of 120

Coding with Rules�for Drupal 8

Josef Dabernig, Amazee Labs

2 of 120

twitter.com/dasjo

  • Facet API Pretty Paths�
  • Geocluster�
  • Drupal Centroamerica, Austria, Switzerland, ++�
  • Deputy Head Technology �@ Amazee Labs�

3 of 120

4 of 120

#d8rules team

  • fago (Rules creator)
  • klausi (co-maintainer)
  • dasjo (communication)
  • fubhy (developer)
  • nico grienauer (design)
  • steve purkiss (developer)

5 of 120

Rules

6 of 120

Rules

7 of 120

Rules

8 of 120

Rules

  • Build flexible workflows �using events, condition & actions
  • Send customized mails to notify your users
  • Create custom redirections, �system messages, breadcrumbs

9 of 120

Rules

  • Build flexible workflows �using events, condition & actions
  • Send customized mails to notify your users
  • Create custom redirections, �system messages, breadcrumbs
  • 300.000+ reported installs�(30% of all Drupal 7 sites)
  • Hundreds of integration modules
  • Entity API, Fields, Views, Webform, Context, Features, Search API, Tokens, Paths, Menus, Queue, �Field formatter conditions, ...

10 of 120

Drupal 8 wins

11 of 120

Drupal 8 wins

  • OOP, Dependency Injection
  • APIs
  • PHPUnit
  • Symfony2
  • Removed legacy modules
  • Web services built in
  • Front-end, responsive, ...

12 of 120

Drupal 8 for Rules

  • Plug-in API
  • Entity & Typed Data API
  • Conditions API
  • (Actions API -> forked)
  • Context API shared w/ Core, Page Manager
  • Configuration Management (CMI, YAML)

13 of 120

Reusable components (“rtools”)

  • Tokens (automatic, based on typed data)
  • Typed data widgets & formatters
  • Extended Context API
  • Embeddable Rules UI components
    • Actions & Conditions
    • Rules data selector for tokens, contexts

14 of 120

Reusable components (“rtools”)

15 of 120

Planned for site building

  • Admin UI usability improvements
  • (Simple Views Bulk operations in core)
  • “Inline Rules” instead of Rule sets / Rules conditional
  • Deployable configuration

16 of 120

Campaign

17 of 120

#d8rules goals

  • Accelerate Drupal 8 uptake by ensuring that Rules as a key contributed module is ready, early enough.
  • Enable flexible workflows in Drupal 8 sites that are easily configurable & reusable.
  • Make Drupal contributions sustainable by funding contributed code used on hundreds of thousands of sites.

18 of 120

19 of 120

Sponsors

20 of 120

Funding state

21 of 120

Rules 8.x �development status

22 of 120

Rules 8.x M1�Rules core API fundamentals

    • Rules core engine, plugin types
    • Align Rules condition & action APIs with core
    • Parameter configuration & Context mapping

23 of 120

Rules 8.x M2�Rules core completion with basic UI

    • Completed Rules engine features
    • Completed Rules plugins (Events, Loops, API)
    • Configuration entity, CMI and integrity checks
    • Basic UI and autocomplete functionality
    • API for embedding the Rules UI
    • Twig-style token replacements

24 of 120

Rules 8.x M3�Rules release

    • Stable Rules 8.x-3.0 Release
    • Rules Engine follow-ups
    • Complete Rules UI
    • Rules scheduler port
    • Port existing integrations (almost done!)

25 of 120

Sprints, trainings & sessions

Szeged, Portoroz, Austin, Drupalaton, Amsterdam, Zurich, Bogotá, Vienna, Milano, Montpellier, Bratislava, Sunderland, Barcelona, Mumbai, Heidelberg, Granada, ..

http://d8rules.org/events

26 of 120

Contribution

  • 114 forks, 40 contributors
    • paranoik, stevepurkiss, scuts, jibran, chindris, omissis, ndewhurst, jzavrl, MegaChriz, bbujisic, dawehner, torenware, bartfeenstra, M1r1k, rinasek, joashuataylor, lokapujya, icanblink, bojanz, a.milkovsky, mariancalinro, pjezek,czigor, mikl, nlisgo, nielsdefeyter, ...
  • 427 closed pull requests, Thank you all!

27 of 120

Getting started

  • Setup Drupal 8
  • Fork Rules 8.x on github:
    • https://github.com/fago/rules

28 of 120

29 of 120

Diving into Rules 8.x

30 of 120

Writing �Conditions, Actions & Events

31 of 120

Plug-ins

  • OOP
  • Annotations
  • Auto-loading
  • Discovery
  • Derivatives

32 of 120

Provide conditions

  • hook_rules_condition_info() �-> ?

33 of 120

Provide conditions

  • hook_rules_condition_info �-> Implement a Condition plug-in

34 of 120

/**

* Provides a 'Node is sticky' condition.

*

* @Condition(

* id = "rules_node_is_sticky",

* label = @Translation("Node is sticky"),

* category = @Translation("Node"),

* context = {…}

* )

*/

class NodeIsSticky extends RulesConditionBase {

/**

* {@inheritdoc}

*/

public function evaluate() {

$node = $this->getContextValue('node');

return $node->isSticky();

}

}

35 of 120

Provide actions

  • hook_rules_action_info �-> Implement an Action plug-in

36 of 120

/**

* Provides a 'Delete entity' action.

*

* @Action(

* id = "rules_entity_delete",

* label = @Translation("Delete entity"),

* category = @Translation("Entity"),

* context = {…}

* )

*/

class EntityDelete extends RulesActionBase {

/**

* {@inheritdoc}

*/

public function execute() {

$entity = $this->getContextValue('entity');

$entity->delete();

}

}

37 of 120

Provide events

  • hook_rules_event_info �-> Specify Symfony event metadata in *.rules.events.yml

38 of 120

Event example from rules.rules.events.yml

rules_user_login:

label: 'User has logged in'

category: 'User'

context:

account:

type: 'entity:user'

label: 'Logged in user'

39 of 120

Event invocation

/**� * Implements hook_user_login().� */function rules_user_login($account) {� // Set the account twice on the event: as the main subject but also in the// list of arguments.$event = new UserLoginEvent($account, ['account' => $account]);� $event_dispatcher = Drupal::service('event_dispatcher');� $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);�}

40 of 120

Event invocation

/**� * Implements hook_user_login().� */function rules_user_login($account) {� // Set the account twice on the event: as the main subject but also in the// list of arguments.$event = new UserLoginEvent($account, ['account' => $account]);� $event_dispatcher = Drupal::service('event_dispatcher');� $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);�}

41 of 120

Context

42 of 120

Context

  • Defining context for a plug-in

43 of 120

/**

* Provides a 'Delete entity' action.

*

* @Action(

* id = "rules_entity_delete",

* label = @Translation("Delete entity"),

* category = @Translation("Entity"),

* context = {…}

* )

*/

class EntityDelete extends RulesActionBase {

/**

* {@inheritdoc}

*/

public function execute() {

$entity = $this->getContextValue('entity');

$entity->delete();

}

}

44 of 120

/**

* Provides a 'Delete entity' action.

*

* @Action(

* id = "rules_entity_delete",

* label = @Translation("Delete entity"),

* category = @Translation("Entity"),

* context = {

* "entity" = @ContextDefinition("entity",

* label = @Translation("Entity"),

* description = @Translation("Specifies the entity,� which should be deleted permanently.")

* )

* }

* )

*/

class EntityDelete extends RulesActionBase {...}

45 of 120

Context

  • Defining context for a plug-in
  • Using context within a plug-in

46 of 120

class EntityDelete extends RulesActionBase {

/**

* Executes the action with the given context.

*/

public function doExecute(EntityInterface $entity) {

$entity->delete();

}

}

47 of 120

class EntityDelete extends RulesActionBase {

/**

* Executes the action with the given context.

*/

public function doExecute(EntityInterface $entity) {

$entity->delete();

}

}

48 of 120

class FetchEntityById extends RulesActionBase implements ContainerFactoryPluginInterface {

/**

* Executes the action with the given context.

*/

public function doExecute($entity_type, $entity_id) {

$storage = $this->entityManager->getStorage($entity_type);

$entity = $storage->load($entity_id);

$this->setProvidedValue('entity', $entity);

}

}

49 of 120

class FetchEntityById extends RulesActionBase implements ContainerFactoryPluginInterface {

/**

* Executes the action with the given context.

*/

public function doExecute($entity_type, $entity_id) {

$storage = $this->entityManager->getStorage($entity_type);

$entity = $storage->load($entity_id);

$this->setProvidedValue('entity', $entity);

}

}

50 of 120

Context

  • Defining context for a plug-in
  • Using context within a plug-in
  • Passing context to the plug-in

51 of 120

class ListCountIsTest extends RulesIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

// Test that the list count is greater than 2.

$this->condition

->setContextValue('list', [1, 2, 3, 4])

->setContextValue('operator', '>')

->setContextValue('value', '2');

$this->assertTrue($condition->evaluate());

}

}

52 of 120

class ListCountIsTest extends RulesIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

// Test that the list count is greater than 2.

$this->condition

->setContextValue('list', [1, 2, 3, 4])

->setContextValue('operator', '>')

->setContextValue('value', '2');

$this->assertTrue($condition->evaluate());

}

}

53 of 120

Storing configuration

54 of 120

Storing configuration

  • D7: Entity exportables�
  • Rules:
    • RulesPlugin->export()�-> ?
    • RulesEntityController->import()�-> ?

55 of 120

Storing configuration

  • D7: Entity exportables�
  • Rules:
    • RulesPlugin->export()�-> RulesExpression::getConfiguration()
    • RulesEntityController->import()�-> RulesExpression::setConfiguration()

56 of 120

Drupal 8: CMI

  • Rules leverages the Config System
  • Two types of config entities:
    • Reaction Rules (WIP)
    • Components
  • Inherit features from CMI
    • Config deployment / import&export / sync
    • Config Translation (instead of Entity i18n)
    • Default config

57 of 120

Provide default rule configs

  • hook_default_rules_configuration()�-> ?

58 of 120

Provide default rule configs

  • hook_default_rules_configuration()�-> rules.reaction.*.yml-> rules.component.*.yml

59 of 120

langcode: en

status: true

dependencies: {}

id: rules_test_default_component

label: Rules test default component

module: rules

description: 'Tests adding Rules component by default.'

tag: 'test'

core: 8.x

expression_id: rules_rule

configuration:��...

60 of 120

configuration:

id: rules_rule

context:

user:

type: 'entity:user'

label: User

conditions:

id: rules_and

conditions: {}

actions:

id: rules_action_set

actions:

- id: rules_action

action_id: rules_system_message

context_mapping:

message: 'user:mail:value'

61 of 120

Executing the component

$config_entity = RulesComponent::load('rules_test_default_component');

$expression = $config_entity->getExpression();

$expression

->setContextValue('user', \Drupal::currentUser())

->execute();

62 of 120

Describe data to Rules

Drupal 7

  • hook_rules_data_info()�-> ?
  • hook_entity_property_info_alter�-> ?

63 of 120

Typed Data API

  • Consistent way of interacting � with any data based on metadata
  • Part of Drupal 8 & Entity Fields
  • Defines a type system for PHP:
    • Primitive types (integer, float, string, dates, ..)
    • Complex types
    • Lists (with items of a specified type)

64 of 120

Data types

  • any
  • string, integer, uri, float, ...
    • email
    • timestamp, datetime_iso8601
    • timespan, duration_iso8601
  • entity
    • entity:node
    • entity:comment
  • field_item
    • field_item:string
    • field_item:text
    • field_item:image

65 of 120

/**

* The float data type.

*

* The plain value of a float is a regular PHP float. For setting the value

* any PHP variable that casts to a float may be passed.

*

* @DataType(

* id = "float",

* label = @Translation("Float")

* )

*/

class FloatData extends PrimitiveBase implements FloatInterface {

/**

* {@inheritdoc}

*/

public function getCastedValue() {

return (float) $this->value;

}

}

66 of 120

/**

* The float data type.

*

* The plain value of a float is a regular PHP float. For setting the value

* any PHP variable that casts to a float may be passed.

*

* @DataType(

* id = "float",

* label = @Translation("Float")

* )

*/

class FloatData extends PrimitiveBase implements FloatInterface {

/**

* {@inheritdoc}

*/

public function getCastedValue() {

return (float) $this->value;

}

}

67 of 120

/**

* Plugin implementation of the 'link' field type.

*

* @FieldType(

* id = "link",

* label = @Translation("Link"),

* description = @Translation(“..."),

* default_widget = "link_default",

* default_formatter = "link",

* constraints = {"LinkType" = {}}

* )

*/

class LinkItem extends FieldItemBase implements LinkItemInterface {

public static function propertyDefinitions(� FieldStorageDefinitionInterface $field_definition) {

$properties['url'] = DataDefinition::create('string')

->setLabel(t('URL'));

return $properties;

}

68 of 120

/**

* Plugin implementation of the 'link' field type.

*

* @FieldType(

* id = "link",

* label = @Translation("Link"),

* description = @Translation(“..."),

* default_widget = "link_default",

* default_formatter = "link",

* constraints = {"LinkType" = {}}

* )

*/

class LinkItem extends FieldItemBase implements LinkItemInterface {

public static function propertyDefinitions(� FieldStorageDefinitionInterface $field_definition) {

$properties['url'] = DataDefinition::create('string')

->setLabel(t('URL'));

return $properties;

}

69 of 120

Describe data to rules

  • hook_rules_data_info()�-> ?
  • hook_entity_property_info_alter�-> ?

70 of 120

Describe data to rules

  • hook_rules_data_info()�-> Data type plugins, Typed Data
  • hook_entity_property_info_alter�-> hook_data_type_info_alter()�-> hook_entity_base_field_info/alter()�-> hook_entity_bundle_field_info/alter()�-> FieldItem::propertyDefintions()

71 of 120

Every content entity & field type �in Drupal 8 is supported �by Rules out-of-the box!

72 of 120

[META] Rules 8.x UI

73 of 120

[META] Rules 8.x UI

74 of 120

75 of 120

76 of 120

d8rules.org - dasjo

77 of 120

78 of 120

Lists & Multiple values

  • list<foo> notation is gone
  • “list” data type & per data type class
  • Separate data definitions for lists vs. list items
  • Mark context as “multiple”

79 of 120

Creating a Rule with context

80 of 120

$rule = $this->expressionManager->createRule([

'context_definitions' => [

'test' => [

'type' => 'string',

'label' => 'Test string',

],

],

]);

81 of 120

$rule = $this->expressionManager->createRule([

'context_definitions' => [

'test' => [

'type' => 'string',

'label' => 'Test string',

],

],

]);

$rule->addCondition('rules_test_string_condition',� ContextConfig::create()

->map('text', 'test')�);

82 of 120

$rule = $this->expressionManager->createRule([

'context_definitions' => [

'test' => [

'type' => 'string',

'label' => 'Test string',

],

],

]);

$rule->addCondition('rules_test_string_condition',� ContextConfig::create()

->map('text', 'test')�);

$rule->addAction('rules_test_log');

83 of 120

$rule = $this->expressionManager->createRule([

'context_definitions' => [

'test' => [

'type' => 'string',

'label' => 'Test string',

],

],

]);

$rule->addCondition('rules_test_string_condition',� ContextConfig::create()

->map('text', 'test')�);

$rule->addAction('rules_test_log');

$rule->setContextValue('test', 'test value');

84 of 120

$rule = $this->expressionManager->createRule([

'context_definitions' => [

'test' => [

'type' => 'string',

'label' => 'Test string',

],

],

]);

$rule->addCondition('rules_test_string_condition',� ContextConfig::create()

->map('text', 'test')�);

$rule->addAction('rules_test_log');

$rule->setContextValue('test', 'test value');

$rule->execute();

85 of 120

Mapping required and provided context

86 of 120

$rule = $this->expressionManager->createRule();

87 of 120

$rule = $this->expressionManager->createRule();

// Condition provides a "provided_text" variable.

$rule->addCondition('rules_test_provider');

88 of 120

$rule = $this->expressionManager->createRule();

// Condition provides a "provided_text" variable.

$rule->addCondition('rules_test_provider');

// Action provides a "concatenated" variable.

$rule->addAction('rules_test_string', � ContextConfig::create()

->provideAs('text', 'provided_text')

);

89 of 120

$rule = $this->expressionManager->createRule();

// Condition provides a "provided_text" variable.

$rule->addCondition('rules_test_provider');

// Action provides a "concatenated" variable.

$rule->addAction('rules_test_string', � ContextConfig::create()

->provideAs('text', 'provided_text')

);

// Same action again now provides "concatenated2"

$rule->addAction('rules_test_string', � ContextConfig::create()

->map(text, 'concatenated')

->provideAs('concatenated', 'concatenated2')

);

90 of 120

$rule = $this->expressionManager->createRule();

// Condition provides a "provided_text" variable.

$rule->addCondition('rules_test_provider');

// Action provides a "concatenated" variable.

$rule->addAction('rules_test_string', � ContextConfig::create()

->provideAs('text', 'provided_text')

);

// Same action again now provides "concatenated2"

$rule->addAction('rules_test_string', � ContextConfig::create()

->map(text, 'concatenated')

->provideAs('concatenated', 'concatenated2')

);

$rule->execute();

91 of 120

Advanced / under the hood

92 of 120

Provide other Rules plugins

  • hook_rules_plugin_info �-> Implement a RulesExpression plugin

93 of 120

Provide input evaluators

  • hook_rules_evaluator_info�-> Implement a RulesDataProcessor plugin

94 of 120

/**

* A data processor for applying numerical offsets.

*

* The plugin configuration must contain the following entry:

* - offset: the value that should be added.

*

* @RulesDataProcessor(

* id = "rules_numeric_offset",

* label = @Translation("Apply numeric offset")

* )

*/

class NumericOffset extends PluginBase implements RulesDataProcessorInterface {

/**

* {@inheritdoc}

*/

public function process($value) {

return $value + $this->configuration['offset'];

}

}

95 of 120

Automated testing

96 of 120

Automated testing

  • RulesUnitTestBase extends UnitTestCase
    • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, …

97 of 120

Automated testing

  • RulesUnitTestBase extends UnitTestCase
    • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, …
  • RulesIntegrationTestBase
    • use ActionManager, ConditionManager, TypedDataManager, …

98 of 120

class DataListCountIs extends RulesConditionBase {

/**

* {@inheritdoc}

*/

public function evaluate() {

$list = $this->getContextValue('list');

$operator = $this->getContextValue('operator');

$value = $this->getContextValue('value');

switch ($operator) {

case '==':

return count($list) == $value;

case '<';

return count($list) < $value;

case '>';

return count($list) > $value;

}

}

}

99 of 120

class ListCountIsTest extends RulesIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

// Test that the list count is greater than 2.

$condition = $this->condition

->setContextValue('list', [1, 2, 3, 4])

->setContextValue('operator', '>')

->setContextValue('value', '2');

$this->assertTrue($condition->evaluate());

// Test that the list count is not equal to 0.

$condition = $this->condition

->setContextValue('list', [1, 2, 3])

->setContextValue('operator', '==')

->setContextValue('value', '0');

$this->assertFalse($condition->evaluate());

}

}

100 of 120

Automated testing

  • RulesUnitTestBase extends UnitTestCase
    • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, …
  • RulesIntegrationTestBase
    • use ActionManager, ConditionManager, TypedDataManager, …

101 of 120

Automated testing

  • RulesUnitTestBase extends UnitTestCase
    • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, …
  • RulesIntegrationTestBase
    • use ActionManager, ConditionManager, TypedDataManager, …
  • RulesEntityIntegrationTestBase

102 of 120

/**

* @Condition(

* id = "rules_entity_is_of_type",

* label = @Translation("Entity is of type"),

* category = @Translation("Entity"),

* context = {

* "entity" = @ContextDefinition("entity",

* label = @Translation("Entity"),

* ),

* "type" = @ContextDefinition("string",

* label = @Translation("Type"),

* )

* }

* )

*/

class EntityIsOfType extends RulesConditionBase {

public function evaluate() {

$provided_entity = $this->getContextValue('entity');

$specified_type = $this->getContextValue('type');

$entity_type = $provided_entity->getEntityTypeId();

// Check whether the entity type matches.

return $entity_type == $specified_type;

}

}

103 of 120

class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');

$entity->expects($this->exactly(2))

->method('getEntityTypeId')

->will($this->returnValue('node'));

// Add the test node to our context as the evaluated entity.

// First, test with a value that should evaluate TRUE.

$this->condition->setContextValue('entity', $entity)

->setContextValue('type', 'node');

$this->assertTrue($this->condition->evaluate());

// Then test with values that should evaluate FALSE.

$this->condition->setContextValue('type', 'taxonomy_term');

$this->assertFalse($this->condition->evaluate());

}

}

104 of 120

class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');

$entity->expects($this->exactly(2))

->method('getEntityTypeId')

->will($this->returnValue('node'));

// Add the test node to our context as the evaluated entity.

// First, test with a value that should evaluate TRUE.

$this->condition->setContextValue('entity', $entity)

->setContextValue('type', 'node');

$this->assertTrue($this->condition->evaluate());

// Then test with values that should evaluate FALSE.

$this->condition->setContextValue('type', 'taxonomy_term');

$this->assertFalse($this->condition->evaluate());

}

}

105 of 120

class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');

$entity->expects($this->exactly(2))

->method('getEntityTypeId')

->will($this->returnValue('node'));

// Add the test node to our context as the evaluated entity.

// First, test with a value that should evaluate TRUE.

$this->condition->setContextValue('entity', $entity)

->setContextValue('type', 'node');

$this->assertTrue($this->condition->evaluate());

// Then test with values that should evaluate FALSE.

$this->condition->setContextValue('type', 'taxonomy_term');

$this->assertFalse($this->condition->evaluate());

}

}

106 of 120

class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {

/**

* Tests evaluating the condition.

*

* @covers ::evaluate()

*/

public function testConditionEvaluation() {

$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');

$entity->expects($this->exactly(2))

->method('getEntityTypeId')

->will($this->returnValue('node'));

// Add the test node to our context as the evaluated entity.

// First, test with a value that should evaluate TRUE.

$this->condition->setContextValue('entity', $entity)

->setContextValue('type', 'node');

$this->assertTrue($this->condition->evaluate());

// Then test with values that should evaluate FALSE.

$this->condition->setContextValue('type', 'taxonomy_term');

$this->assertFalse($this->condition->evaluate());

}

}

107 of 120

RulesState

108 of 120

RulesState

  • variables
  • applyDataSelector
  • autoSave

109 of 120

Traits

110 of 120

trait StringTranslationTrait {

/**

* The string translation service.

*

* @var \Drupal\Core\StringTranslation\TranslationInterface

*/

protected $stringTranslation;

/**

* Translates a string to the current language or to a given language.

*

* See the t() documentation for details.

*/

protected function t($string, …) {

return $this->getStringTranslation()->translate($string, …);

}

}

111 of 120

class MyClass {

use StringTranslationTrait;

public function __construct(� TranslationInterface $string_translation) {

$this->stringTranslation = $string_translation;

}

/**

* Does something.

*/

public function doSth() {

// ...

$string = $this->t('Something');

// ...

}

}

112 of 120

Traits

  • RulesContextTrait
    • in addition to ContextAwarePluginBase
    • used in RulesActionBase and RulesConditionBase

113 of 120

Outro

114 of 120

115 of 120

Sprint with us!

  • Port actions & conditions
  • https://www.drupal.org/node/2245015

116 of 120

Sprint with us!

  • Port actions & conditions
  • https://www.drupal.org/node/2245015

117 of 120

118 of 120

Fluxkraft UI proposals

119 of 120

Rules Transformers

120 of 120

NoFlo