entity.rules.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. <?php
  2. /**
  3. * @file
  4. * General entity related rules integration.
  5. *
  6. * @addtogroup rules
  7. *
  8. * @{
  9. */
  10. /**
  11. * Implements hook_rules_file_info() on behalf of the entity module.
  12. *
  13. * @see rules_core_modules()
  14. */
  15. function rules_entity_file_info() {
  16. return array('modules/entity.eval');
  17. }
  18. /**
  19. * Implements hook_rules_category_info() on behalf of the pseudo entity module.
  20. */
  21. function rules_entity_category_info() {
  22. return array(
  23. 'rules_entity' => array(
  24. 'label' => t('Entities'),
  25. 'equals group' => t('Entities'),
  26. 'weight' => -50,
  27. ),
  28. );
  29. }
  30. /**
  31. * Implements hook_rules_action_info() on behalf of the entity module.
  32. *
  33. * @see rules_core_modules()
  34. */
  35. function rules_entity_action_info() {
  36. if (rules_entity_action_type_options('entity_fetch')) {
  37. $return['entity_fetch'] = array(
  38. 'label' => t('Fetch entity by id'),
  39. 'parameter' => array(
  40. 'type' => array(
  41. 'type' => 'text',
  42. 'label' => t('Entity type'),
  43. 'options list' => 'rules_entity_action_type_options',
  44. 'description' => t('Specifies the type of entity that should be fetched.'),
  45. 'restriction' => 'input',
  46. ),
  47. 'id' => array('type' => 'unknown', 'label' => t('Identifier')),
  48. ),
  49. 'provides' => array(
  50. 'entity_fetched' => array('type' => 'unknown', 'label' => t('Fetched entity')),
  51. ),
  52. 'group' => t('Entities'),
  53. 'access callback' => 'rules_entity_action_access',
  54. 'base' => 'rules_action_entity_fetch',
  55. 'callbacks' => array(
  56. 'access' => 'rules_action_entity_createfetch_access',
  57. 'form_alter' => 'rules_action_type_form_alter',
  58. ),
  59. );
  60. $return['entity_query'] = array(
  61. 'label' => t('Fetch entity by property'),
  62. 'parameter' => array(
  63. 'type' => array(
  64. 'type' => 'text',
  65. 'label' => t('Entity type'),
  66. 'options list' => 'rules_entity_action_type_options',
  67. 'description' => t('Specifies the type of the entity that should be fetched.'),
  68. 'restriction' => 'input',
  69. ),
  70. 'property' => array(
  71. 'type' => 'text',
  72. 'label' => t('Property'),
  73. 'description' => t('The property by which the entity is to be selected.'),
  74. 'restriction' => 'input',
  75. ),
  76. 'value' => array(
  77. 'type' => 'unknown',
  78. 'label' => t('Value'),
  79. 'description' => t('The property value of the entity to be fetched.'),
  80. ),
  81. 'limit' => array(
  82. 'type' => 'integer',
  83. 'label' => t('Limit result count'),
  84. 'description' => t('Limit the maximum number of fetched entities.'),
  85. 'optional' => TRUE,
  86. 'default value' => '10',
  87. ),
  88. ),
  89. 'provides' => array(
  90. 'entity_fetched' => array('type' => 'list', 'label' => t('Fetched entity')),
  91. ),
  92. 'group' => t('Entities'),
  93. 'access callback' => 'rules_entity_action_access',
  94. 'base' => 'rules_action_entity_query',
  95. 'callbacks' => array(
  96. 'form_alter' => 'rules_action_type_form_alter',
  97. ),
  98. );
  99. }
  100. if (rules_entity_action_type_options('entity_create')) {
  101. $return['entity_create'] = array(
  102. 'label' => t('Create a new entity'),
  103. 'named parameter' => TRUE,
  104. 'parameter' => array(
  105. 'type' => array(
  106. 'type' => 'text',
  107. 'label' => t('Entity type'),
  108. 'options list' => 'rules_entity_action_type_options',
  109. 'description' => t('Specifies the type of the entity that should be created.'),
  110. 'restriction' => 'input',
  111. ),
  112. // Further needed parameter depends on the type.
  113. ),
  114. 'provides' => array(
  115. 'entity_created' => array(
  116. 'type' => 'unknown',
  117. 'label' => t('Created entity'),
  118. 'save' => TRUE,
  119. ),
  120. ),
  121. 'group' => t('Entities'),
  122. 'access callback' => 'rules_entity_action_access',
  123. 'base' => 'rules_action_entity_create',
  124. 'callbacks' => array(
  125. 'access' => 'rules_action_entity_createfetch_access',
  126. 'form_alter' => 'rules_action_type_form_alter',
  127. 'validate' => 'rules_action_create_type_validate',
  128. ),
  129. );
  130. }
  131. $return['entity_save'] = array(
  132. 'label' => t('Save entity'),
  133. 'parameter' => array(
  134. 'data' => array(
  135. 'type' => 'entity',
  136. 'label' => t('Entity'),
  137. 'description' => t('Specifies the entity, which should be saved permanently.'),
  138. 'restriction' => 'selector',
  139. 'wrapped' => TRUE,
  140. ),
  141. 'immediate' => array(
  142. 'type' => 'boolean',
  143. 'label' => t('Force saving immediately'),
  144. 'description' => t('Usually saving is postponed till the end of the evaluation, so that multiple saves can be fold into one. If this set, saving is forced to happen immediately.'),
  145. 'default value' => FALSE,
  146. 'optional' => TRUE,
  147. 'restriction' => 'input',
  148. ),
  149. ),
  150. 'group' => t('Entities'),
  151. 'access callback' => 'rules_entity_action_access',
  152. 'base' => 'rules_action_entity_save',
  153. 'callbacks' => array(
  154. 'access' => 'rules_action_entity_savedelete_access',
  155. ),
  156. );
  157. $return['entity_delete'] = array(
  158. 'label' => t('Delete entity'),
  159. 'parameter' => array(
  160. 'data' => array(
  161. 'type' => 'entity',
  162. 'label' => t('Entity'),
  163. 'description' => t('Specifies the entity, which should be deleted permanently.'),
  164. 'restriction' => 'selector',
  165. 'wrapped' => TRUE,
  166. ),
  167. ),
  168. 'group' => t('Entities'),
  169. 'access callback' => 'rules_entity_action_access',
  170. 'base' => 'rules_action_entity_delete',
  171. 'callbacks' => array(
  172. 'access' => 'rules_action_entity_savedelete_access',
  173. ),
  174. );
  175. return $return;
  176. }
  177. /**
  178. * Custom access callback for data create and fetch action.
  179. */
  180. function rules_action_entity_createfetch_access(RulesAbstractPlugin $element) {
  181. $op = $element->getElementName() == 'entity_create' ? 'create' : 'view';
  182. return entity_access($op, $element->settings['type']);
  183. }
  184. /**
  185. * Custom access callback for the data query action.
  186. */
  187. function rules_action_entity_query_access(RulesAbstractPlugin $element) {
  188. if (!rules_action_entity_createfetch_access($element)) {
  189. return FALSE;
  190. }
  191. $properties = entity_get_all_property_info($element->settings['type']);
  192. if (isset($element->settings['property']) && isset($properties[$element->settings['property']]['access callback'])) {
  193. return call_user_func($properties[$element->settings['property']]['access callback'], 'view', $element->settings['property'], $element->settings['type'], NULL, NULL);
  194. }
  195. return TRUE;
  196. }
  197. /**
  198. * Options list callback for a parameter of entity_create.
  199. */
  200. function rules_action_entity_parameter_options_list(RulesPlugin $element, $param_name) {
  201. // Remove the parameter name prefix 'param_'.
  202. $property_name = substr($param_name, 6);
  203. $wrapper = entity_metadata_wrapper($element->settings['type']);
  204. // The possible values of the "value" parameter are those of the data param.
  205. return $wrapper->$property_name->optionsList();
  206. }
  207. /**
  208. * Custom access callback for data save and delete action.
  209. */
  210. function rules_action_entity_savedelete_access(RulesAbstractPlugin $element) {
  211. if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
  212. $op = $element->getElementName() == 'entity_save' ? 'save' : 'delete';
  213. return $wrapper instanceof EntityDrupalWrapper && $wrapper->entityAccess($op);
  214. }
  215. return FALSE;
  216. }
  217. /**
  218. * Returns the options list for choosing a property of an entity type.
  219. */
  220. function rules_action_entity_query_property_options_list(RulesAbstractPlugin $element) {
  221. $element->settings += array('type' => NULL);
  222. if ($element->settings['type']) {
  223. $properties = entity_get_all_property_info($element->settings['type']);
  224. return rules_extract_property($properties, 'label');
  225. }
  226. }
  227. /**
  228. * Returns the options list specified for the chosen property.
  229. */
  230. function rules_action_entity_query_value_options_list(RulesAbstractPlugin $element) {
  231. // Get the possible values for the selected property.
  232. $element->settings += array('type' => NULL, 'property' => NULL);
  233. if ($element->settings['type'] && $element->settings['property']) {
  234. $wrapper = rules_get_entity_metadata_wrapper_all_properties($element);
  235. if (isset($wrapper->{$element->settings['property']}) && $property = $wrapper->{$element->settings['property']}) {
  236. return $property->optionsList('view');
  237. }
  238. }
  239. }
  240. /**
  241. * Options list callback for data actions.
  242. *
  243. * @param $element
  244. * The element to return options for.
  245. * @param $param
  246. * The name of the parameter to return options for.
  247. */
  248. function rules_entity_action_type_options($element, $name = NULL) {
  249. // We allow calling this function with just the element name too. That way
  250. // we ease manual re-use.
  251. $name = is_object($element) ? $element->getElementName() : $element;
  252. return ($name == 'entity_create') ? rules_entity_type_options('create') : rules_entity_type_options();
  253. }
  254. /**
  255. * Returns options containing entity types having the given key set in the info.
  256. *
  257. * Additionally, we exclude all entity types that are marked as configuration.
  258. */
  259. function rules_entity_type_options($key = NULL) {
  260. $info = entity_get_info();
  261. $types = array();
  262. foreach ($info as $type => $entity_info) {
  263. if (empty($entity_info['configuration']) && empty($entity_info['exportable'])) {
  264. if (!isset($key) || entity_type_supports($type, $key)) {
  265. $types[$type] = $entity_info['label'];
  266. }
  267. }
  268. }
  269. return $types;
  270. }
  271. /**
  272. * Options list callback for getting a list of possible entity bundles.
  273. *
  274. * @param $element
  275. * The element to return options for.
  276. */
  277. function rules_entity_bundle_options(RulesAbstractPlugin $element) {
  278. $bundles = array();
  279. if (isset($element->settings['type'])) {
  280. $entity_info = entity_get_info($element->settings['type']);
  281. foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  282. $bundles[$bundle_name] = $bundle_info['label'];
  283. }
  284. }
  285. return $bundles;
  286. }
  287. /**
  288. * Entity actions access callback.
  289. *
  290. * Returns TRUE if at least one type is available for configuring the action.
  291. */
  292. function rules_entity_action_access($type, $name) {
  293. if ($name == 'entity_fetch' || $name == 'entity_create' || $name == 'entity_query') {
  294. $types = array_keys(rules_entity_action_type_options($name));
  295. $op = $name == 'entity_create' ? 'create' : 'view';
  296. }
  297. elseif ($name == 'entity_save' || $name == 'entity_delete') {
  298. $types = array_keys(entity_get_info());
  299. $op = $name == 'entity_save' ? 'save' : 'delete';
  300. }
  301. foreach ($types as $key => $type) {
  302. if (!entity_access($op, $type)) {
  303. unset($types[$key]);
  304. }
  305. }
  306. return !empty($types);
  307. }
  308. /**
  309. * Implements hook_rules_condition_info() on behalf of the entity module.
  310. *
  311. * @see rules_core_modules()
  312. */
  313. function rules_entity_condition_info() {
  314. return array(
  315. 'entity_is_new' => array(
  316. 'label' => t('Entity is new'),
  317. 'parameter' => array(
  318. 'entity' => array(
  319. 'type' => 'entity',
  320. 'label' => t('Entity'),
  321. 'description' => t('Specifies the entity for which to evaluate the condition.'),
  322. 'restriction' => 'selector',
  323. ),
  324. ),
  325. 'group' => t('Entities'),
  326. 'base' => 'rules_condition_entity_is_new',
  327. ),
  328. 'entity_has_field' => array(
  329. 'label' => t('Entity has field'),
  330. 'parameter' => array(
  331. 'entity' => array(
  332. 'type' => 'entity',
  333. 'label' => t('Entity'),
  334. 'description' => t('Specifies the entity for which to evaluate the condition.'),
  335. 'restriction' => 'selector',
  336. ),
  337. 'field' => array(
  338. 'type' => 'text',
  339. 'label' => t('Field'),
  340. 'description' => t('The name of the field to check for.'),
  341. 'options list' => 'rules_condition_entity_has_field_options',
  342. 'restriction' => 'input',
  343. ),
  344. ),
  345. 'group' => t('Entities'),
  346. 'base' => 'rules_condition_entity_has_field',
  347. ),
  348. 'entity_is_of_type' => array(
  349. 'label' => t('Entity is of type'),
  350. 'parameter' => array(
  351. 'entity' => array(
  352. 'type' => 'entity',
  353. 'label' => t('Entity'),
  354. 'description' => t('Specifies the entity for which to evaluate the condition.'),
  355. ),
  356. 'type' => array(
  357. 'type' => 'token',
  358. 'label' => t('Entity type'),
  359. 'description' => t('The entity type to check for.'),
  360. 'options list' => 'rules_entity_action_type_options',
  361. 'restriction' => 'input',
  362. ),
  363. ),
  364. 'group' => t('Entities'),
  365. 'base' => 'rules_condition_entity_is_of_type',
  366. ),
  367. 'entity_is_of_bundle' => array(
  368. 'label' => t('Entity is of bundle'),
  369. 'parameter' => array(
  370. 'entity' => array(
  371. 'type' => 'entity',
  372. 'label' => t('Entity'),
  373. 'description' => t('Specifies the entity for which to evaluate the condition.'),
  374. ),
  375. 'type' => array(
  376. 'type' => 'token',
  377. 'label' => t('Entity type'),
  378. 'description' => t('The type of the checked entity.'),
  379. 'options list' => 'rules_entity_action_type_options',
  380. 'restriction' => 'input',
  381. ),
  382. 'bundle' => array(
  383. 'type' => 'list<text>',
  384. 'label' => t('Entity bundle'),
  385. 'description' => t('The condition is met if the entity is of one of the selected bundles.'),
  386. 'options list' => 'rules_entity_bundle_options',
  387. 'restriction' => 'input',
  388. ),
  389. ),
  390. 'group' => t('Entities'),
  391. 'base' => 'rules_condition_entity_is_of_bundle',
  392. ),
  393. 'entity_field_access' => array(
  394. 'label' => t('User has field access'),
  395. 'parameter' => array(
  396. 'entity' => array(
  397. 'type' => 'entity',
  398. 'label' => t('Entity'),
  399. 'description' => t('Specifies the entity for which to evaluate the condition.'),
  400. 'restriction' => 'selector',
  401. 'wrapped' => TRUE,
  402. ),
  403. 'field' => array(
  404. 'type' => 'token',
  405. 'label' => t('Field name'),
  406. 'description' => t('The name of the field to check for.'),
  407. 'options list' => 'rules_condition_entity_has_field_options',
  408. 'restriction' => 'input',
  409. ),
  410. 'op' => array(
  411. 'type' => 'text',
  412. 'label' => t('Access operation'),
  413. 'options list' => 'rules_condition_entity_field_access_op_options',
  414. 'restriction' => 'input',
  415. 'optional' => TRUE,
  416. 'default value' => 'view',
  417. ),
  418. 'account' => array(
  419. 'type' => 'user',
  420. 'label' => t('User account'),
  421. 'description' => t('Specifies the user account for which to check access. If left empty, the currently logged in user will be used.'),
  422. 'restriction' => 'selector',
  423. 'optional' => TRUE,
  424. 'default value' => NULL,
  425. ),
  426. ),
  427. 'group' => t('Entities'),
  428. 'base' => 'rules_condition_entity_field_access',
  429. ),
  430. );
  431. }
  432. /**
  433. * Help callback for condition entity_is_new.
  434. */
  435. function rules_condition_entity_is_new_help() {
  436. return t('This condition determines whether the specified entity has just been created and has not yet been saved to the database.');
  437. }
  438. /**
  439. * Returns options for choosing a field for the selected entity.
  440. */
  441. function rules_condition_entity_has_field_options(RulesAbstractPlugin $element) {
  442. // The field_info_field_map() function was introduced in Drupal 7.22. See
  443. // https://www.drupal.org/node/1915646.
  444. if (function_exists('field_info_field_map')) {
  445. $fields = field_info_field_map();
  446. }
  447. else {
  448. $fields = field_info_fields();
  449. }
  450. $field_list = drupal_map_assoc(array_keys($fields));
  451. ksort($field_list);
  452. return $field_list;
  453. }
  454. /**
  455. * Returns options for choosing a field_access() operation.
  456. */
  457. function rules_condition_entity_field_access_op_options(RulesAbstractPlugin $element) {
  458. return array(
  459. 'view' => t('View'),
  460. 'edit' => t('Edit'),
  461. );
  462. }
  463. /**
  464. * Assert that the entity has the field, if there is metadata for the field.
  465. */
  466. function rules_condition_entity_has_field_assertions($element) {
  467. // Assert the field is there if the condition matches.
  468. if ($wrapper = $element->applyDataSelector($element->settings['entity:select'])) {
  469. $type = $wrapper->type();
  470. $field_property = $element->settings['field'];
  471. // Get all possible properties and check whether we have one for the field.
  472. $properties = entity_get_all_property_info($type == 'entity' ? NULL : $type);
  473. if (isset($properties[$field_property])) {
  474. $assertion = array('property info' => array($field_property => $properties[$field_property]));
  475. return array($element->settings['entity:select'] => $assertion);
  476. }
  477. }
  478. }
  479. /**
  480. * Assert the selected entity type.
  481. */
  482. function rules_condition_entity_is_of_type_assertions($element) {
  483. if ($type = $element->settings['type']) {
  484. return array('entity' => array('type' => $type));
  485. }
  486. }
  487. /**
  488. * Assert the selected entity type and bundle.
  489. */
  490. function rules_condition_entity_is_of_bundle_assertions($element) {
  491. if ($bundle = $element->settings['bundle']) {
  492. $assertions = array();
  493. $assertions['entity']['type'] = $element->settings['type'];
  494. $assertions['entity']['bundle'] = $bundle;
  495. return $assertions;
  496. }
  497. }
  498. /**
  499. * Process callback for the condition entity_is_of_bundle.
  500. */
  501. function rules_condition_entity_is_of_bundle_process(RulesAbstractPlugin $element) {
  502. // If we know the entity type, auto-populate it.
  503. if (($info = $element->getArgumentInfo('entity')) && $info['type'] != 'entity') {
  504. $element->settings['type'] = $info['type'];
  505. }
  506. }
  507. /**
  508. * Form alter callback for the condition entity_is_of_bundle.
  509. *
  510. * Use multiple steps to configure the condition as the needed bundle field list
  511. * depends on the selected entity type.
  512. */
  513. function rules_condition_entity_is_of_bundle_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
  514. if (empty($element->settings['entity:select'])) {
  515. $step = 1;
  516. }
  517. elseif (empty($element->settings['type'])) {
  518. $step = 2;
  519. }
  520. else {
  521. $step = 3;
  522. }
  523. $form['reload'] = array(
  524. '#weight' => $form['submit']['#weight'] + 1,
  525. '#type' => 'submit',
  526. '#name' => 'reload',
  527. '#value' => $step != 3 ? t('Continue') : t('Reload form'),
  528. '#limit_validation_errors' => array(array('parameter', 'entity'), array('parameter', 'type')),
  529. '#submit' => array('rules_form_submit_rebuild'),
  530. '#ajax' => rules_ui_form_default_ajax('fade'),
  531. '#attributes' => array('class' => array('rules-hide-js')),
  532. );
  533. // Use ajax and trigger as the reload button.
  534. $form['parameter']['type']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array(
  535. 'event' => 'change',
  536. 'trigger_as' => array('name' => 'reload'),
  537. );
  538. switch ($step) {
  539. case 1:
  540. $form['reload']['#limit_validation_errors'] = array(array('parameter', 'entity'));
  541. unset($form['parameter']['type']);
  542. unset($form['reload']['#attributes']['class']);
  543. // NO break.
  544. case 2:
  545. $form['negate']['#access'] = FALSE;
  546. unset($form['parameter']['bundle']);
  547. unset($form['submit']);
  548. break;
  549. case 3:
  550. if (($info = $element->getArgumentInfo('entity')) && $info['type'] != 'entity') {
  551. // Hide the entity type parameter if not needed.
  552. unset($form['parameter']['type']);
  553. }
  554. break;
  555. }
  556. }
  557. /**
  558. * @}
  559. */