rules.plugins.inc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. <?php
  2. /**
  3. * @file Contains plugin info and implementations not needed for rule evaluation.
  4. */
  5. /**
  6. * Implements a rules action.
  7. */
  8. class RulesAction extends RulesAbstractPlugin implements RulesActionInterface {
  9. protected $itemName = 'action';
  10. /**
  11. * Execute the callback and update/save data as specified by the action.
  12. */
  13. protected function executeCallback(array $args, RulesState $state = NULL) {
  14. rules_log('Evaluating the action %name.', array('%name' => $this->elementName), RulesLog::INFO, $this);
  15. $return = $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args));
  16. // Get the (partially) wrapped arguments.
  17. $args = $state->currentArguments;
  18. if (is_array($return)) {
  19. foreach ($return as $name => $data) {
  20. // Add provided variables.
  21. if (isset($this->info['provides'][$name])) {
  22. $var_name = isset($this->settings[$name . ':var']) ? $this->settings[$name . ':var'] : $name;
  23. if (!$state->varInfo($var_name)) {
  24. $state->addVariable($var_name, $data, $this->info['provides'][$name]);
  25. rules_log('Added the provided variable %name of type %type', array('%name' => $var_name, '%type' => $this->info['provides'][$name]['type']), RulesLog::INFO, $this);
  26. if (!empty($this->info['provides'][$name]['save']) && $state->variables[$var_name] instanceof EntityMetadataWrapper) {
  27. $state->saveChanges($var_name, $state->variables[$var_name]);
  28. }
  29. }
  30. }
  31. // Support updating variables by returning the values.
  32. elseif (!isset($this->info['provides'][$name])) {
  33. // Update the data value using the wrapper.
  34. if (isset($args[$name]) && $args[$name] instanceof EntityMetadataWrapper) {
  35. try {
  36. $args[$name]->set($data);
  37. }
  38. catch (EntityMetadataWrapperException $e) {
  39. throw new RulesEvaluationException('Unable to update the argument for parameter %name: %error', array('%name' => $name, '%error' => $e->getMessage()), $this);
  40. }
  41. }
  42. elseif (array_key_exists($name, $args)) {
  43. // Map back to the source variable name and update it.
  44. $var_name = !empty($this->settings[$name . ':select']) ? str_replace('-', '_', $this->settings[$name . ':select']) : $name;
  45. $state->variables[$var_name] = $data;
  46. }
  47. }
  48. }
  49. }
  50. // Save parameters as defined in the parameter info.
  51. if ($return !== FALSE) {
  52. foreach ($this->info['parameter'] as $name => $info) {
  53. if (!empty($info['save']) && $args[$name] instanceof EntityMetadataWrapper) {
  54. if (isset($this->settings[$name . ':select'])) {
  55. $state->saveChanges($this->settings[$name . ':select'], $args[$name]);
  56. }
  57. else {
  58. // Wrapper has been configured via direct input, so just save.
  59. rules_log('Saved argument of type %type for parameter %name.', array('%name' => $name, '%type' => $args[$name]->type()));
  60. $args[$name]->save();
  61. }
  62. }
  63. }
  64. }
  65. }
  66. }
  67. /**
  68. * Implements a rules condition.
  69. */
  70. class RulesCondition extends RulesAbstractPlugin implements RulesConditionInterface {
  71. protected $itemName = 'condition';
  72. protected $negate = FALSE;
  73. public function providesVariables() {
  74. return array();
  75. }
  76. public function negate($negate = TRUE) {
  77. $this->negate = (bool) $negate;
  78. return $this;
  79. }
  80. public function isNegated() {
  81. return $this->negate;
  82. }
  83. protected function executeCallback(array $args, RulesState $state = NULL) {
  84. $return = (bool) $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args));
  85. rules_log('The condition %name evaluated to %bool', array('%name' => $this->elementName, '%bool' => $return ? 'TRUE' : 'FALSE'), RulesLog::INFO, $this);
  86. return $this->negate ? !$return : $return;
  87. }
  88. public function __sleep() {
  89. return parent::__sleep() + array('negate' => 'negate');
  90. }
  91. /**
  92. * Just return the boolean result.
  93. */
  94. protected function returnVariables(RulesState $state, $result = NULL) {
  95. return $result;
  96. }
  97. protected function exportToArray() {
  98. $not = $this->negate ? 'NOT ' : '';
  99. $export = $this->exportSettings();
  100. // Abbreviate the export making "USING" implicit.
  101. return array($not . $this->elementName => isset($export['USING']) ? $export['USING'] : array());
  102. }
  103. public function import(array $export) {
  104. $this->elementName = rules_array_key($export);
  105. if (strpos($this->elementName, 'NOT ') === 0) {
  106. $this->elementName = substr($this->elementName, 4);
  107. $this->negate = TRUE;
  108. }
  109. // After setting the element name, setup the element again so the right
  110. // element info is loaded.
  111. $this->setUp();
  112. // Re-add 'USING' which has been removed for abbreviation.
  113. $this->importSettings(array('USING' => reset($export)));
  114. }
  115. public function label() {
  116. $label = parent::label();
  117. return $this->negate ? t('NOT @condition', array('@condition' => $label)) : $label;
  118. }
  119. }
  120. /**
  121. * An actual rule.
  122. * Note: A rule also implements the RulesActionInterface (inherited).
  123. */
  124. class Rule extends RulesActionContainer {
  125. protected $conditions = NULL;
  126. protected $itemName = 'rule';
  127. public $label = 'unlabeled';
  128. public function __construct($variables = array(), $providesVars = array()) {
  129. parent::__construct($variables, $providesVars);
  130. // Initialize the conditions container.
  131. if (!isset($this->conditions)) {
  132. $this->conditions = rules_and();
  133. // Don't use setParent() to avoid having it added to the children.
  134. $this->conditions->parent = $this;
  135. }
  136. }
  137. /**
  138. * Get an iterator over all contained conditions. Note that this iterator also
  139. * implements the ArrayAcces interface.
  140. *
  141. * @return RulesRecursiveElementIterator
  142. */
  143. public function conditions() {
  144. return $this->conditions->getIterator();
  145. }
  146. /**
  147. * Returns the "And" condition container, which contains all conditions of
  148. * this rule.
  149. *
  150. * @return RulesAnd
  151. */
  152. public function conditionContainer() {
  153. return $this->conditions;
  154. }
  155. public function __sleep() {
  156. return parent::__sleep() + drupal_map_assoc(array('conditions', 'label'));
  157. }
  158. /**
  159. * Get an iterator over all contained actions. Note that this iterator also
  160. * implements the ArrayAccess interface.
  161. *
  162. * @return RulesRecursiveElementIterator
  163. */
  164. public function actions() {
  165. return parent::getIterator();
  166. }
  167. /**
  168. * Add a condition. Pass either an instance of the RulesConditionInterface
  169. * or the arguments as needed by rules_condition().
  170. *
  171. * @return Rule
  172. * Returns $this to support chained usage.
  173. */
  174. public function condition($name, $settings = array()) {
  175. $this->conditions->condition($name, $settings);
  176. return $this;
  177. }
  178. public function sortChildren($deep = FALSE) {
  179. $this->conditions->sortChildren($deep);
  180. parent::sortChildren($deep);
  181. }
  182. public function evaluate(RulesState $state) {
  183. rules_log('Evaluating conditions of rule %label.', array('%label' => $this->label), RulesLog::INFO, $this);
  184. if ($this->conditions->evaluate($state)) {
  185. rules_log('Rule %label fires.', array('%label' => $this->label), RulesLog::INFO, $this, TRUE);
  186. parent::evaluate($state);
  187. rules_log('Rule %label has fired.', array('%label' => $this->label), RulesLog::INFO, $this, FALSE);
  188. }
  189. }
  190. /**
  191. * Fires the rule, i.e. evaluates the rule without checking its conditions.
  192. *
  193. * @see RulesPlugin::evaluate()
  194. */
  195. public function fire(RulesState $state) {
  196. rules_log('Firing rule %label.', array('%label' => $this->label), RulesLog::INFO, $this);
  197. parent::evaluate($state);
  198. }
  199. public function integrityCheck() {
  200. parent::integrityCheck();
  201. $this->conditions->integrityCheck();
  202. return $this;
  203. }
  204. public function access() {
  205. return (!isset($this->conditions) || $this->conditions->access()) && parent::access();
  206. }
  207. public function dependencies() {
  208. return array_keys(array_flip($this->conditions->dependencies()) + array_flip(parent::dependencies()));
  209. }
  210. public function destroy() {
  211. $this->conditions->destroy();
  212. parent::destroy();
  213. }
  214. /**
  215. * @return RulesRecursiveElementIterator
  216. */
  217. public function getIterator() {
  218. $array = array_merge(array($this->conditions), $this->children);
  219. return new RulesRecursiveElementIterator($array);
  220. }
  221. protected function stateVariables($element = NULL) {
  222. // Don't add in provided action variables for the conditions.
  223. if (isset($element) && $element === $this->conditions) {
  224. return $this->availableVariables();
  225. }
  226. $vars = parent::stateVariables($element);
  227. // Take variable info assertions of conditions into account.
  228. if ($assertions = $this->conditions->variableInfoAssertions()) {
  229. $vars = RulesData::addMetadataAssertions($vars, $assertions);
  230. }
  231. return $vars;
  232. }
  233. protected function exportFlat() {
  234. return $this->isRoot();
  235. }
  236. protected function exportToArray() {
  237. $export = parent::exportToArray();
  238. if (!$this->isRoot()) {
  239. $export[strtoupper($this->plugin())]['LABEL'] = $this->label;
  240. }
  241. return $export;
  242. }
  243. protected function exportChildren($key = NULL) {
  244. $export = array();
  245. if ($this->conditions->children) {
  246. $export = $this->conditions->exportChildren('IF');
  247. }
  248. return $export + parent::exportChildren('DO');
  249. }
  250. public function import(array $export) {
  251. if (!$this->isRoot() && isset($export[strtoupper($this->plugin())]['LABEL'])) {
  252. $this->label = $export[strtoupper($this->plugin())]['LABEL'];
  253. }
  254. parent::import($export);
  255. }
  256. protected function importChildren($export, $key = NULL) {
  257. if (!empty($export['IF'])) {
  258. $this->conditions->importChildren($export, 'IF');
  259. }
  260. parent::importChildren($export, 'DO');
  261. }
  262. public function __clone() {
  263. parent::__clone();
  264. $this->conditions = clone $this->conditions;
  265. $this->conditions->parent = $this;
  266. }
  267. /**
  268. * Rules may not provided any variable info assertions, as Rules are only
  269. * conditionally executed.
  270. */
  271. protected function variableInfoAssertions() {
  272. return array();
  273. }
  274. /**
  275. * Overridden to ensure the whole Rule is deleted at once.
  276. */
  277. public function delete($keep_children = FALSE) {
  278. parent::delete($keep_children);
  279. }
  280. /**
  281. * Overriden to expose the variables of all actions for embedded rules.
  282. */
  283. public function providesVariables() {
  284. $provides = parent::providesVariables();
  285. if (!$this->isRoot()) {
  286. foreach ($this->actions() as $action) {
  287. $provides += $action->providesVariables();
  288. }
  289. }
  290. return $provides;
  291. }
  292. public function resetInternalCache() {
  293. parent::resetInternalCache();
  294. $this->conditions->resetInternalCache();
  295. }
  296. }
  297. /**
  298. * Represents rules getting triggered by events.
  299. */
  300. class RulesReactionRule extends Rule implements RulesTriggerableInterface {
  301. protected $itemName = 'reaction rule';
  302. protected $events = array(), $eventSettings = array();
  303. /**
  304. * Implements RulesTriggerableInterface::events().
  305. */
  306. public function events() {
  307. return $this->events;
  308. }
  309. /**
  310. * Implements RulesTriggerableInterface::removeEvent().
  311. */
  312. public function removeEvent($event) {
  313. if (($id = array_search($event, $this->events)) !== FALSE) {
  314. unset($this->events[$id]);
  315. }
  316. return $this;
  317. }
  318. /**
  319. * Implements RulesTriggerableInterface::event().
  320. */
  321. public function event($event_name, array $settings = NULL) {
  322. // Process any settings and determine the configured event's name.
  323. if ($settings) {
  324. $handler = rules_get_event_handler($event_name, $settings);
  325. if ($suffix = $handler->getEventNameSuffix()) {
  326. $event_name .= '--' . $suffix;
  327. $this->eventSettings[$event_name] = $settings;
  328. }
  329. else {
  330. // Do not store settings if there is no suffix.
  331. unset($this->eventSettings[$event_name]);
  332. }
  333. }
  334. if (array_search($event_name, $this->events) === FALSE) {
  335. $this->events[] = $event_name;
  336. }
  337. return $this;
  338. }
  339. /**
  340. * Implements RulesTriggerableInterface::getEventSettings().
  341. */
  342. public function getEventSettings($event_name) {
  343. if (isset($this->eventSettings[$event_name])) {
  344. return $this->eventSettings[$event_name];
  345. }
  346. }
  347. public function integrityCheck() {
  348. parent::integrityCheck();
  349. // Check integrity of the configured events.
  350. foreach ($this->events as $event_name) {
  351. $handler = rules_get_event_handler($event_name, $this->getEventSettings($event_name));
  352. $handler->validate();
  353. }
  354. return $this;
  355. }
  356. /**
  357. * Reaction rules can't add variables to the parent scope, so clone $state.
  358. */
  359. public function evaluate(RulesState $state) {
  360. // Implement recursion prevention for reaction rules.
  361. if ($state->isBlocked($this)) {
  362. return rules_log('Not evaluating @plugin %label to prevent recursion.', array('%label' => $this->label(), '@plugin' => $this->plugin()), RulesLog::INFO, $this);
  363. }
  364. $state->block($this);
  365. $copy = clone $state;
  366. parent::evaluate($copy);
  367. $state->unblock($this);
  368. }
  369. public function access() {
  370. foreach ($this->events as $event_name) {
  371. $event_info = rules_get_event_info($event_name);
  372. if (!empty($event_info['access callback']) && !call_user_func($event_info['access callback'], 'event', $event_info['name'])) {
  373. return FALSE;
  374. }
  375. }
  376. return parent::access();
  377. }
  378. public function dependencies() {
  379. $modules = array_flip(parent::dependencies());
  380. foreach ($this->events as $event_name) {
  381. $event_info = rules_get_event_info($event_name);
  382. if (isset($event_info['module'])) {
  383. $modules[$event_info['module']] = TRUE;
  384. }
  385. }
  386. return array_keys($modules);
  387. }
  388. public function providesVariables() {
  389. return array();
  390. }
  391. public function parameterInfo($optional = FALSE) {
  392. // If executed directly, all variables as defined by the event need to
  393. // be passed.
  394. return rules_filter_array($this->availableVariables(), 'handler', FALSE);
  395. }
  396. public function availableVariables() {
  397. if (!isset($this->availableVariables)) {
  398. if (isset($this->parent)) {
  399. // Return the event variables provided by the event set, once cached.
  400. $this->availableVariables = $this->parent->stateVariables();
  401. }
  402. else {
  403. // The intersection of the variables provided by the events are
  404. // available.
  405. foreach ($this->events as $event_name) {
  406. $handler = rules_get_event_handler($event_name, $this->getEventSettings($event_name));
  407. if (isset($this->availableVariables)) {
  408. $event_vars = $handler->availableVariables();
  409. // Merge variable info by intersecting the variable-info keys also,
  410. // so we have only metadata available that is valid for all of the
  411. // provided variables.
  412. foreach (array_intersect_key($this->availableVariables, $event_vars) as $name => $variable_info) {
  413. $this->availableVariables[$name] = array_intersect_key($variable_info, $event_vars[$name]);
  414. }
  415. }
  416. else {
  417. $this->availableVariables = $handler->availableVariables();
  418. }
  419. }
  420. $this->availableVariables = isset($this->availableVariables) ? RulesState::defaultVariables() + $this->availableVariables : RulesState::defaultVariables();
  421. }
  422. }
  423. return $this->availableVariables;
  424. }
  425. public function __sleep() {
  426. return parent::__sleep() + drupal_map_assoc(array('events', 'eventSettings'));
  427. }
  428. protected function exportChildren($key = 'ON') {
  429. foreach ($this->events as $event_name) {
  430. $export[$key][$event_name] = (array) $this->getEventSettings($event_name);
  431. }
  432. return $export + parent::exportChildren();
  433. }
  434. protected function importChildren($export, $key = 'ON') {
  435. // Detect and support old-style exports: a numerically indexed array of
  436. // event names.
  437. if (is_string(reset($export[$key])) && is_numeric(key($export[$key]))) {
  438. $this->events = $export[$key];
  439. }
  440. else {
  441. $this->events = array_keys($export[$key]);
  442. $this->eventSettings = array_filter($export[$key]);
  443. }
  444. parent::importChildren($export);
  445. }
  446. /**
  447. * Overrides optimize().
  448. */
  449. public function optimize() {
  450. parent::optimize();
  451. // No need to keep event settings for evaluation.
  452. $this->eventSettings = array();
  453. }
  454. }
  455. /**
  456. * A logical AND.
  457. */
  458. class RulesAnd extends RulesConditionContainer {
  459. protected $itemName = 'and';
  460. public function evaluate(RulesState $state) {
  461. foreach ($this->children as $condition) {
  462. if (!$condition->evaluate($state)) {
  463. rules_log('AND evaluated to FALSE.');
  464. return $this->negate;
  465. }
  466. }
  467. rules_log('AND evaluated to TRUE.');
  468. return !$this->negate;
  469. }
  470. public function label() {
  471. return !empty($this->label) ? $this->label : ($this->negate ? t('NOT AND') : t('AND'));
  472. }
  473. }
  474. /**
  475. * A logical OR.
  476. */
  477. class RulesOr extends RulesConditionContainer {
  478. protected $itemName = 'or';
  479. public function evaluate(RulesState $state) {
  480. foreach ($this->children as $condition) {
  481. if ($condition->evaluate($state)) {
  482. rules_log('OR evaluated to TRUE.');
  483. return !$this->negate;
  484. }
  485. }
  486. rules_log('OR evaluated to FALSE.');
  487. return $this->negate;
  488. }
  489. public function label() {
  490. return !empty($this->label) ? $this->label : ($this->negate ? t('NOT OR') : t('OR'));
  491. }
  492. /**
  493. * Overridden to exclude all variable assertions as in an OR we cannot assert
  494. * the children are successfully evaluated.
  495. */
  496. protected function stateVariables($element = NULL) {
  497. $vars = $this->availableVariables();
  498. if (isset($element)) {
  499. // Add in variables provided by siblings executed before the element.
  500. foreach ($this->children as $child) {
  501. if ($child === $element) {
  502. break;
  503. }
  504. $vars += $child->providesVariables();
  505. }
  506. }
  507. return $vars;
  508. }
  509. }
  510. /**
  511. * A loop element.
  512. */
  513. class RulesLoop extends RulesActionContainer {
  514. protected $itemName = 'loop';
  515. protected $listItemInfo;
  516. public function __construct($settings = array(), $variables = NULL) {
  517. $this->setUp();
  518. $this->settings = (array) $settings + array(
  519. 'item:var' => 'list_item',
  520. 'item:label' => t('Current list item'),
  521. );
  522. if (!empty($variables)) {
  523. $this->info['variables'] = $variables;
  524. }
  525. }
  526. public function pluginParameterInfo() {
  527. $info['list'] = array(
  528. 'type' => 'list',
  529. 'restriction' => 'selector',
  530. 'label' => t('List'),
  531. 'description' => t('The list to loop over. The loop will step through each item in the list, allowing further actions on them. See <a href="@url"> the online handbook</a> for more information on how to use loops.',
  532. array('@url' => rules_external_help('loops'))),
  533. );
  534. return $info;
  535. }
  536. public function integrityCheck() {
  537. parent::integrityCheck();
  538. $this->checkVarName($this->settings['item:var']);
  539. }
  540. public function listItemInfo() {
  541. if (!isset($this->listItemInfo)) {
  542. if ($info = $this->getArgumentInfo('list')) {
  543. // Pass through the variable info keys like property info.
  544. $this->listItemInfo = array_intersect_key($info, array_flip(array('type', 'property info', 'bundle')));
  545. $this->listItemInfo['type'] = isset($info['type']) ? entity_property_list_extract_type($info['type']) : 'unknown';
  546. }
  547. else {
  548. $this->listItemInfo = array('type' => 'unknown');
  549. }
  550. $this->listItemInfo['label'] = $this->settings['item:label'];
  551. }
  552. return $this->listItemInfo;
  553. }
  554. public function evaluate(RulesState $state) {
  555. try {
  556. $param_info = $this->pluginParameterInfo();
  557. $list = $this->getArgument('list', $param_info['list'], $state);
  558. $item_var_info = $this->listItemInfo();
  559. $item_var_name = $this->settings['item:var'];
  560. if (isset($this->settings['list:select'])) {
  561. rules_log('Looping over the list items of %selector', array('%selector' => $this->settings['list:select']), RulesLog::INFO, $this);
  562. }
  563. // Loop over the list and evaluate the children for each list item.
  564. foreach ($list as $key => $item) {
  565. // Use a separate state so variables are available in the loop only.
  566. $state2 = clone $state;
  567. $state2->addVariable($item_var_name, $list[$key], $item_var_info);
  568. parent::evaluate($state2);
  569. // Update variables from parent scope.
  570. foreach ($state->variables as $var_key => &$var_value) {
  571. if (array_key_exists($var_key, $state2->variables)) {
  572. $var_value = $state2->variables[$var_key];
  573. }
  574. }
  575. }
  576. }
  577. catch (RulesEvaluationException $e) {
  578. rules_log($e->msg, $e->args, $e->severity);
  579. rules_log('Unable to evaluate %name.', array('%name' => $this->getPluginName()), RulesLog::WARN, $this);
  580. }
  581. }
  582. protected function stateVariables($element = NULL) {
  583. return array($this->settings['item:var'] => $this->listItemInfo()) + parent::stateVariables($element);
  584. }
  585. public function label() {
  586. return !empty($this->label) ? $this->label : t('Loop');
  587. }
  588. protected function exportChildren($key = 'DO') {
  589. return parent::exportChildren($key);
  590. }
  591. protected function importChildren($export, $key = 'DO') {
  592. parent::importChildren($export, $key);
  593. }
  594. protected function exportSettings() {
  595. $export = parent::exportSettings();
  596. $export['ITEM'][$this->settings['item:var']] = $this->settings['item:label'];
  597. return $export;
  598. }
  599. protected function importSettings($export) {
  600. parent::importSettings($export);
  601. if (isset($export['ITEM'])) {
  602. $this->settings['item:var'] = rules_array_key($export['ITEM']);
  603. $this->settings['item:label'] = reset($export['ITEM']);
  604. }
  605. }
  606. }
  607. /**
  608. * An action set component.
  609. */
  610. class RulesActionSet extends RulesActionContainer {
  611. protected $itemName = 'action set';
  612. }
  613. /**
  614. * A set of rules to execute upon defined variables.
  615. */
  616. class RulesRuleSet extends RulesActionContainer {
  617. protected $itemName = 'rule set';
  618. /**
  619. * @return RulesRuleSet
  620. */
  621. public function rule($rule) {
  622. return $this->action($rule);
  623. }
  624. protected function exportChildren($key = 'RULES') {
  625. return parent::exportChildren($key);
  626. }
  627. protected function importChildren($export, $key = 'RULES') {
  628. parent::importChildren($export, $key);
  629. }
  630. }
  631. /**
  632. * This class is used for caching the rules to be evaluated per event.
  633. */
  634. class RulesEventSet extends RulesRuleSet {
  635. protected $itemName = 'event set';
  636. // Event sets may recurse as we block recursions on rule-level.
  637. public $recursion = TRUE;
  638. public function __construct($info = array()) {
  639. $this->setup();
  640. $this->info = $info;
  641. }
  642. public function executeByArgs($args = array()) {
  643. rules_log('Reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, TRUE);
  644. $state = $this->setUpState($args);
  645. module_invoke_all('rules_config_execute', $this);
  646. $this->evaluate($state);
  647. $state->cleanUp($this);
  648. rules_log('Finished reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, FALSE);
  649. }
  650. /**
  651. * Cache event-sets per event to allow efficient usage via rules_invoke_event().
  652. *
  653. * @see rules_get_cache()
  654. * @see rules_invoke_event()
  655. */
  656. public static function rebuildEventCache() {
  657. // Set up the per-event cache.
  658. $events = rules_fetch_data('event_info');
  659. $sets = array();
  660. // Add all rules associated with this event to an EventSet for caching.
  661. $rules = rules_config_load_multiple(FALSE, array('plugin' => 'reaction rule', 'active' => TRUE));
  662. foreach ($rules as $name => $rule) {
  663. foreach ($rule->events() as $event_name) {
  664. $event_base_name = rules_get_event_base_name($event_name);
  665. // Skip not defined events.
  666. if (empty($events[$event_base_name])) {
  667. continue;
  668. }
  669. // Create an event set if not yet done.
  670. if (!isset($sets[$event_name])) {
  671. $handler = rules_get_event_handler($event_name, $rule->getEventSettings($event_name));
  672. // Start the event dispatcher for this event, if any.
  673. if ($handler instanceof RulesEventDispatcherInterface && !$handler->isWatching()) {
  674. $handler->startWatching();
  675. }
  676. // Update the event info with the variables available based on the
  677. // event settings.
  678. $event_info = $events[$event_base_name];
  679. $event_info['variables'] = $handler->availableVariables();
  680. $sets[$event_name] = new RulesEventSet($event_info);
  681. $sets[$event_name]->name = $event_name;
  682. }
  683. // If a rule is marked as dirty, check if this still applies.
  684. if ($rule->dirty) {
  685. rules_config_update_dirty_flag($rule);
  686. }
  687. if (!$rule->dirty) {
  688. // Clone the rule to avoid modules getting the changed version from
  689. // the static cache.
  690. $sets[$event_name]->rule(clone $rule);
  691. }
  692. }
  693. }
  694. // Create cache items for all created sets.
  695. foreach ($sets as $event_name => $set) {
  696. $set->sortChildren();
  697. $set->optimize();
  698. // Allow modules to alter the cached event set.
  699. drupal_alter('rules_event_set', $event_name, $set);
  700. rules_set_cache('event_' . $event_name, $set);
  701. }
  702. // Cache a whitelist of configured events so we can use it to speed up later
  703. // calls. See rules_invoke_event().
  704. variable_set('rules_event_whitelist', array_flip(array_keys($sets)));
  705. }
  706. protected function stateVariables($element = NULL) {
  707. return $this->availableVariables();
  708. }
  709. /**
  710. * Do not save since this class is for caching purposes only.
  711. *
  712. * @see RulesPlugin::save()
  713. */
  714. public function save($name = NULL, $module = 'rules') {
  715. return FALSE;
  716. }
  717. }