rules.state.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. <?php
  2. /**
  3. * @file Contains the state and data related stuff.
  4. */
  5. /**
  6. * The rules evaluation state.
  7. *
  8. * A rule element may clone the state, so any added variables are only visible
  9. * for elements in the current PHP-variable-scope.
  10. */
  11. class RulesState {
  12. /**
  13. * Globally keeps the ids of rules blocked due to recursion prevention.
  14. */
  15. static protected $blocked = array();
  16. /**
  17. * The known variables.
  18. */
  19. public $variables = array();
  20. /**
  21. * Holds info about the variables.
  22. */
  23. protected $info = array();
  24. /**
  25. * Keeps wrappers to be saved later on.
  26. */
  27. protected $save;
  28. /**
  29. * Holds the arguments while an element is executed. May be used by the
  30. * element to easily access the wrapped arguments.
  31. */
  32. public $currentArguments;
  33. /**
  34. * Variable for saving currently blocked configs for serialization.
  35. */
  36. protected $currentlyBlocked;
  37. public function __construct() {
  38. // Use an object in order to ensure any cloned states reference the same
  39. // save information.
  40. $this->save = new ArrayObject();
  41. $this->addVariable('site', FALSE, self::defaultVariables('site'));
  42. }
  43. /**
  44. * Adds the given variable to the given execution state.
  45. */
  46. public function addVariable($name, $data, $info) {
  47. $this->info[$name] = $info + array(
  48. 'skip save' => FALSE,
  49. 'type' => 'unknown',
  50. 'handler' => FALSE,
  51. );
  52. if (empty($this->info[$name]['handler'])) {
  53. $this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
  54. }
  55. }
  56. /**
  57. * Runs post-evaluation tasks, such as saving variables.
  58. */
  59. public function cleanUp() {
  60. // Make changes permanent.
  61. foreach ($this->save->getArrayCopy() as $selector => $wrapper) {
  62. $this->saveNow($selector);
  63. }
  64. unset($this->currentArguments);
  65. }
  66. /**
  67. * Block a rules configuration from execution.
  68. */
  69. public function block($rules_config) {
  70. if (empty($rules_config->recursion) && $rules_config->id) {
  71. self::$blocked[$rules_config->id] = TRUE;
  72. }
  73. }
  74. /**
  75. * Unblock a rules configuration from execution.
  76. */
  77. public function unblock($rules_config) {
  78. if (empty($rules_config->recursion) && $rules_config->id) {
  79. unset(self::$blocked[$rules_config->id]);
  80. }
  81. }
  82. /**
  83. * Returns whether a rules configuration should be blocked from execution.
  84. */
  85. public function isBlocked($rule_config) {
  86. return !empty($rule_config->id) && isset(self::$blocked[$rule_config->id]);
  87. }
  88. /**
  89. * Get the info about the state variables or a single variable.
  90. */
  91. public function varInfo($name = NULL) {
  92. if (isset($name)) {
  93. return isset($this->info[$name]) ? $this->info[$name] : FALSE;
  94. }
  95. return $this->info;
  96. }
  97. /**
  98. * Returns whether the given wrapper is savable.
  99. */
  100. public function isSavable($wrapper) {
  101. return ($wrapper instanceof EntityDrupalWrapper && entity_type_supports($wrapper->type(), 'save')) || $wrapper instanceof RulesDataWrapperSavableInterface;
  102. }
  103. /**
  104. * Returns whether the variable with the given name is an entity.
  105. */
  106. public function isEntity($name) {
  107. $entity_info = entity_get_info();
  108. return isset($this->info[$name]['type']) && isset($entity_info[$this->info[$name]['type']]);
  109. }
  110. /**
  111. * Gets a variable.
  112. *
  113. * If necessary, the specified handler is invoked to fetch the variable.
  114. *
  115. * @param $name
  116. * The name of the variable to return.
  117. *
  118. * @return
  119. * The variable or a EntityMetadataWrapper containing the variable.
  120. *
  121. * @throws RulesEvaluationException
  122. * Throws a RulesEvaluationException in case we have info about the
  123. * requested variable, but it is not defined.
  124. */
  125. public function &get($name) {
  126. if (!array_key_exists($name, $this->variables)) {
  127. // If there is handler to load the variable, do it now.
  128. if (!empty($this->info[$name]['handler'])) {
  129. $data = call_user_func($this->info[$name]['handler'], rules_unwrap_data($this->variables), $name, $this->info[$name]);
  130. $this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
  131. $this->info[$name]['handler'] = FALSE;
  132. if (!isset($data)) {
  133. throw new RulesEvaluationException('Unable to load variable %name, aborting.', array('%name' => $name), NULL, RulesLog::INFO);
  134. }
  135. }
  136. else {
  137. throw new RulesEvaluationException('Unable to get variable %name, it is not defined.', array('%name' => $name), NULL, RulesLog::ERROR);
  138. }
  139. }
  140. return $this->variables[$name];
  141. }
  142. /**
  143. * Apply permanent changes provided the wrapper's data type is savable.
  144. *
  145. * @param $selector
  146. * The data selector of the wrapper to save or just a variable name.
  147. * @param $immediate
  148. * Pass FALSE to postpone saving to later on. Else it's immediately saved.
  149. */
  150. public function saveChanges($selector, $wrapper, $immediate = FALSE) {
  151. $info = $wrapper->info();
  152. if (empty($info['skip save']) && $this->isSavable($wrapper)) {
  153. $this->save($selector, $wrapper, $immediate);
  154. }
  155. // No entity, so try saving the parent.
  156. elseif (empty($info['skip save']) && isset($info['parent']) && !($wrapper instanceof EntityDrupalWrapper)) {
  157. // Cut of the last part of the selector.
  158. $selector = implode(':', explode(':', $selector, -1));
  159. $this->saveChanges($selector, $info['parent'], $immediate);
  160. }
  161. return $this;
  162. }
  163. /**
  164. * Remembers to save the wrapper on cleanup or does it now.
  165. */
  166. protected function save($selector, EntityMetadataWrapper $wrapper, $immediate) {
  167. // Convert variable names and selectors to both use underscores.
  168. $selector = strtr($selector, '-', '_');
  169. if (isset($this->save[$selector])) {
  170. if ($this->save[$selector][0]->getIdentifier() == $wrapper->getIdentifier()) {
  171. // The entity is already remembered. So do a combined save.
  172. $this->save[$selector][1] += self::$blocked;
  173. }
  174. else {
  175. // The wrapper is already in there, but wraps another entity. So first
  176. // save the old one, then care about the new one.
  177. $this->saveNow($selector);
  178. }
  179. }
  180. if (!isset($this->save[$selector])) {
  181. // In case of immediate saving don't clone the wrapper, so saving a new
  182. // entity immediately makes the identifier available afterwards.
  183. $this->save[$selector] = array($immediate ? $wrapper : clone $wrapper, self::$blocked);
  184. }
  185. if ($immediate) {
  186. $this->saveNow($selector);
  187. }
  188. }
  189. /**
  190. * Saves the wrapper for the given selector.
  191. */
  192. protected function saveNow($selector) {
  193. // Add the set of blocked elements for the recursion prevention.
  194. $previously_blocked = self::$blocked;
  195. self::$blocked += $this->save[$selector][1];
  196. // Actually save!
  197. $wrapper = $this->save[$selector][0];
  198. $entity = $wrapper->value();
  199. // When operating in hook_entity_insert() $entity->is_new might be still
  200. // set. In that case remove the flag to avoid causing another insert instead
  201. // of an update.
  202. if (!empty($entity->is_new) && $wrapper->getIdentifier()) {
  203. $entity->is_new = FALSE;
  204. }
  205. rules_log('Saved %selector of type %type.', array('%selector' => $selector, '%type' => $wrapper->type()));
  206. $wrapper->save();
  207. // Restore the state's set of blocked elements.
  208. self::$blocked = $previously_blocked;
  209. unset($this->save[$selector]);
  210. }
  211. /**
  212. * Merges the info about to be saved variables form the given state into the
  213. * existing state. Therefor we can aggregate saves from invoked components.
  214. * Merged in saves are removed from the given state, but not mergable saves
  215. * remain there.
  216. *
  217. * @param $state
  218. * The state for which to merge the to be saved variables in.
  219. * @param $component
  220. * The component which has been invoked, thus needs to be blocked for the
  221. * merged in saves.
  222. * @param $settings
  223. * The settings of the element that invoked the component. Contains
  224. * information about variable/selector mappings between the states.
  225. */
  226. public function mergeSaveVariables(RulesState $state, RulesPlugin $component, $settings) {
  227. // For any saves that we take over, also block the component.
  228. $this->block($component);
  229. foreach ($state->save->getArrayCopy() as $selector => $data) {
  230. $parts = explode(':', $selector, 2);
  231. // Adapt the selector to fit for the parent state and move the wrapper.
  232. if (isset($settings[$parts[0] . ':select'])) {
  233. $parts[0] = $settings[$parts[0] . ':select'];
  234. $this->save(implode(':', $parts), $data[0], FALSE);
  235. unset($state->save[$selector]);
  236. }
  237. }
  238. $this->unblock($component);
  239. }
  240. /**
  241. * Returns an entity metadata wrapper as specified in the selector.
  242. *
  243. * @param $selector
  244. * The selector string, e.g. "node:author:mail".
  245. * @param $langcode
  246. * (optional) The language code used to get the argument value if the
  247. * argument value should be translated. Defaults to LANGUAGE_NONE.
  248. *
  249. * @return EntityMetadataWrapper
  250. * The wrapper for the given selector.
  251. *
  252. * @throws RulesEvaluationException
  253. * Throws a RulesEvaluationException in case the selector cannot be applied.
  254. */
  255. public function applyDataSelector($selector, $langcode = LANGUAGE_NONE) {
  256. $parts = explode(':', str_replace('-', '_', $selector), 2);
  257. $wrapper = $this->get($parts[0]);
  258. if (count($parts) == 1) {
  259. return $wrapper;
  260. }
  261. elseif (!$wrapper instanceof EntityMetadataWrapper) {
  262. throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not wrapped correctly.', array('%selector' => $selector));
  263. }
  264. try {
  265. foreach (explode(':', $parts[1]) as $name) {
  266. if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) {
  267. // Make sure we are usign the right language. Wrappers might be cached
  268. // and have previous langcodes set, so always set the right language.
  269. if ($wrapper instanceof EntityStructureWrapper) {
  270. $wrapper->language($langcode);
  271. }
  272. $wrapper = $wrapper->get($name);
  273. }
  274. else {
  275. throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not a list or a structure: %wrapper.', array('%selector' => $selector, '%wrapper' => $wrapper));
  276. }
  277. }
  278. }
  279. catch (EntityMetadataWrapperException $e) {
  280. // In case of an exception, re-throw it.
  281. throw new RulesEvaluationException('Unable to apply data selector %selector: %error', array('%selector' => $selector, '%error' => $e->getMessage()));
  282. }
  283. return $wrapper;
  284. }
  285. /**
  286. * Magic method. Only serialize variables and their info.
  287. * Additionally we remember currently blocked configs, so we can restore them
  288. * upon deserialization using restoreBlocks().
  289. */
  290. public function __sleep () {
  291. $this->currentlyBlocked = self::$blocked;
  292. return array('info', 'variables', 'currentlyBlocked');
  293. }
  294. public function __wakeup() {
  295. $this->save = new ArrayObject();
  296. }
  297. /**
  298. * Restore the before serialization blocked configurations.
  299. *
  300. * Warning: This overwrites any possible currently blocked configs. Thus
  301. * do not invoke this method, if there might be evaluations active.
  302. */
  303. public function restoreBlocks() {
  304. self::$blocked = $this->currentlyBlocked;
  305. }
  306. /**
  307. * Defines always available variables.
  308. */
  309. public static function defaultVariables($key = NULL) {
  310. // Add a variable for accessing site-wide data properties.
  311. $vars['site'] = array(
  312. 'type' => 'site',
  313. 'label' => t('Site information'),
  314. 'description' => t("Site-wide settings and other global information."),
  315. // Add the property info via a callback making use of the cached info.
  316. 'property info alter' => array('RulesData', 'addSiteMetadata'),
  317. 'property info' => array(),
  318. 'optional' => TRUE,
  319. );
  320. return isset($key) ? $vars[$key] : $vars;
  321. }
  322. }
  323. /**
  324. * A class holding static methods related to data.
  325. */
  326. class RulesData {
  327. /**
  328. * Returns whether the type match. They match if type1 is compatible to type2.
  329. *
  330. * @param $var_info
  331. * The name of the type to check for whether it is compatible to type2.
  332. * @param $param_info
  333. * The type expression to check for.
  334. * @param $ancestors
  335. * Whether sub-type relationships for checking type compatibility should be
  336. * taken into account. Defaults to TRUE.
  337. *
  338. * @return
  339. * Whether the types match.
  340. */
  341. public static function typesMatch($var_info, $param_info, $ancestors = TRUE) {
  342. $var_type = $var_info['type'];
  343. $param_type = $param_info['type'];
  344. if ($param_type == '*' || $param_type == 'unknown') {
  345. return TRUE;
  346. }
  347. if ($var_type == $param_type) {
  348. // Make sure the bundle matches, if specified by the parameter.
  349. return !isset($param_info['bundles']) || isset($var_info['bundle']) && in_array($var_info['bundle'], $param_info['bundles']);
  350. }
  351. // Parameters may specify multiple types using an array.
  352. $valid_types = is_array($param_type) ? $param_type : array($param_type);
  353. if (in_array($var_type, $valid_types)) {
  354. return TRUE;
  355. }
  356. // Check for sub-type relationships.
  357. if ($ancestors && !isset($param_info['bundles'])) {
  358. $cache = &rules_get_cache();
  359. self::typeCalcAncestors($cache, $var_type);
  360. // If one of the types is an ancestor return TRUE.
  361. return (bool)array_intersect_key($cache['data_info'][$var_type]['ancestors'], array_flip($valid_types));
  362. }
  363. return FALSE;
  364. }
  365. protected static function typeCalcAncestors(&$cache, $type) {
  366. if (!isset($cache['data_info'][$type]['ancestors'])) {
  367. $cache['data_info'][$type]['ancestors'] = array();
  368. if (isset($cache['data_info'][$type]['parent']) && $parent = $cache['data_info'][$type]['parent']) {
  369. $cache['data_info'][$type]['ancestors'][$parent] = TRUE;
  370. self::typeCalcAncestors($cache, $parent);
  371. // Add all parent ancestors to our own ancestors.
  372. $cache['data_info'][$type]['ancestors'] += $cache['data_info'][$parent]['ancestors'];
  373. }
  374. // For special lists like list<node> add in "list" as valid parent.
  375. if (entity_property_list_extract_type($type)) {
  376. $cache['data_info'][$type]['ancestors']['list'] = TRUE;
  377. }
  378. }
  379. }
  380. /**
  381. * Returns matching data variables or properties for the given info and the to
  382. * be configured parameter.
  383. *
  384. * @param $source
  385. * Either an array of info about available variables or a entity metadata
  386. * wrapper.
  387. * @param $param_info
  388. * The information array about the to be configured parameter.
  389. * @param $prefix
  390. * An optional prefix for the data selectors.
  391. * @param $recursions
  392. * The number of recursions used to go down the tree. Defaults to 2.
  393. * @param $suggestions
  394. * Whether possibilities to recurse are suggested as soon as the deepest
  395. * level of recursions is reached. Defaults to TRUE.
  396. *
  397. * @return
  398. * An array of info about matching variables or properties that match, keyed
  399. * with the data selector.
  400. */
  401. public static function matchingDataSelector($source, $param_info, $prefix = '', $recursions = 2, $suggestions = TRUE) {
  402. // If an array of info is given, get entity metadata wrappers first.
  403. $data = NULL;
  404. if (is_array($source)) {
  405. foreach ($source as $name => $info) {
  406. $source[$name] = rules_wrap_data($data, $info, TRUE);
  407. }
  408. }
  409. $matches = array();
  410. foreach ($source as $name => $wrapper) {
  411. $info = $wrapper->info();
  412. $name = str_replace('_', '-', $name);
  413. if (self::typesMatch($info, $param_info)) {
  414. $matches[$prefix . $name] = $info;
  415. if (!is_array($source) && $source instanceof EntityListWrapper) {
  416. // Add some more possible list items.
  417. for ($i = 1; $i < 4; $i++) {
  418. $matches[$prefix . $i] = $info;
  419. }
  420. }
  421. }
  422. // Recurse later on to get an improved ordering of the results.
  423. if ($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper) {
  424. $recurse[$prefix . $name] = $wrapper;
  425. if ($recursions > 0) {
  426. $matches += self::matchingDataSelector($wrapper, $param_info, $prefix . $name . ':', $recursions - 1, $suggestions);
  427. }
  428. elseif ($suggestions) {
  429. // We may not recurse any more, but indicate the possibility to recurse.
  430. $matches[$prefix . $name . ':'] = $wrapper->info();
  431. if (!is_array($source) && $source instanceof EntityListWrapper) {
  432. // Add some more possible list items.
  433. for ($i = 1; $i < 4; $i++) {
  434. $matches[$prefix . $i . ':'] = $wrapper->info();
  435. }
  436. }
  437. }
  438. }
  439. }
  440. return $matches;
  441. }
  442. /**
  443. * Adds asserted metadata to the variable info. In case there are already
  444. * assertions for a variable, the assertions are merged such that both apply.
  445. *
  446. * @see RulesData::applyMetadataAssertions()
  447. */
  448. public static function addMetadataAssertions($var_info, $assertions) {
  449. foreach ($assertions as $selector => $assertion) {
  450. // Convert the selector back to underscores, such it matches the varname.
  451. $selector = str_replace('-', '_', $selector);
  452. $parts = explode(':', $selector);
  453. if (isset($var_info[$parts[0]])) {
  454. // Apply the selector to determine the right target array. We build an
  455. // array like
  456. // $var_info['rules assertion']['property1']['property2']['#info'] = ..
  457. $target = &$var_info[$parts[0]]['rules assertion'];
  458. foreach (array_slice($parts, 1) as $part) {
  459. $target = &$target[$part];
  460. }
  461. // In case the assertion is directly for a variable, we have to modify
  462. // the variable info directly. In case the asserted property is nested
  463. // the info-has to be altered by RulesData::applyMetadataAssertions()
  464. // before the child-wrapper is created.
  465. if (count($parts) == 1) {
  466. // Support asserting a type in case of generic entity references only.
  467. if (isset($assertion['type']) && $var_info[$parts[0]]['type'] == 'entity') {
  468. if (entity_get_info($assertion['type'])) {
  469. $var_info[$parts[0]]['type'] = $assertion['type'];
  470. }
  471. unset($assertion['type']);
  472. }
  473. // Add any single bundle directly to the variable info, so the
  474. // variable fits as argument for parameters requiring the bundle.
  475. if (isset($assertion['bundle']) && count($bundles = (array) $assertion['bundle']) == 1) {
  476. $var_info[$parts[0]]['bundle'] = reset($bundles);
  477. }
  478. }
  479. // Add the assertions, but merge them with any previously added
  480. // assertions if necessary.
  481. $target['#info'] = isset($target['#info']) ? rules_update_array($target['#info'], $assertion) : $assertion;
  482. // Add in a callback that the entity metadata wrapper pick up for
  483. // altering the property info, such that we can add in the assertions.
  484. $var_info[$parts[0]] += array('property info alter' => array('RulesData', 'applyMetadataAssertions'));
  485. // In case there is a VARNAME_unchanged variable as it is used in update
  486. // hooks, assume the assertions are valid for the unchanged variable
  487. // too.
  488. if (isset($var_info[$parts[0] . '_unchanged'])) {
  489. $name = $parts[0] . '_unchanged';
  490. $var_info[$name]['rules assertion'] = $var_info[$parts[0]]['rules assertion'];
  491. $var_info[$name]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
  492. if (isset($var_info[$parts[0]]['bundle']) && !isset($var_info[$name]['bundle'])) {
  493. $var_info[$name]['bundle'] = $var_info[$parts[0]]['bundle'];
  494. }
  495. }
  496. }
  497. }
  498. return $var_info;
  499. }
  500. /**
  501. * Property info alter callback for the entity metadata wrapper for applying
  502. * the rules metadata assertions.
  503. *
  504. * @see RulesData::addMetadataAssertions()
  505. */
  506. public static function applyMetadataAssertions(EntityMetadataWrapper $wrapper, $property_info) {
  507. $info = $wrapper->info();
  508. if (!empty($info['rules assertion'])) {
  509. $assertion = $info['rules assertion'];
  510. // In case there are list-wrappers pass through the assertions of the item
  511. // but make sure we only apply the assertions for the list items for
  512. // which the conditions are executed.
  513. if (isset($info['parent']) && $info['parent'] instanceof EntityListWrapper) {
  514. $assertion = isset($assertion[$info['name']]) ? $assertion[$info['name']] : array();
  515. }
  516. // Support specifying multiple bundles, whereas the added properties are
  517. // the intersection of the bundle properties.
  518. if (isset($assertion['#info']['bundle'])) {
  519. $bundles = (array) $assertion['#info']['bundle'];
  520. foreach ($bundles as $bundle) {
  521. $properties[] = isset($property_info['bundles'][$bundle]['properties']) ? $property_info['bundles'][$bundle]['properties'] : array();
  522. }
  523. // Add the intersection.
  524. $property_info['properties'] += count($properties) > 1 ? call_user_func_array('array_intersect_key', $properties) : reset($properties);
  525. }
  526. // Support adding directly asserted property info.
  527. if (isset($assertion['#info']['property info'])) {
  528. $property_info['properties'] += $assertion['#info']['property info'];
  529. }
  530. // Pass through any rules assertion of properties to their info, so any
  531. // derived wrappers apply it.
  532. foreach (element_children($assertion) as $key) {
  533. $property_info['properties'][$key]['rules assertion'] = $assertion[$key];
  534. $property_info['properties'][$key]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
  535. // Apply any 'type' and 'bundle' assertion directly to the propertyinfo.
  536. if (isset($assertion[$key]['#info']['type'])) {
  537. $type = $assertion[$key]['#info']['type'];
  538. // Support asserting a type in case of generic entity references only.
  539. if ($property_info['properties'][$key]['type'] == 'entity' && entity_get_info($type)) {
  540. $property_info['properties'][$key]['type'] = $type;
  541. }
  542. }
  543. if (isset($assertion[$key]['#info']['bundle'])) {
  544. $bundle = (array) $assertion[$key]['#info']['bundle'];
  545. // Add any single bundle directly to the variable info, so the
  546. // property fits as argument for parameters requiring the bundle.
  547. if (count($bundle) == 1) {
  548. $property_info['properties'][$key]['bundle'] = reset($bundle);
  549. }
  550. }
  551. }
  552. }
  553. return $property_info;
  554. }
  555. /**
  556. * Property info alter callback for the entity metadata wrapper to inject
  557. * metadata for the 'site' variable. In contrast to doing this via
  558. * hook_rules_data_info() this callback makes use of the already existing
  559. * property info cache for site information of entity metadata.
  560. *
  561. * @see RulesPlugin::availableVariables()
  562. */
  563. public static function addSiteMetadata(EntityMetadataWrapper $wrapper, $property_info) {
  564. $site_info = entity_get_property_info('site');
  565. $property_info['properties'] += $site_info['properties'];
  566. // Also invoke the usual callback for altering metadata, in case actions
  567. // have specified further metadata.
  568. return RulesData::applyMetadataAssertions($wrapper, $property_info);
  569. }
  570. }
  571. /**
  572. * A wrapper class similar to the EntityDrupalWrapper, but for non-entities.
  573. *
  574. * This class is intended to serve as base for a custom wrapper classes of
  575. * identifiable data types, which are non-entities. By extending this class only
  576. * the extractIdentifier() and load() methods have to be defined.
  577. * In order to make the data type savable implement the
  578. * RulesDataWrapperSavableInterface.
  579. *
  580. * That way it is possible for non-entity data types to be work with Rules, i.e.
  581. * one can implement a 'ui class' with a direct input form returning the
  582. * identifier of the data. However, instead of that it is suggested to implement
  583. * an entity type, such that the same is achieved via general API functions like
  584. * entity_load().
  585. */
  586. abstract class RulesIdentifiableDataWrapper extends EntityStructureWrapper {
  587. /**
  588. * Contains the id.
  589. */
  590. protected $id = FALSE;
  591. /**
  592. * Construct a new wrapper object.
  593. *
  594. * @param $type
  595. * The type of the passed data.
  596. * @param $data
  597. * Optional. The data to wrap or its identifier.
  598. * @param $info
  599. * Optional. Used internally to pass info about properties down the tree.
  600. */
  601. public function __construct($type, $data = NULL, $info = array()) {
  602. parent::__construct($type, $data, $info);
  603. $this->setData($data);
  604. }
  605. /**
  606. * Sets the data internally accepting both the data id and object.
  607. */
  608. protected function setData($data) {
  609. if (isset($data) && $data !== FALSE && !is_object($data)) {
  610. $this->id = $data;
  611. $this->data = FALSE;
  612. }
  613. elseif (is_object($data)) {
  614. // We got the data object passed.
  615. $this->data = $data;
  616. $id = $this->extractIdentifier($data);
  617. $this->id = isset($id) ? $id : FALSE;
  618. }
  619. }
  620. /**
  621. * Returns the identifier of the wrapped data.
  622. */
  623. public function getIdentifier() {
  624. return $this->dataAvailable() && $this->value() ? $this->id : NULL;
  625. }
  626. /**
  627. * Overridden.
  628. */
  629. public function value(array $options = array()) {
  630. $this->setData(parent::value());
  631. if (!$this->data && !empty($this->id)) {
  632. // Lazy load the data if necessary.
  633. $this->data = $this->load($this->id);
  634. if (!$this->data) {
  635. throw new EntityMetadataWrapperException('Unable to load the ' . check_plain($this->type) . ' with the id ' . check_plain($this->id) . '.');
  636. }
  637. }
  638. return $this->data;
  639. }
  640. /**
  641. * Overridden to support setting the data by either the object or the id.
  642. */
  643. public function set($value) {
  644. if (!$this->validate($value)) {
  645. throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
  646. }
  647. // As custom wrapper classes can only appear for Rules variables, but not
  648. // as properties we don't have to care about updating the parent.
  649. $this->clear();
  650. $this->setData($value);
  651. return $this;
  652. }
  653. /**
  654. * Overridden.
  655. */
  656. public function clear() {
  657. $this->id = NULL;
  658. parent::clear();
  659. }
  660. /**
  661. * Prepare for serializiation.
  662. */
  663. public function __sleep() {
  664. $vars = parent::__sleep();
  665. // Don't serialize the loaded data, except for the case the data is not
  666. // saved yet.
  667. if (!empty($this->id)) {
  668. unset($vars['data']);
  669. }
  670. return $vars;
  671. }
  672. public function __wakeup() {
  673. if ($this->id !== FALSE) {
  674. // Make sure data is set, so the data will be loaded when needed.
  675. $this->data = FALSE;
  676. }
  677. }
  678. /**
  679. * Extract the identifier of the given data object.
  680. *
  681. * @return
  682. * The extracted identifier.
  683. */
  684. abstract protected function extractIdentifier($data);
  685. /**
  686. * Load a data object given an identifier.
  687. *
  688. * @return
  689. * The loaded data object, or FALSE if loading failed.
  690. */
  691. abstract protected function load($id);
  692. }
  693. /**
  694. * Interface that allows custom wrapper classes to declare that they are savable.
  695. */
  696. interface RulesDataWrapperSavableInterface {
  697. /**
  698. * Save the currently wrapped data.
  699. */
  700. public function save();
  701. }