rules.plugins.inc 26 KB

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