rules.test 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986
  1. <?php
  2. /**
  3. * @file
  4. * Rules tests.
  5. */
  6. class RulesTestCase extends DrupalWebTestCase {
  7. static function getInfo() {
  8. return array(
  9. 'name' => 'Rules Engine tests',
  10. 'description' => 'Test using the rules API to create and evaluate rules.',
  11. 'group' => 'Rules',
  12. );
  13. }
  14. function setUp() {
  15. parent::setUp('rules', 'rules_test');
  16. RulesLog::logger()->clear();
  17. variable_set('rules_debug_log', 1);
  18. }
  19. /**
  20. * Calculates the output of t() given an array of placeholders to replace.
  21. */
  22. static function t($text, $strings) {
  23. $placeholders = array();
  24. foreach ($strings as $key => $string) {
  25. $key = !is_numeric($key) ? $key : $string;
  26. $placeholders['%' . $key] = drupal_placeholder($string);
  27. }
  28. return strtr($text, $placeholders);
  29. }
  30. protected function createTestRule() {
  31. $rule = rule();
  32. $rule->condition('rules_test_condition_true')
  33. ->condition('rules_test_condition_true')
  34. ->condition(rules_or()
  35. ->condition(rules_condition('rules_test_condition_true')->negate())
  36. ->condition('rules_test_condition_false')
  37. ->condition(rules_and()
  38. ->condition('rules_test_condition_false')
  39. ->condition('rules_test_condition_true')
  40. ->negate()
  41. )
  42. );
  43. $rule->action('rules_test_action');
  44. return $rule;
  45. }
  46. /**
  47. * Tests creating a rule and iterating over the rule elements.
  48. */
  49. function testRuleCreation() {
  50. $rule = $this->createTestRule();
  51. $rule->integrityCheck();
  52. $rule->execute();
  53. $log = RulesLog::logger()->get();
  54. $last = array_pop($log);
  55. $last = array_pop($log);
  56. $last = array_pop($log);
  57. $this->assertEqual($last[0], 'action called', 'Action called');
  58. RulesLog::logger()->checkLog();
  59. // Make sure condition and action iterators are working.
  60. $it = new RecursiveIteratorIterator($rule->conditions(), RecursiveIteratorIterator::SELF_FIRST);
  61. $this->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers');
  62. $it = new RecursiveIteratorIterator($rule->conditions());
  63. $this->assertEqual(iterator_count($it), 6, 'Iterated over all conditions');
  64. $this->assertEqual(iterator_count($rule->actions()), 1, 'Iterated over all actions');
  65. $this->assertEqual(iterator_count($rule->elements()), 10, 'Iterated over all rule elements.');
  66. // Test getting dependencies and the integrity check.
  67. $rule->integrityCheck();
  68. $this->assertTrue($rule->dependencies() === array('rules_test'), 'Dependencies correctly returned.');
  69. }
  70. /**
  71. * Test handling dependencies.
  72. */
  73. function testdependencies() {
  74. $action = rules_action('rules_node_publish_action');
  75. $this->assertEqual($action->dependencies(), array('rules_test'), 'Providing module is returned as dependency.');
  76. // Test handling unmet dependencies.
  77. $rule = rules_config_load('rules_export_test');
  78. $this->assertTrue(in_array('comment', $rule->dependencies) && !$rule->dirty, 'Dependencies have been imported.');
  79. // Remove the required comment module and make sure the rule is dirty then.
  80. module_disable(array('comment'));
  81. rules_clear_cache();
  82. $rule = rules_config_load('rules_export_test');
  83. $this->assertTrue($rule->dirty, 'Rule has been marked as dirty');
  84. // Now try re-enabling.
  85. module_enable(array('comment'));
  86. rules_clear_cache();
  87. $rule = rules_config_load('rules_export_test');
  88. $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');
  89. // Test it with components.
  90. module_enable(array('path'));
  91. $action_set = rules_action_set(array('node' => array('type' => 'node')));
  92. $action_set->action('node_path_alias');
  93. $action_set->save('rules_test_alias');
  94. $rule = rule(array('node' => array('type' => 'node')));
  95. $rule->action('component_rules_test_alias');
  96. $rule->integrityCheck();
  97. $rule->save('rules_test_rule');
  98. $rule = rules_config_load('rules_test_rule');
  99. $component = rules_config_load('rules_test_alias');
  100. $this->assertTrue(in_array('path', $component->dependencies) && !$rule->dirty && !$component->dirty, 'Component has path module dependency.');
  101. // Now disable path module and make sure both configs are marked as dirty.
  102. module_disable(array('path'));
  103. rules_clear_cache();
  104. $rule = rules_config_load('rules_test_rule');
  105. $component = rules_config_load('rules_test_alias');
  106. $this->assertTrue($component->dirty, 'Component has been marked as dirty');
  107. $node = $this->drupalCreateNode();
  108. $result = rules_invoke_component('rules_test_alias', $node);
  109. $this->assertTrue($result === FALSE, 'Unable to execute a dirty component.');
  110. // When the rule is evaluated, the broken component is detected and the
  111. // rule should be marked as dirty too.
  112. $rule->execute($node);
  113. $this->assertTrue($rule->dirty, 'Rule has been marked as dirty');
  114. module_enable(array('path'));
  115. rules_clear_cache();
  116. // Trigger rebuilding the cache, so configs are checked again.
  117. rules_get_cache();
  118. $rule = rules_config_load('rules_test_rule');
  119. $component = rules_config_load('rules_test_alias');
  120. $this->assertTrue(!$component->dirty, 'Component has been marked as not dirty again.');
  121. $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');
  122. }
  123. /**
  124. * Test setting up an action with some action_info and serializing and
  125. * executing it.
  126. */
  127. function testActionSetup() {
  128. $action = rules_action('rules_node_publish_action');
  129. $s = serialize($action);
  130. $action2 = unserialize($s);
  131. $node = (object) array('status' => 0, 'type' => 'page');
  132. $node->title = 'test';
  133. $action2->execute($node);
  134. $this->assertEqual($node->status, 1, 'Action executed correctly');
  135. $this->assertTrue(in_array('node', array_keys($action2->parameterInfo())), 'Parameter info returned.');
  136. $node->status = 0;
  137. $action2->integrityCheck();
  138. $action2->executeByArgs(array('node' => $node));
  139. $this->assertEqual($node->status, 1, 'Action executed correctly');
  140. // Test calling an extended + overriden method.
  141. $this->assertEqual($action2->help(), 'custom', 'Using custom help callback.');
  142. // Inspect the cache
  143. //$this->pass(serialize(rules_get_cache()));
  144. RulesLog::logger()->checkLog();
  145. }
  146. /**
  147. * Test executing with wrong arguments.
  148. */
  149. function testActionExecutionFails() {
  150. $action = rules_action('rules_node_publish_action');
  151. try {
  152. $action->execute();
  153. $this->fail("Execution hasn't created an exception.");
  154. }
  155. catch (RulesEvaluationException $e) {
  156. $this->pass("RulesEvaluationException was thrown: ". $e);
  157. }
  158. }
  159. /**
  160. * Test setting up a rule and mapping variables.
  161. */
  162. function testVariableMapping() {
  163. $rule = rule(array(
  164. 'node' => array('type' => 'node'),
  165. 'node_unchanged' => array('type' => 'node'),
  166. ));
  167. $rule->condition(rules_condition('rules_condition_content_is_published')->negate())
  168. ->condition('rules_condition_content_is_type', array('type' => array('page', 'story')))
  169. ->action('rules_node_publish_action', array('node:select' => 'node_unchanged'));
  170. $node1 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
  171. $node2 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
  172. $rule->integrityCheck();
  173. $rule->execute($node1, $node2);
  174. $this->assertEqual($node2->status, 1, 'Action executed correctly on node2.');
  175. $this->assertEqual($node1->status, 0, 'Action not executed on node1.');
  176. RulesLog::logger()->checkLog();
  177. }
  178. /**
  179. * Tests CRUD functionality.
  180. */
  181. function testRulesCRUD() {
  182. $rule = $this->createTestRule();
  183. $rule->integrityCheck()->save('test');
  184. $this->assertEqual(TRUE, $rule->active, 'Rule is active.');
  185. $this->assertEqual(0, $rule->weight, 'Rule weight is zero.');
  186. $results = entity_load('rules_config', array('test'));
  187. $rule2 = array_pop($results);
  188. $this->assertEqual($rule->id, $rule2->id, 'Rule created and loaded');
  189. $this->assertEqual(get_class($rule2), get_class($rule), 'Class properly instantiated.');
  190. $rule2->execute();
  191. // Update.
  192. $rule2->save();
  193. // Make sure all rule elements are still here.
  194. $it = new RecursiveIteratorIterator($rule2->conditions(), RecursiveIteratorIterator::SELF_FIRST);
  195. $this->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers');
  196. $it = new RecursiveIteratorIterator($rule2->conditions());
  197. $this->assertEqual(iterator_count($it), 6, 'Iterated over all conditions');
  198. $this->assertEqual(iterator_count($rule2->actions()), 1, 'Iterated over all actions');
  199. // Delete.
  200. $rule2->delete();
  201. $this->assertEqual(entity_load('rules_config', FALSE, array('id' => $rule->id)), array(), 'Deleted.');
  202. // Tests CRUD for tags - making sure the tags are stored properly..
  203. $rule = $this->createTestRule();
  204. $tag = $this->randomString();
  205. $rule->tags = array($tag);
  206. $rule->save();
  207. $result = db_select('rules_tags')
  208. ->fields('rules_tags', array('tag'))
  209. ->condition('id', $rule->id)
  210. ->execute();
  211. $this->assertEqual($result->fetchField(), $tag, 'Associated tag has been saved.');
  212. // Try updating.
  213. $rule->tags = array($this->randomName(), $this->randomName());
  214. $rule->integrityCheck()->save();
  215. $result = db_select('rules_tags')
  216. ->fields('rules_tags', array('tag'))
  217. ->condition('id', $rule->id)
  218. ->execute()
  219. ->fetchCol();
  220. $this->assertTrue(in_array($rule->tags[0], $result) && in_array($rule->tags[1], $result), 'Updated associated tags.');
  221. // Try loading multiple rules by tags.
  222. $rule2 = $this->createTestRule();
  223. $rule2->tags = array($this->randomName());
  224. $rule2->save();
  225. $loaded = entity_load('rules_config', FALSE, array('tags' => array($rule->tags[0], $rule2->tags[0])));
  226. $this->assertTrue($loaded[$rule->id]->id == $rule->id && $loaded[$rule2->id]->id == $rule2->id, 'Loading configs by tags');
  227. // Try deleting.
  228. $rule->delete();
  229. $result = db_select('rules_tags')
  230. ->fields('rules_tags', array('tag'))
  231. ->condition('id', $rule->id)
  232. ->execute();
  233. $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated tags.');
  234. }
  235. /**
  236. * Test automatic saving of variables.
  237. */
  238. function testActionSaving() {
  239. // Test saving a parameter.
  240. $action = rules_action('rules_node_publish_action_save');
  241. $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
  242. $action->executeByArgs(array('node' => $node));
  243. $this->assertEqual($node->status, 1, 'Action executed correctly on node.');
  244. // Sync node_load cache with node_save
  245. entity_get_controller('node')->resetCache();
  246. $node = node_load($node->nid);
  247. $this->assertEqual($node->status, 1, 'Node has been saved.');
  248. // Now test saving a provided variable, which is renamed and modified before
  249. // it is saved.
  250. $title = $this->randomName();
  251. $rule = rule();
  252. $rule->action('entity_create', array(
  253. 'type' => 'node',
  254. 'param_type' => 'article',
  255. 'param_author:select' => 'site:current-user',
  256. 'param_title' => $title,
  257. 'entity_created:var' => 'node',
  258. ));
  259. $rule->action('data_set', array(
  260. 'data:select' => 'node:body',
  261. 'value' => array('value' => 'body'),
  262. ));
  263. $rule->integrityCheck();
  264. $rule->execute();
  265. $node = $this->drupalGetNodeByTitle($title);
  266. $this->assertTrue(!empty($node) && $node->body[LANGUAGE_NONE][0]['value'] == 'body', 'Saved a provided variable');
  267. RulesLog::logger()->checkLog();
  268. }
  269. /**
  270. * Test adding a variable and optional parameters.
  271. */
  272. function testVariableAdding() {
  273. $node = $this->drupalCreateNode();
  274. $rule = rule(array('nid' => array('type' => 'integer')));
  275. $rule->condition('rules_test_condition_true')
  276. ->action('rules_action_load_node')
  277. ->action('rules_action_delete_node', array('node:select' => 'node_loaded'))
  278. ->execute($node->nid);
  279. $this->assertEqual(FALSE, node_load($node->nid), 'Variable added and skipped optional parameter.');
  280. RulesLog::logger()->checkLog();
  281. $vars = $rule->conditions()->offsetGet(0)->availableVariables();
  282. $this->assertEqual(!isset($vars['node_loaded']), 'Loaded variable is not available to conditions.');
  283. // Test adding a variable with a custom variable name.
  284. $node = $this->drupalCreateNode();
  285. $rule = rule(array('nid' => array('type' => 'integer')));
  286. $rule->action('rules_action_load_node', array('node_loaded:var' => 'node'))
  287. ->action('rules_action_delete_node')
  288. ->execute($node->nid);
  289. $this->assertEqual(FALSE, node_load($node->nid), 'Variable with custom name added.');
  290. RulesLog::logger()->checkLog();
  291. }
  292. /**
  293. * Test custom access for using component actions/conditions.
  294. */
  295. function testRuleComponentAccess() {
  296. // Create a normal user.
  297. $normal_user = $this->drupalCreateUser();
  298. // Create a role for granting access to the rule component.
  299. $this->normal_role = $this->drupalCreateRole(array(), 'test_role');
  300. $normal_user->roles[$this->normal_role] = 'test_role';
  301. user_save($normal_user, array('roles' => $normal_user->roles));
  302. // Create an 'action set' rule component making use of a permission.
  303. $action_set = rules_action_set(array('node' => array('type' => 'node')));
  304. $action_set->access_exposed = TRUE;
  305. $action_set->save('rules_test_roles');
  306. // Set the global user to be the current one as access is checked for the
  307. // global user.
  308. global $user;
  309. $user = user_load($normal_user->uid);
  310. $this->assertFalse(rules_action('component_rules_test_roles')->access(), 'Authenticated user without the correct role can\'t use the rule component.');
  311. // Assign the role that will have permissions for the rule component.
  312. user_role_change_permissions($this->normal_role, array('use Rules component rules_test_roles' => TRUE));
  313. $this->assertTrue(rules_action('component_rules_test_roles')->access(), 'Authenticated user with the correct role can use the rule component.');
  314. // Reset global user to anonyous.
  315. $user = user_load(0);
  316. $this->assertFalse(rules_action('component_rules_test_roles')->access(), 'Anonymous user can\'t use the rule component.');
  317. }
  318. /**
  319. * Test passing arguments by reference to an action.
  320. */
  321. function testPassingByReference() {
  322. // Keeping references of variables is unsupported, though the
  323. // EntityMetadataArrayObject may be used to achieve that.
  324. $array = array('foo' => 'bar');
  325. $data = new EntityMetadataArrayObject($array);
  326. rules_action('rules_action_test_reference')->execute($data);
  327. $this->assertTrue($data['changed'], 'Parameter has been passed by reference');
  328. }
  329. /**
  330. * Test sorting rule elements.
  331. */
  332. function testSorting() {
  333. $rule = $this->createTestRule();
  334. $conditions = $rule->conditions();
  335. $conditions[0]->weight = 10;
  336. $conditions[2]->weight = 10;
  337. $id[0] = $conditions[0]->elementId();
  338. $id[1] = $conditions[1]->elementId();
  339. $id[2] = $conditions[2]->elementId();
  340. // For testing use a deep sort, even if not necessary here.
  341. $rule->sortChildren(TRUE);
  342. $conditions = $rule->conditions();
  343. $this->assertEqual($conditions[0]->elementId(), $id[1], 'Condition sorted correctly.');
  344. $this->assertEqual($conditions[1]->elementId(), $id[0], 'Condition sorted correctly.');
  345. $this->assertEqual($conditions[2]->elementId(), $id[2], 'Condition sorted correctly.');
  346. }
  347. /**
  348. * Tests using data selectors.
  349. */
  350. function testDataSelectors() {
  351. $body[LANGUAGE_NONE][0] = array('value' => '<b>The body & nothing.</b>');
  352. $node = $this->drupalCreateNode(array('body' => $body, 'type' => 'page', 'summary' => ''));
  353. $rule = rule(array('nid' => array('type' => 'integer')));
  354. $rule->action('rules_action_load_node')
  355. ->action('drupal_message', array('message:select' => 'node_loaded:body:value'))
  356. ->execute($node->nid);
  357. RulesLog::logger()->checkLog();
  358. $msg = drupal_get_messages('status');
  359. $last_msg = array_pop($msg['status']);
  360. $wrapper = entity_metadata_wrapper('node', $node);
  361. $this->assertEqual($last_msg, $wrapper->body->value->value(array('sanitize' => TRUE)), 'Data selector for getting parameter applied.');
  362. // Get a "reference" on the same object as returned by node_load().
  363. $node = node_load($node->nid);
  364. $rule = rule(array('nid' => array('type' => 'integer')));
  365. $rule->action('rules_action_load_node')
  366. ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title'))
  367. // Use two actions and make sure the node get saved only once.
  368. ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title2'))
  369. ->execute($node->nid);
  370. $wrapper = entity_metadata_wrapper('node', $node);
  371. $this->assertEqual('Test title2', $wrapper->title->value(), 'Data has been modified and saved.');
  372. RulesLog::logger()->checkLog();
  373. $text = RulesLog::logger()->render();
  374. $msg = RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node'));
  375. if ($pos1 = strpos($text, $msg)) {
  376. $pos2 = strpos($text, $msg, $pos1 + 1);
  377. }
  378. $this->assertTrue($pos1 && $pos2 === FALSE, 'Data has been saved only once.');
  379. // Test validation.
  380. try {
  381. rules_action('data_set', array('data' => 'no-selector', 'value' => ''))->integrityCheck();
  382. $this->fail("Validation hasn't created an exception.");
  383. }
  384. catch (RulesIntegrityException $e) {
  385. $this->pass("Validation error correctly detected: ". $e);
  386. }
  387. // Test auto creation of nested data structures, like the node body field.
  388. // I.e. if $node->body is not set, it is automatically initialized to an
  389. // empty array, so that the nested value can be set and the wrappers do not
  390. // complain about missing parent data structures.
  391. $rule = rule();
  392. $rule->action('entity_create', array(
  393. 'type' => 'node',
  394. 'param_type' => 'page',
  395. 'param_title' => 'foo',
  396. 'param_author' => $GLOBALS['user'],
  397. ));
  398. $rule->action('data_set', array('data:select' => 'entity_created:body:value', 'value' => 'test content'))
  399. ->execute();
  400. try {
  401. RulesLog::logger()->checkLog();
  402. $this->pass('Auto creation of nested data structures.');
  403. }
  404. catch (Exception $e) {
  405. $this->fail('Auto creation of nested data structures.');
  406. }
  407. // Make sure variables that are passed wrapped work.
  408. $result = rules_condition('rules_test_condition_node_wrapped')->execute($node->nid);
  409. $this->assertTrue($result, 'Condition receiving wrapped parameter.');
  410. // Make sure wrapped parameters are checked for containing NULL values.
  411. $rule = rule(array('node' => array('type' => 'node', 'optional' => TRUE)));
  412. $rule->condition('rules_test_condition_node_wrapped', array('node:select' => 'node'));
  413. $rule->execute(entity_metadata_wrapper('node'));
  414. $text = RulesLog::logger()->render();
  415. $msg = RulesTestCase::t('The variable or parameter %node is empty.', array('node'));
  416. $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
  417. }
  418. /**
  419. * Tests making use of rule sets.
  420. */
  421. function testRuleSets() {
  422. $set = rules_rule_set(array(
  423. 'node' => array('type' => 'node', 'label' => 'node'),
  424. ));
  425. $set->rule(rule()->action('drupal_message', array('message:select' => 'node:title')))
  426. ->rule(rule()->condition('rules_condition_content_is_published')
  427. ->action('drupal_message', array('message' => 'Node is published.'))
  428. );
  429. $set->integrityCheck()->save('rules_test_set_1');
  430. $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1));
  431. // Execute.
  432. rules_invoke_component('rules_test_set_1', $node);
  433. $msg = drupal_get_messages();
  434. $this->assertEqual($msg['status'][0], 'The title.', 'First rule evaluated.');
  435. $this->assertEqual($msg['status'][1], 'Node is published.', 'Second rule evaluated.');
  436. // Test a condition set.
  437. $set = rules_or(array(
  438. 'node' => array('type' => 'node', 'label' => 'node'),
  439. ));
  440. $set->condition('data_is', array('data:select' => 'node:author:name', 'value' => 'notthename'))
  441. ->condition('data_is', array('data:select' => 'node:nid', 'value' => $node->nid))
  442. ->integrityCheck()
  443. ->save('test', 'rules_test');
  444. // Load and execute condition set.
  445. $set = rules_config_load('test');
  446. $this->assertTrue($set->execute($node), 'Set has been correctly evaluated.');
  447. RulesLog::logger()->checkLog();
  448. }
  449. /**
  450. * Tests invoking components from the action.
  451. */
  452. function testComponentInvocations() {
  453. $set = rules_rule_set(array(
  454. 'node1' => array('type' => 'node', 'label' => 'node'),
  455. ));
  456. $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1'))
  457. ->action('node_unpublish', array('node:select' => 'node1'))
  458. );
  459. $set->integrityCheck()->save('rules_test_set_2');
  460. // Use different names for the variables to ensure they are properly mapped
  461. // when taking over the variables to be saved.
  462. $rule = rule(array(
  463. 'node2' => array('type' => 'node', 'label' => 'node'),
  464. ));
  465. $rule->action('component_rules_test_set_2', array('node1:select' => 'node2'));
  466. $rule->action('node_make_sticky', array('node:select' => 'node2'));
  467. $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1, 'sticky' => 0));
  468. $rule->execute($node);
  469. $node = node_load($node->nid, NULL, TRUE);
  470. $this->assertFalse($node->status, 'The component changes have been saved correctly.');
  471. $this->assertTrue($node->sticky, 'The action changes have been saved correctly.');
  472. // Check that we have saved the changes only once.
  473. $text = RulesLog::logger()->render();
  474. // Make sure both saves are handled in one save operation.
  475. $this->assertEqual(substr_count($text, 'Saved'), 1, 'Changes have been saved in one save operation.');
  476. RulesLog::logger()->checkLog();
  477. // Test recursion prevention on components by invoking the component from
  478. // itself, what should be prevented.
  479. $set->action('component_rules_test_set_2', array('node1:select' => 'node1'))
  480. ->save();
  481. $rule->execute($node);
  482. $text1 = RulesLog::logger()->render();
  483. $text2 = RulesTestCase::t('Not evaluating rule set %rules_test_set_2 to prevent recursion.', array('rules_test_set_2'));
  484. $this->assertTrue((strpos($text1, $text2) !== FALSE), "Recursion of component invocation prevented.");
  485. // Test executing the component provided in code via the action. This makes
  486. // sure the component in code has been properly picked up.
  487. $node->status = 0;
  488. node_save($node);
  489. rules_action('component_rules_test_action_set')->execute($node);
  490. $this->assertTrue($node->status == 1, 'Component provided in code has been executed.');
  491. }
  492. /**
  493. * Test asserting metadata, customizing action info and make sure integrity
  494. * is checked.
  495. */
  496. function testMetadataAssertion() {
  497. $action = rules_action('rules_node_make_sticky_action');
  498. // Test failing integrity check.
  499. try {
  500. $rule = rule(array('node' => array('type' => 'entity')));
  501. $rule->action($action);
  502. // Fails due to the 'node' variable not matching the node type.
  503. $rule->integrityCheck();
  504. $this->fail('Integrity check has not thrown an exception.');
  505. }
  506. catch (RulesIntegrityException $e) {
  507. $this->pass('Integrity check has thrown exception: ' . $e->getMessage());
  508. }
  509. // Test asserting additional metadata.
  510. $rule = rule(array('node' => array('type' => 'node')));
  511. // Customize action info using the settings.
  512. $rule->condition('data_is', array('data:select' => 'node:type', 'value' => 'page'))
  513. // Configure an condition using the body. As the body is a field,
  514. // tis requires the bundle to be correctly asserted.
  515. ->condition(rules_condition('data_is', array('data:select' => 'node:body:value', 'value' => 'foo'))->negate())
  516. // The action also requires the page bundle in order to work.
  517. ->action($action);
  518. // Make sure the integrity check doesn't throw an exception.
  519. $rule->integrityCheck();
  520. // Test the rule.
  521. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
  522. $rule->execute($node);
  523. $this->assertTrue($node->sticky, 'Rule with asserted metadata executed.');
  524. // Test asserting metadata on a derived property, i.e. not a variable.
  525. $rule = rule(array('node' => array('type' => 'node')));
  526. $rule->condition('entity_is_of_type', array('entity:select' => 'node:reference', 'type' => 'node'))
  527. ->condition('data_is', array('data:select' => 'node:reference:type', 'value' => 'page'))
  528. ->action('rules_node_page_make_sticky_action', array('node:select' => 'node:reference'));
  529. $rule->integrityCheck();
  530. $rule->execute($node);
  531. // Test asserting an entity field.
  532. $rule = rule(array('node' => array('type' => 'node')));
  533. $rule->condition('entity_has_field', array('entity:select' => 'node:reference', 'field' => 'field_tags'))
  534. ->action('data_set', array('data:select' => 'node:reference:field-tags', 'value' => array()));
  535. $rule->integrityCheck();
  536. $rule->execute($node);
  537. // Make sure an asserted bundle can be used as argument.
  538. $rule = rule(array('node' => array('type' => 'node')));
  539. $rule->condition('entity_is_of_type', array('entity:select' => 'node:reference', 'type' => 'node'))
  540. ->condition('node_is_of_type', array('node:select' => 'node:reference', 'type' => array('page')))
  541. ->action('rules_node_page_make_sticky_action', array('node:select' => 'node:reference'));
  542. $rule->integrityCheck();
  543. $rule->execute($node);
  544. // Test asserting metadata on a derived property being a list item.
  545. $rule = rule(array('node' => array('type' => 'node')));
  546. $rule->condition('node_is_of_type', array('node:select' => 'node:ref-nodes:0', 'type' => array('article')))
  547. ->action('data_set', array('data:select' => 'node:ref-nodes:0:field-tags', 'value' => array()));
  548. $rule->integrityCheck();
  549. $rule->execute($node);
  550. // Give green lights if there were no exceptions and check rules-log errors.
  551. $this->pass('Rules asserting metadata on a derived property pass integrity checks.');
  552. RulesLog::logger()->checkLog();
  553. // Make sure assertions of a one list item are not valid for another item.
  554. $rule = rule(array('node' => array('type' => 'node')));
  555. $rule->condition('node_is_of_type', array('node:select' => 'node:ref-nodes:0', 'type' => array('article')))
  556. ->action('data_set', array('data:select' => 'node:ref-nodes:1:field-tags', 'value' => array()));
  557. try {
  558. $rule->integrityCheck();
  559. $this->fail('Assertion of a list item is not valid for another item.');
  560. }
  561. catch (RulesException $e) {
  562. $this->pass('Assertion of a list item is not valid for another item.');
  563. }
  564. }
  565. /**
  566. * Test using loops.
  567. */
  568. function testLoops() {
  569. // Test passing the list parameter as argument to ensure that is working
  570. // generally for plugin container too.
  571. drupal_get_messages(NULL, TRUE);
  572. $loop = rules_loop();
  573. $loop->action('drupal_message', array('message' => 'test'));
  574. $arg_info = $loop->parameterInfo();
  575. $this->assert($arg_info['list']['type'] == 'list', 'Argument info contains list.');
  576. $loop->execute(array(1, 2));
  577. // Ensure the action has been executed twice, once for each list item.
  578. $msg = drupal_get_messages();
  579. $this->assert($msg['status'][0] == 'test' && $msg['status'][1], 'Loop has been properly executed');
  580. // Now test looping over nodes.
  581. $node1 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
  582. $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
  583. $node3 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
  584. $rule = rule(array(
  585. 'list' => array(
  586. 'type' => 'list<node>',
  587. 'label' => 'A list of nodes',
  588. )
  589. ));
  590. $loop = rules_loop(array('list:select' => 'list', 'item:var' => 'node'));
  591. $loop->action('data_set', array('data:select' => 'node:sticky', 'value' => TRUE));
  592. $rule->action($loop);
  593. // Test using a list with data selectors, just output the last nodes type.
  594. $rule->action('drupal_message', array('message:select' => 'list:2:type'));
  595. $rule->execute(array($node1->nid, $node2->nid, $node3->nid));
  596. $text = RulesLog::logger()->render();
  597. $save_msg = RulesTestCase::t('Saved %node of type %node.', array('node', 'node'));
  598. $this->assertTrue(substr_count($text, $save_msg) == 3, 'List item variables have been saved.');
  599. RulesLog::logger()->checkLog();
  600. }
  601. /**
  602. * Test access checks.
  603. */
  604. function testAccessCheck() {
  605. $rule = rule();
  606. // Try to set a property which is provided by the test module and is not
  607. // accessible, so the access check has to return FALSE.
  608. $rule->action('data_set', array('data:select' => 'site:no-access-user', 'value' => 'foo'));
  609. $this->assertTrue($rule->access() === FALSE, 'Access check is working.');
  610. }
  611. /**
  612. * Test returning provided variables.
  613. */
  614. function testReturningVariables() {
  615. $node = $this->drupalCreateNode();
  616. $action = rules_action('entity_fetch', array('type' => 'node', 'id' => $node->nid));
  617. list($node2) = $action->execute();
  618. $this->assertTrue($node2->nid == $node->nid, 'Action returned a variable.');
  619. // Create a simple set that just passed through the given node.
  620. $set = rules_rule_set(array('node' => array('type' => 'node')), array('node'));
  621. $set->integrityCheck()->save('rules_test_set_1');
  622. $provides = $set->providesVariables();
  623. $this->assertTrue($provides['node']['type'] == 'node', 'Rule set correctly passed through the node.');
  624. list($node2) = $set->execute($node);
  625. $this->assertTrue($node2->nid == $node->nid, 'Rule set returned a variable.');
  626. // Create an action set returning a variable that is no parameter.
  627. $set = rules_action_set(array(
  628. 'node' => array(
  629. 'type' => 'node',
  630. 'parameter' => FALSE,
  631. )), array('node'));
  632. $set->action('entity_fetch', array('type' => 'node', 'id' => $node->nid))
  633. ->action('data_set', array('data:select' => 'node', 'value:select' => 'entity_fetched'));
  634. $set->integrityCheck();
  635. list($node3) = $set->execute();
  636. $this->assertTrue($node3->nid == $node->nid, 'Action set returned a variable that has not been passed as parameter.');
  637. // Test the same again with a variable holding a not wrapped data type.
  638. $set = rules_action_set(array(
  639. 'number' => array(
  640. 'type' => 'integer',
  641. 'parameter' => FALSE,
  642. )), array('number'));
  643. $set->action('data_set', array('data:select' => 'number', 'value' => 3));
  644. $set->integrityCheck();
  645. list($number) = $set->execute();
  646. $this->assertTrue($number == 3, 'Actions set returned a number.');
  647. }
  648. /**
  649. * Tests using input evaluators.
  650. */
  651. function testInputEvaluators() {
  652. $node = $this->drupalCreateNode(array('title' => '<b>The body & nothing.</b>', 'type' => 'page'));
  653. $rule = rule(array('nid' => array('type' => 'integer')));
  654. $rule->action('rules_action_load_node')
  655. ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'))
  656. ->execute($node->nid);
  657. RulesLog::logger()->checkLog();
  658. $msg = drupal_get_messages();
  659. $this->assertEqual(array_pop($msg['status']), 'Title: ' . check_plain('<b>The body & nothing.</b>'), 'Token input evaluator applied.');
  660. // Test token replacements on a list of text values.
  661. $component = rules_action_set(array('var' => array('type' => 'list<text>', 'label' => 'var')), array('var'));
  662. $component->save('rules_test_input');
  663. $action = rules_action('component_rules_test_input', array('var' => array('uid: [site:current-user:uid]')));
  664. list($var) = $action->execute();
  665. $uid = $GLOBALS['user']->uid;
  666. $this->assertEqual(array("uid: $uid"), $var, 'Token replacements on a list of values applied.');
  667. }
  668. /**
  669. * Test importing and exporting a rule.
  670. */
  671. function testRuleImportExport() {
  672. $rule = rule(array('nid' => array('type' => 'integer')));
  673. $rule->name = "rules_export_test";
  674. $rule->action('rules_action_load_node')
  675. ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'));
  676. $export =
  677. '{ "rules_export_test" : {
  678. "PLUGIN" : "rule",
  679. "REQUIRES" : [ "rules_test", "rules" ],
  680. "USES VARIABLES" : { "nid" : { "type" : "integer" } },
  681. "DO" : [
  682. { "rules_action_load_node" : { "PROVIDE" : { "node_loaded" : { "node_loaded" : "Loaded content" } } } },
  683. { "drupal_message" : { "message" : "Title: [node_loaded:title]" } }
  684. ]
  685. }
  686. }';
  687. $this->assertEqual($export, $rule->export(), 'Rule has been exported correctly.');
  688. // Test importing a rule which makes use of almost all features.
  689. $export = _rules_export_get_test_export();
  690. $rule = rules_import($export);
  691. $this->assertTrue(!empty($rule) && $rule->integrityCheck(), 'Rule has been imported.');
  692. // Test loading the same export provided as default rule.
  693. $rule = rules_config_load('rules_export_test');
  694. $this->assertTrue(!empty($rule) && $rule->integrityCheck(), 'Export has been provided in code.');
  695. // Export it and make sure the same export is generated again.
  696. $this->assertEqual($export, $rule->export(), 'Export of imported rule equals original export.');
  697. // Now try importing a rule set.
  698. $export =
  699. '{ "rules_test_set" : {
  700. "LABEL" : "Test set",
  701. "PLUGIN" : "rule set",
  702. "REQUIRES" : [ "rules" ],
  703. "USES VARIABLES" : { "node" : { "label" : "Test node", "type" : "node" } },
  704. "RULES" : [
  705. { "RULE" : {
  706. "IF" : [ { "NOT data_is" : { "data" : [ "node:title" ], "value" : "test" } } ],
  707. "DO" : [ { "data_set" : { "data" : [ "node:title" ], "value" : "test" } } ],
  708. "LABEL" : "Test Rule"
  709. }
  710. },
  711. { "RULE" : {
  712. "DO" : [ { "drupal_message" : { "message" : "hi" } } ],
  713. "LABEL" : "Test Rule 2"
  714. }
  715. }
  716. ]
  717. }
  718. }';
  719. $set = rules_import($export);
  720. $this->assertTrue(!empty($set) && $set->integrityCheck(), 'Rule set has been imported.');
  721. // Export it and make sure the same export is generated again.
  722. $this->assertEqual($export, $set->export(), 'Export of imported rule set equals original export.');
  723. // Try executing the imported rule set.
  724. $node = $this->drupalCreateNode();
  725. $set->execute($node);
  726. $this->assertEqual($node->title, 'test', 'Imported rule set has been executed.');
  727. RulesLog::logger()->checkLog();
  728. // Try import / export for a rule component providing a variable.
  729. $rule = rule(array(
  730. 'number' => array(
  731. 'type' => 'integer',
  732. 'label' => 'Number',
  733. 'parameter' => FALSE,
  734. )), array('number'));
  735. $rule->action('data_set', array('data:select' => 'number', 'value' => 3));
  736. $rule->name = 'rules_test_provides';
  737. $export = '{ "rules_test_provides" : {
  738. "PLUGIN" : "rule",
  739. "REQUIRES" : [ "rules" ],
  740. "USES VARIABLES" : { "number" : { "type" : "integer", "label" : "Number", "parameter" : false } },
  741. "DO" : [ { "data_set" : { "data" : [ "number" ], "value" : 3 } } ],
  742. "PROVIDES VARIABLES" : [ "number" ]
  743. }
  744. }';
  745. $this->assertEqual($export, $rule->export(), 'Rule 2 has been exported correctly.');
  746. $imported_rule = rules_import($rule->export());
  747. $this->assertTrue(!empty($imported_rule) && $imported_rule->integrityCheck(), 'Rule 2 has been imported.');
  748. $this->assertEqual($export, $imported_rule->export(), 'Export of imported rule 2 equals original export.');
  749. // Test importing a negated condition component.
  750. $export = '{ "rules_negated_component" : {
  751. "LABEL" : "negated_component",
  752. "PLUGIN" : "or",
  753. "REQUIRES" : [ "rules" ],
  754. "NOT OR" : [ { "data_is_empty" : { "data" : [ "site:slogan" ] } } ]
  755. }
  756. }';
  757. $or = rules_import($export);
  758. $this->assertTrue($or->integrityCheck() && $or->isNegated(), 'Negated condition component imported.');
  759. }
  760. /**
  761. * Test the named parameter mode.
  762. */
  763. function testNamedParameters() {
  764. $rule = rule(array('node' => array('type' => 'node')));
  765. $rule->action('rules_action_node_set_title', array('title' => 'foo'));
  766. $rule->integrityCheck();
  767. // Test the rule.
  768. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
  769. $rule->execute($node);
  770. $this->assertTrue($node->title == 'foo', 'Action with named parameters has been correctly executed.');
  771. RulesLog::logger()->checkLog();
  772. }
  773. /**
  774. * Make sure Rules aborts when NULL values are used.
  775. */
  776. function testAbortOnNULLValues() {
  777. $rule = rule(array('node' => array('type' => 'node')));
  778. $rule->action('drupal_message', array('message:select' => 'node:log'));
  779. $rule->integrityCheck();
  780. // Test the rule.
  781. $node = $this->drupalCreateNode();
  782. $node->log = NULL;
  783. $rule->execute($node);
  784. $text = RulesLog::logger()->render();
  785. $msg = RulesTestCase::t('The variable or parameter %message is empty.', array('message'));
  786. $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
  787. }
  788. }
  789. /**
  790. * Test rules data wrappers.
  791. */
  792. class RulesTestDataCase extends DrupalWebTestCase {
  793. static function getInfo() {
  794. return array(
  795. 'name' => 'Rules Data tests',
  796. 'description' => 'Tests rules data saving and type matching.',
  797. 'group' => 'Rules',
  798. );
  799. }
  800. function setUp() {
  801. parent::setUp('rules', 'rules_test');
  802. variable_set('rules_debug_log', 1);
  803. // Make sure we don't ran over issues with the node_load static cache.
  804. entity_get_controller('node')->resetCache();
  805. }
  806. /**
  807. * Tests intelligently saving data.
  808. */
  809. function testDataSaving() {
  810. $node = $this->drupalCreateNode();
  811. $state = new RulesState(rule());
  812. $state->addVariable('node', $node, array('type' => 'node'));
  813. $wrapper = $state->get('node');
  814. $node->title = 'test';
  815. $wrapper->set($node);
  816. $state->saveChanges('node', $wrapper, FALSE);
  817. $this->assertFalse($this->drupalGetNodeByTitle('test'), 'Changes have not been saved.');
  818. $state->saveChanges('node', $wrapper, TRUE);
  819. $this->assertTrue($this->drupalGetNodeByTitle('test'), 'Changes have been saved.');
  820. // Test skipping saving.
  821. $state->addVariable('node2', $node, array(
  822. 'type' => 'node',
  823. 'skip save' => TRUE,
  824. ));
  825. $wrapper = $state->get('node2');
  826. $node->title = 'test2';
  827. $wrapper->set($node);
  828. $state->saveChanges('node2', $wrapper, TRUE);
  829. $this->assertFalse($this->drupalGetNodeByTitle('test2'), 'Changes have not been saved.');
  830. // Try saving a non-entity wrapper, which should result in saving the
  831. // parent entity containing the property.
  832. $wrapper = $state->get('node');
  833. $wrapper->title->set('test3');
  834. $state->saveChanges('node:title', $wrapper, TRUE);
  835. $this->assertTrue($this->drupalGetNodeByTitle('test3'), 'Parent entity has been saved.');
  836. }
  837. /**
  838. * Test type matching
  839. */
  840. function testTypeMatching() {
  841. $entity = array('type' => 'entity');
  842. $node = array('type' => 'node');
  843. $this->assertTrue(RulesData::typesMatch($node, $entity), 'Types match.');
  844. $this->assertFalse(RulesData::typesMatch($entity, $node), 'Types don\'t match.');
  845. $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node), 'Types match.');
  846. $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $entity), 'Types match.');
  847. $this->assertTrue(RulesData::typesMatch(array('type' => 'list<node>'), array('type' => 'list')), 'Types match.');
  848. $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node + array('bundles' => array('page', 'story'))), 'Types match.');
  849. $this->assertFalse(RulesData::typesMatch($node, $node + array('bundles' => array('page', 'story'))), 'Types don\'t match.');
  850. // Test that a type matches its grand-parent type (text > decimal > integer)
  851. $this->assertTrue(RulesData::typesMatch(array('type' => 'integer'), array('type' => 'text')), 'Types match.');
  852. $this->assertFalse(RulesData::typesMatch(array('type' => 'text'), array('type' => 'integer')), 'Types don\'t match.');
  853. }
  854. /**
  855. * Tests making use of custom wrapper classes.
  856. */
  857. function testCustomWrapperClasses() {
  858. // Test loading a vocabulary by name, which is done by a custom wrapper.
  859. $set = rules_action_set(array('vocab' => array('type' => 'taxonomy_vocabulary')), array('vocab'));
  860. $set->action('drupal_message', array('message:select' => 'vocab:name'));
  861. $set->integrityCheck();
  862. list($vocab) = $set->execute('tags');
  863. $this->assertTrue($vocab->machine_name == 'tags', 'Loaded vocabulary by name.');
  864. // Now test wrapper creation for a direct input argument value.
  865. $set = rules_action_set(array('term' => array('type' => 'taxonomy_term')));
  866. $set->action('data_set', array('data:select' => 'term:vocabulary', 'value' => 'tags'));
  867. $set->integrityCheck();
  868. $vocab = entity_create('taxonomy_vocabulary', array(
  869. 'name' => 'foo',
  870. 'machine_name' => 'foo',
  871. ));
  872. entity_save('taxonomy_vocabulary', $vocab);
  873. $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
  874. 'name' => $this->randomName(),
  875. 'vocabulary' => $vocab,
  876. ))->save();
  877. $set->execute($term_wrapped);
  878. $this->assertEqual($term_wrapped->vocabulary->machine_name->value(), 'tags', 'Vocabulary name used as direct input value.');
  879. RulesLog::logger()->checkLog();
  880. }
  881. /**
  882. * Makes sure the RulesIdentifiableDataWrapper is working correctly.
  883. */
  884. function testRulesIdentifiableDataWrapper() {
  885. $node = $this->drupalCreateNode();
  886. $wrapper = new RulesTestTypeWrapper('rules_test_type', $node);
  887. $this->assertTrue($wrapper->value() == $node, 'Data correctly wrapped.');
  888. // Test serializing and make sure only the id is stored.
  889. $this->assertTrue(strpos(serialize($wrapper), $node->title) === FALSE, 'Data has been correctly serialized.');
  890. $this->assertEqual(unserialize(serialize($wrapper))->value()->title, $node->title, 'Serializing works right.');
  891. $wrapper2 = unserialize(serialize($wrapper));
  892. // Test serializing the unloaded wrapper.
  893. $this->assertEqual(unserialize(serialize($wrapper2))->value()->title, $node->title, 'Serializing works right.');
  894. // Test loading a not more existing node.
  895. $s = serialize($wrapper2);
  896. node_delete($node->nid);
  897. $this->assertFalse(node_load($node->nid), 'Node deleted.');
  898. try {
  899. unserialize($s)->value();
  900. $this->fail("Loading hasn't created an exception.");
  901. }
  902. catch (EntityMetadataWrapperException $e) {
  903. $this->pass("Exception was thrown: ". $e->getMessage());
  904. }
  905. // Test saving a savable custom, identifiable wrapper.
  906. $action = rules_action('test_type_save');
  907. $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
  908. $node->status = 1;
  909. $action->execute($node);
  910. // Load the node fresh from the db.
  911. $node = node_load($node->nid, NULL, TRUE);
  912. $this->assertEqual($node->status, 1, 'Savable non-entity has been saved.');
  913. }
  914. }
  915. /**
  916. * Test triggering rules.
  917. */
  918. class RulesTriggerTestCase extends DrupalWebTestCase {
  919. static function getInfo() {
  920. return array(
  921. 'name' => 'Reaction Rules',
  922. 'description' => 'Tests triggering reactive rules.',
  923. 'group' => 'Rules',
  924. );
  925. }
  926. function setUp() {
  927. parent::setUp('rules', 'rules_test');
  928. RulesLog::logger()->clear();
  929. variable_set('rules_debug_log', 1);
  930. }
  931. protected function createTestRule($action = TRUE, $event = 'node_presave') {
  932. $rule = rules_reaction_rule();
  933. $rule->event($event)
  934. ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate())
  935. ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page'));
  936. if ($action) {
  937. $rule->action('rules_action_delete_node');
  938. }
  939. return $rule;
  940. }
  941. /**
  942. * Tests CRUD for reaction rules - making sure the events are stored properly.
  943. */
  944. function testReactiveRuleCreation() {
  945. $rule = $this->createTestRule();
  946. $rule->save();
  947. $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
  948. $this->assertEqual($result->fetchField(), 'node_presave', 'Associated event has been saved.');
  949. // Try updating.
  950. $events =& $rule->events();
  951. unset($events[0]);
  952. $events[] = 'node_insert';
  953. $events[] = 'node_update';
  954. $rule->active = FALSE;
  955. $rule->integrityCheck()->save();
  956. $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
  957. $this->assertEqual($result->fetchCol(), array_values($events), 'Updated associated events.');
  958. // Try deleting.
  959. $rule->delete();
  960. $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
  961. $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated events.');
  962. }
  963. /**
  964. * Tests creating and triggering a basic reaction rule.
  965. */
  966. function testBasicReactionRule() {
  967. $node = $this->drupalCreateNode(array('type' => 'page'));
  968. $rule = $this->createTestRule();
  969. $rule->integrityCheck()->save();
  970. // Test the basics of the event set work right.
  971. $event = rules_get_cache('event_node_presave');
  972. $this->assertEqual(array_keys($event->parameterInfo()), array('node'), 'EventSet returns correct argument info.');
  973. // Trigger the rule by updating the node.
  974. $nid = $node->nid;
  975. $node->status = 0;
  976. node_save($node);
  977. RulesLog::logger()->checkLog();
  978. $this->assertFalse(node_load($nid), 'Rule successfully triggered and executed');
  979. //debug(RulesLog::logger()->render());
  980. }
  981. /**
  982. * Test a rule using a handler to load a variable.
  983. */
  984. function testVariableHandler() {
  985. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  986. $rule = $this->createTestRule(FALSE, 'node_update');
  987. $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged'));
  988. // Test without recursion prevention to make sure recursive invocations
  989. // work right too. This rule won't ran in an infinite loop anyway.
  990. $rule->recursion = TRUE;
  991. $rule->label = 'rule 1';
  992. $rule->integrityCheck()->save();
  993. $node->status = 0;
  994. $node->sticky = 1;
  995. node_save($node);
  996. RulesLog::logger()->checkLog();
  997. entity_get_controller('node')->resetCache();
  998. $node = node_load($node->nid);
  999. $this->assertFalse($node->sticky, 'Parameter has been loaded and saved.');
  1000. $this->assertTrue($node->status, 'Action has been executed.');
  1001. // Ensure the rule was evaluated a second time
  1002. $text = RulesLog::logger()->render();
  1003. $msg = RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1'));
  1004. $pos = strpos($text, $msg);
  1005. $pos = ($pos !== FALSE) ? strpos($text, $msg, $pos) : FALSE;
  1006. $this->assertTrue($pos !== FALSE, "Recursion prevented.");
  1007. //debug(RulesLog::logger()->render());
  1008. }
  1009. /**
  1010. * Test aborting silently when handlers are not able to load.
  1011. */
  1012. function testVariableHandlerFailing() {
  1013. $rule = $this->createTestRule(FALSE, 'node_presave');
  1014. $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged'));
  1015. $rule->integrityCheck()->save();
  1016. // On insert it's not possible to get the unchanged node during presave.
  1017. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1018. //debug(RulesLog::logger()->render());
  1019. $text = RulesTestCase::t('Unable to load variable %node_unchanged, aborting.', array('node_unchanged'));
  1020. $this->assertTrue(strpos(RulesLog::logger()->render(), $text) !== FALSE, "Aborted evaluation.");
  1021. }
  1022. /**
  1023. * Tests preventing recursive rule invocations by creating a rule that reacts
  1024. * on node-update and generates a node update that would trigger it itself.
  1025. */
  1026. function testRecursionPrevention() {
  1027. $rule = $this->createTestRule(FALSE, 'node_update');
  1028. $rule->action('rules_node_make_sticky_action');
  1029. $rule->integrityCheck()->save();
  1030. // Now trigger the rule.
  1031. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1032. node_save($node);
  1033. $text = RulesTestCase::t('Not evaluating reaction rule %label to prevent recursion.', array('label' => $rule->name));
  1034. //debug(RulesLog::logger()->render());
  1035. $this->assertTrue((strpos(RulesLog::logger()->render(), $text) !== FALSE), "Recursion prevented.");
  1036. //debug(RulesLog::logger()->render());
  1037. }
  1038. /**
  1039. * Ensure the recursion prevention still allows to let the rule trigger again
  1040. * during evaluation of the same event set, if the event isn't caused by the
  1041. * rule itself - thus we won't run in an infinte loop.
  1042. */
  1043. function testRecursionOnDifferentArguments() {
  1044. // Create rule1 - which might recurse.
  1045. $rule = $this->createTestRule(FALSE, 'node_update');
  1046. $rule->action('rules_node_make_sticky_action');
  1047. $rule->label = 'rule 1';
  1048. $rule->integrityCheck()->save();
  1049. // Create rule2 - which triggers rule1 on another node.
  1050. $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1051. $rule2 = $this->createTestRule(FALSE, 'node_update');
  1052. $rule2->action('rules_action_load_node', array('nid' => $node2->nid))
  1053. ->action('rules_node_make_sticky_action', array('node:select' => 'node_loaded'));
  1054. $rule2->label = 'rule 2';
  1055. $rule2->save();
  1056. // Now trigger both rules by generating the event.
  1057. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1058. node_save($node);
  1059. //debug(RulesLog::logger()->render());
  1060. $text = RulesLog::logger()->render();
  1061. $pos = strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1')));
  1062. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 2', array('rule 2')), $pos) : FALSE;
  1063. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node')), $pos) : FALSE;
  1064. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1')), $pos) : FALSE;
  1065. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Not evaluating reaction rule %rule 2 to prevent recursion', array('rule 2')), $pos) : FALSE;
  1066. $this->assertTrue($pos !== FALSE, 'Rule1 was triggered on the event caused by Rule2.');
  1067. }
  1068. /**
  1069. * Tests the provided default rule 'rules_test_default_1'.
  1070. */
  1071. function testDefaultRule() {
  1072. $rule = rules_config_load('rules_test_default_1');
  1073. $this->assertTrue($rule->status & ENTITY_IN_CODE && !($rule->status & ENTITY_IN_DB), 'Default rule can be loaded and has the right status.');
  1074. // Enable.
  1075. $rule->active = TRUE;
  1076. $rule->save();
  1077. // Create a node that triggers the rule.
  1078. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1079. // Clear messages.
  1080. drupal_get_messages();
  1081. // Let event node_update occur.
  1082. node_save($node);
  1083. $msg = drupal_get_messages();
  1084. $this->assertEqual($msg['status'][0], 'A node has been updated.', 'Default rule has been triggered.');
  1085. }
  1086. }
  1087. /**
  1088. * Tests provided module integration.
  1089. */
  1090. class RulesIntegrationTestCase extends DrupalWebTestCase {
  1091. static function getInfo() {
  1092. return array(
  1093. 'name' => 'Rules Core Integration',
  1094. 'description' => 'Tests provided integration for drupal core.',
  1095. 'group' => 'Rules',
  1096. );
  1097. }
  1098. function setUp() {
  1099. parent::setUp('rules', 'rules_test', 'php', 'path');
  1100. RulesLog::logger()->clear();
  1101. variable_set('rules_debug_log', 1);
  1102. }
  1103. /**
  1104. * Just make sure the access callback run without errors.
  1105. */
  1106. function testAccessCallbacks() {
  1107. $cache = rules_get_cache();
  1108. foreach (array('action', 'condition', 'event') as $type) {
  1109. foreach (rules_fetch_data($type . '_info') as $name => $info) {
  1110. if (isset($info['access callback'])) {
  1111. $info['access callback']($type, $name);
  1112. }
  1113. }
  1114. }
  1115. }
  1116. /**
  1117. * Test data integration.
  1118. */
  1119. function testDataIntegration() {
  1120. // Test data_create action.
  1121. $action = rules_action('data_create', array(
  1122. 'type' => 'log_entry',
  1123. 'param_type' => 'rules_test',
  1124. 'param_message' => 'Rules test log message',
  1125. 'param_severity' => WATCHDOG_WARNING,
  1126. 'param_request_uri' => 'http://example.com',
  1127. 'param_link' => '',
  1128. ));
  1129. $action->access();
  1130. $action->execute();
  1131. $text = RulesLog::logger()->render();
  1132. $pos = strpos($text, RulesTestCase::t('Added the provided variable %data_created of type %log_entry', array('data_created', 'log_entry')));
  1133. $this->assertTrue($pos !== FALSE, 'Data of type log entry has been created.');
  1134. // Test variable_add action.
  1135. $action = rules_action('variable_add', array(
  1136. 'type' => 'text_formatted',
  1137. 'value' => array(
  1138. 'value' => 'test text',
  1139. 'format' => 1,
  1140. )
  1141. ));
  1142. $action->access();
  1143. $action->execute();
  1144. $text = RulesLog::logger()->render();
  1145. $pos = strpos($text, RulesTestCase::t('Added the provided variable %variable_added of type %text_formatted', array('variable_added', 'text_formatted')));
  1146. $this->assertTrue($pos !== FALSE, 'Data of type text formatted has been created.');
  1147. // Test using the list actions.
  1148. $rule = rule(array(
  1149. 'list' => array(
  1150. 'type' => 'list<text>',
  1151. 'label' => 'A list of text',
  1152. )
  1153. ));
  1154. $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar2'));
  1155. $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'pos' => 'start'));
  1156. $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'unique' => TRUE));
  1157. $rule->action('list_remove', array('list:select' => 'list', 'item' => 'bar2'));
  1158. $list = entity_metadata_wrapper('list', array('foo', 'foo2'));
  1159. $rule->execute($list);
  1160. RulesLog::logger()->checkLog();
  1161. $this->assertEqual($list->value(), array('bar', 'foo', 'foo2'), 'List items removed and added.');
  1162. $this->assertFalse(rules_condition('list_contains')->execute($list, 'foo-bar'), 'Condition "List item contains" evaluates to FALSE');
  1163. $this->assertTrue(rules_condition('list_contains')->execute($list, 'foo'), 'Condition "List item contains" evaluates to TRUE');
  1164. //debug(RulesLog::logger()->render());
  1165. // Test data_is condition with IN operation.
  1166. $rule = rule(array('node' => array('type' => 'node')));
  1167. $rule->condition('data_is', array('data:select' => 'node:title', 'op' => 'IN', 'value' => array('foo', 'bar')));
  1168. $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar'));
  1169. $rule->integrityCheck();
  1170. $node = $this->drupalCreateNode(array('title' => 'foo'));
  1171. $rule->execute($node);
  1172. $this->assertEqual($node->title, 'bar', "Data comparision using IN operation evaluates to TRUE.");
  1173. // Test Condition: Data is empty.
  1174. $rule = rule(array('node' => array('type' => 'node')));
  1175. $rule->condition('data_is_empty', array('data:select' => 'node:title'));
  1176. $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar'));
  1177. $rule->integrityCheck();
  1178. // Data is empty condition evaluates to TRUE
  1179. // for node with empty title, action sets title to 'bar'.
  1180. $node = $this->drupalCreateNode(array('title' => '', 'type' => 'article'));
  1181. $rule->execute($node);
  1182. $this->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for node with empty title, action sets title to 'bar'.");
  1183. // Data is empty condition evaluates to FALSE
  1184. // for node with title 'foo', action is not executed.
  1185. $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article'));
  1186. $rule->execute($node);
  1187. $this->assertEqual($node->title, 'foo', "Data is empty condition evaluates to FALSE for node with title 'foo', action is not executed.");
  1188. // Data is empty condition evaluates to TRUE for the parent of a
  1189. // not existing term in the tags field of the node.
  1190. $rule = rule(array('node' => array('type' => 'node')));
  1191. $rule->condition('node_is_of_type', array('type' => array('article')));
  1192. $rule->condition('data_is_empty', array('data:select' => 'node:field-tags:0:parent'));
  1193. $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar'));
  1194. $rule->integrityCheck();
  1195. $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article'));
  1196. $rule->execute($node);
  1197. $this->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for not existing data structures");
  1198. // Test Action: Calculate a value.
  1199. $rule = rule(array('node' => array('type' => 'node')));
  1200. $rule->action('data_calc', array('input_1:select' => 'node:nid', 'op' => '*', 'input_2' => 2));
  1201. $rule->action('data_set', array('data:select' => 'node:title', 'value:select' => 'result'));
  1202. $rule->integrityCheck();
  1203. $rule->execute($node);
  1204. $this->assertEqual($node->title, $node->nid * 2, "Value has been calculated.");
  1205. // Test moving a date.
  1206. $action_set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
  1207. $action_set->action('data_calc', array('input_1:select' => 'date', 'op' => '+', 'input_2' => 3600))
  1208. ->action('data_set', array('data:select' => 'date', 'value:select' => 'result'));
  1209. $action_set->integrityCheck();
  1210. list($result) = $action_set->execute(REQUEST_TIME);
  1211. $this->assertEqual($result, REQUEST_TIME + 3600, 'Used data calculation action to move a date by an hour.');
  1212. // Test data type conversion action.
  1213. $set = rules_action_set(array('result' => array('type' => 'text', 'parameter' => FALSE)), array('result'));
  1214. $set->action('data_convert', array('type' => 'text', 'value:select' => 'site:login-url'));
  1215. $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
  1216. list($result) = $set->execute();
  1217. $set->integrityCheck();
  1218. $this->assertEqual($result, url('user', array('absolute' => TRUE)), 'Converted URI to text.');
  1219. $set = rules_action_set(array(
  1220. 'result' => array('type' => 'integer', 'parameter' => FALSE),
  1221. 'source' => array('type' => 'text'),
  1222. ), array('result'));
  1223. $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source'));
  1224. $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
  1225. list($result) = $set->execute('9.4');
  1226. $this->assertEqual($result, 9, 'Converted decimal to integer using rounding.');
  1227. $set = rules_action_set(array(
  1228. 'result' => array('type' => 'integer', 'parameter' => FALSE),
  1229. 'source' => array('type' => 'text'),
  1230. ), array('result'));
  1231. $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'down'));
  1232. $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
  1233. list($result) = $set->execute('9.6');
  1234. $this->assertEqual($result, 9, 'Converted decimal to integer using roundin behavio down.');
  1235. $set = rules_action_set(array(
  1236. 'result' => array('type' => 'integer', 'parameter' => FALSE),
  1237. 'source' => array('type' => 'text'),
  1238. ), array('result'));
  1239. $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'up'));
  1240. $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
  1241. list($result) = $set->execute('9.4');
  1242. $this->assertEqual($result, 10, 'Converted decimal to integer using rounding behavior up.');
  1243. // Test text matching condition.
  1244. $result = rules_condition('text_matches')->execute('my-text', 'text', 'contains');
  1245. $result2 = rules_condition('text_matches')->execute('my-text', 'tex2t', 'contains');
  1246. $this->assertTrue($result && !$result2, 'Text matching condition using operation contain evaluated.');
  1247. $result = rules_condition('text_matches')->execute('my-text', 'my', 'starts');
  1248. $result2 = rules_condition('text_matches')->execute('my-text', 'text', 'starts');
  1249. $this->assertTrue($result && !$result2, 'Text matching condition using operation starts evaluated.');
  1250. $result = rules_condition('text_matches')->execute('my-text', 'text', 'ends');
  1251. $result2 = rules_condition('text_matches')->execute('my-text', 'my', 'ends');
  1252. $this->assertTrue($result && !$result2, 'Text matching condition using operation ends evaluated.');
  1253. $result = rules_condition('text_matches')->execute('my-text', 'me?y-texx?t', 'regex');
  1254. $result2 = rules_condition('text_matches')->execute('my-text', 'me+y-texx?t', 'regex');
  1255. $this->assertTrue($result && !$result2, 'Text matching condition using operation regex evaluated.');
  1256. }
  1257. /**
  1258. * Tests entity related integration.
  1259. */
  1260. function testEntityIntegration() {
  1261. global $user;
  1262. $page = $this->drupalCreateNode(array('type' => 'page'));
  1263. $article = $this->drupalCreateNode(array('type' => 'article'));
  1264. $result = rules_condition('entity_field_access')
  1265. ->execute(entity_metadata_wrapper('node', $article), 'field_tags');
  1266. $this->assertTrue($result);
  1267. // Test entiy_is_of_bundle condition.
  1268. $result = rules_condition('entity_is_of_bundle', array(
  1269. 'type' => 'node',
  1270. 'bundle' => array('article'),
  1271. ))->execute(entity_metadata_wrapper('node', $page));
  1272. $this->assertFalse($result, 'Entity is of bundle condition has not been met.');
  1273. $result = rules_condition('entity_is_of_bundle', array(
  1274. 'type' => 'node',
  1275. 'bundle' => array('article'),
  1276. ))->execute(entity_metadata_wrapper('node', $article));
  1277. $this->assertTrue($result, 'Entity is of bundle condition has been met.');
  1278. // Also test a full rule so the integrity check must work.
  1279. $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
  1280. 'name' => $this->randomName(),
  1281. 'vocabulary' => 1,
  1282. ))->save();
  1283. $rule = rule(array(
  1284. 'node' => array('type' => 'node'),
  1285. ));
  1286. $rule->condition('entity_is_of_bundle', array(
  1287. 'entity:select' => 'node',
  1288. 'bundle' => array('article'),
  1289. ));
  1290. $rule->action('data_set', array('data:select' => 'node:field_tags', 'value' => array($term_wrapped->getIdentifier())));
  1291. $rule->integrityCheck();
  1292. $rule->execute($article);
  1293. $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
  1294. // Test again using an entity variable.
  1295. $article = $this->drupalCreateNode(array('type' => 'article'));
  1296. $rule = rule(array(
  1297. 'entity' => array('type' => 'entity'),
  1298. ));
  1299. $rule->condition('entity_is_of_bundle', array(
  1300. 'entity:select' => 'entity',
  1301. 'type' => 'node',
  1302. 'bundle' => array('article'),
  1303. ));
  1304. $rule->action('data_set', array('data:select' => 'entity:field_tags', 'value' => array($term_wrapped->getIdentifier())));
  1305. $rule->integrityCheck();
  1306. $rule->execute(entity_metadata_wrapper('node', $article));
  1307. $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
  1308. // Test CRUD actions.
  1309. $action = rules_action('entity_create', array(
  1310. 'type' => 'node',
  1311. 'param_type' => 'page',
  1312. 'param_title' => 'foo',
  1313. 'param_author' => $GLOBALS['user'],
  1314. ));
  1315. $action->access();
  1316. $action->execute();
  1317. $text = RulesLog::logger()->render();
  1318. $pos = strpos($text, RulesTestCase::t('Added the provided variable %entity_created of type %node', array('entity_created', 'node')));
  1319. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %entity_created of type %node.', array('entity_created', 'node')), $pos) : FALSE;
  1320. $this->assertTrue($pos !== FALSE, 'Data has been created and saved.');
  1321. $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
  1322. $rule = rule();
  1323. $rule->action('entity_fetch', array('type' => 'node', 'id' => $node->nid, 'entity_fetched:var' => 'node'));
  1324. $rule->action('entity_save', array('data:select' => 'node', 'immediate' => TRUE));
  1325. $rule->action('entity_delete', array('data:select' => 'node'));
  1326. $rule->access();
  1327. $rule->integrityCheck()->execute();
  1328. $text = RulesLog::logger()->render();
  1329. $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch')));
  1330. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Added the provided variable %node of type %node', array('node')), $pos) : FALSE;
  1331. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node of type %node.', array('node')), $pos) : FALSE;
  1332. $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE;
  1333. $this->assertTrue($pos !== FALSE, 'Data has been fetched, saved and deleted.');
  1334. //debug(RulesLog::logger()->render());
  1335. $node = entity_property_values_create_entity('node', array(
  1336. 'type' => 'article',
  1337. 'author' => $user,
  1338. 'title' => 'foo',
  1339. ))->value();
  1340. $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
  1341. 'name' => $this->randomName(),
  1342. 'vocabulary' => 1,
  1343. ))->save();
  1344. // Test asserting the field and using it afterwards.
  1345. $rule = rule(array('node' => array('type' => 'node')));
  1346. $rule->condition('entity_has_field', array('entity:select' => 'node', 'field' => 'field_tags'));
  1347. $rule->condition('entity_is_new', array('entity:select' => 'node'));
  1348. $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped));
  1349. $rule->integrityCheck();
  1350. $rule->execute($node);
  1351. $tid = $term_wrapped->getIdentifier();
  1352. $this->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(0 => array('tid' => $tid)), 'Entity has field conditions evaluted.');
  1353. // Test loading a non-node entity.
  1354. $action = rules_action('entity_fetch', array('type' => 'taxonomy_term', 'id' => $tid));
  1355. list($term) = $action->execute();
  1356. $this->assertEqual($term->tid, $tid, 'Fetched a taxonomy term using "entity_fetch".');
  1357. // Test the entity is of type condition.
  1358. $rule = rule(array('entity' => array('type' => 'entity', 'label' => 'entity')));
  1359. $rule->condition('entity_is_of_type', array('type' => 'node'));
  1360. $rule->action('data_set', array('data:select' => 'entity:title', 'value' => 'bar'));
  1361. $rule->integrityCheck();
  1362. $rule->execute(entity_metadata_wrapper('node', $node));
  1363. $this->assertEqual(entity_metadata_wrapper('node', $node->nid)->title->value(), 'bar', 'Entity is of type condition correctly asserts the entity type.');
  1364. // Test the entity_query action.
  1365. $node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'foo2'));
  1366. $rule = rule();
  1367. $rule->action('entity_query', array('type' => 'node', 'property' => 'title', 'value' => 'foo2'))
  1368. ->action('data_set', array('data:select' => 'entity_fetched:0:title', 'value' => 'bar'));
  1369. $rule->access();
  1370. $rule->integrityCheck();
  1371. $rule->execute();
  1372. $node = node_load($node->nid);
  1373. $this->assertEqual('bar', $node->title, 'Fetched a node by title and modified it.');
  1374. RulesLog::logger()->checkLog();
  1375. }
  1376. /**
  1377. * Test integration for the taxonomy module.
  1378. */
  1379. function testTaxonomyIntegration() {
  1380. $term = entity_property_values_create_entity('taxonomy_term', array(
  1381. 'name' => $this->randomName(),
  1382. 'vocabulary' => 1,
  1383. ))->value();
  1384. $term2 = clone $term;
  1385. taxonomy_term_save($term);
  1386. taxonomy_term_save($term2);
  1387. $tags[LANGUAGE_NONE][0]['tid'] = $term->tid;
  1388. $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $tags));
  1389. // Test assigning and remove a term from an article.
  1390. $rule = rule(array('node' => array('type' => 'node', 'bundle' => 'article')));
  1391. $term_wrapped = rules_wrap_data($term->tid, array('type' => 'taxonomy_term'));
  1392. $term_wrapped2 = rules_wrap_data($term2->tid, array('type' => 'taxonomy_term'));
  1393. $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped2));
  1394. $rule->action('list_remove', array('list:select' => 'node:field-tags', 'item' => $term_wrapped));
  1395. $rule->execute($node);
  1396. RulesLog::logger()->checkLog();
  1397. $this->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(0 => array('tid' => $term2->tid)), 'Term removed and added from a node.');
  1398. // Test using the taxonomy term reference field on a term object.
  1399. $field_name = drupal_strtolower($this->randomName() . '_field_name');
  1400. $field = field_create_field(array(
  1401. 'field_name' => $field_name,
  1402. 'type' => 'taxonomy_term_reference',
  1403. // Set cardinality to unlimited for tagging.
  1404. 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
  1405. 'settings' => array(
  1406. 'allowed_values' => array(
  1407. array(
  1408. 'vocabulary' => 'tags',
  1409. 'parent' => 0,
  1410. ),
  1411. ),
  1412. ),
  1413. ));
  1414. $instance = array(
  1415. 'field_name' => $field_name,
  1416. 'entity_type' => 'taxonomy_term',
  1417. 'bundle' => 'tags', // Machine name of vocabulary.
  1418. 'label' => $this->randomName() . '_label',
  1419. 'description' => $this->randomName() . '_description',
  1420. 'weight' => mt_rand(0, 127),
  1421. 'widget' => array(
  1422. 'type' => 'taxonomy_autocomplete',
  1423. 'weight' => -4,
  1424. ),
  1425. 'display' => array(
  1426. 'default' => array(
  1427. 'type' => 'taxonomy_term_reference_link',
  1428. 'weight' => 10,
  1429. ),
  1430. ),
  1431. );
  1432. field_create_instance($instance);
  1433. $term1 = entity_property_values_create_entity('taxonomy_term', array(
  1434. 'name' => $this->randomName(),
  1435. 'vocabulary' => 1,
  1436. ))->save();
  1437. $term2 = entity_property_values_create_entity('taxonomy_term', array(
  1438. 'name' => $this->randomName(),
  1439. 'vocabulary' => 1,
  1440. ))->save();
  1441. // Test asserting the term reference field and using it afterwards.
  1442. $rule = rule(array('taxonomy_term' => array('type' => 'taxonomy_term')));
  1443. $rule->condition('entity_has_field', array('entity:select' => 'taxonomy-term', 'field' => $field_name));
  1444. // Add $term2 to $term1 using the term reference field.
  1445. $selector = str_replace('_', '-', 'taxonomy_term:' . $field_name);
  1446. $rule->action('list_add', array('list:select' => $selector, 'item' => $term2));
  1447. $rule->integrityCheck();
  1448. $rule->execute($term1);
  1449. RulesLog::logger()->checkLog();
  1450. $this->assertEqual($term1->{$field_name}[0]->getIdentifier(), $term2->getIdentifier(), 'Rule appended a term to the term reference field on a term.');
  1451. // Test an action set for merging term parents, which is provided as default
  1452. // config.
  1453. $term = entity_property_values_create_entity('taxonomy_term', array(
  1454. 'name' => $this->randomName(),
  1455. 'vocabulary' => 1,
  1456. 'parent' => array($term1->value()),
  1457. ))->save();
  1458. $action = rules_action('component_rules_retrieve_term_parents');
  1459. list($parents) = $action->execute(array($term->getIdentifier()));
  1460. $this->assertTrue($parents[0]->tid == $term1->getIdentifier(), 'Invoked component to retrieve term parents.');
  1461. RulesLog::logger()->checkLog();
  1462. }
  1463. /**
  1464. * Test integration for the node module.
  1465. */
  1466. function testNodeIntegration() {
  1467. $tests = array(
  1468. array('node_unpublish', 'node_is_published', 'node_publish', 'status'),
  1469. array('node_make_unsticky', 'node_is_sticky', 'node_make_sticky', 'sticky'),
  1470. array('node_unpromote', 'node_is_promoted', 'node_promote', 'promote'),
  1471. );
  1472. $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 1, 'sticky' => 1, 'promote' => 1));
  1473. foreach ($tests as $info) {
  1474. list($action1, $condition, $action2, $property) = $info;
  1475. rules_action($action1)->execute($node);
  1476. $node = node_load($node->nid, NULL, TRUE);
  1477. $this->assertFalse($node->$property, 'Action has permanently disabled node '. $property);
  1478. $return = rules_condition($condition)->execute($node);
  1479. $this->assertFalse($return, 'Condition determines node '. $property . ' is disabled.');
  1480. rules_action($action2)->execute($node);
  1481. $node = node_load($node->nid, NULL, TRUE);
  1482. $this->assertTrue($node->$property, 'Action has permanently enabled node '. $property);
  1483. $return = rules_condition($condition)->execute($node);
  1484. $this->assertTrue($return, 'Condition determines node '. $property . ' is enabled.');
  1485. }
  1486. $return = rules_condition('node_is_of_type', array('type' => array('page', 'article')))->execute($node);
  1487. $this->assertTrue($return, 'Condition determines node is of type page.');
  1488. $return = rules_condition('node_is_of_type', array('type' => array('article')))->execute($node);
  1489. $this->assertFalse($return, 'Condition determines node is not of type article.');
  1490. // Test auto saving of a new node after it has been inserted into the DB.
  1491. $rule = rules_reaction_rule();
  1492. $rand = $this->randomName();
  1493. $rule->event('node_insert')
  1494. ->action('data_set', array('data:select' => 'node:title', 'value' => $rand));
  1495. $rule->save('test');
  1496. $node = $this->drupalCreateNode();
  1497. $node = node_load($node->nid);
  1498. $this->assertEqual($node->title, $rand, 'Node title is correct.');
  1499. RulesLog::logger()->checkLog();
  1500. }
  1501. /**
  1502. * Test integration for the user module.
  1503. */
  1504. function testUserIntegration() {
  1505. $rid = $this->drupalCreateRole(array('administer nodes'), 'foo');
  1506. $user = $this->drupalCreateUser();
  1507. // Test assigning a role with the list_add action.
  1508. $rule = rule(array('user' => array('type' => 'user')));
  1509. $rule->action('list_add', array('list:select' => 'user:roles', 'item' => $rid));
  1510. $rule->execute($user);
  1511. $this->assertTrue(isset($user->roles[$rid]), 'Role assigned to user.');
  1512. // Test removing a role with the list_remove action.
  1513. $rule = rule(array('user' => array('type' => 'user')));
  1514. $rule->action('list_remove', array('list:select' => 'user:roles', 'item' => $rid));
  1515. $rule->execute($user);
  1516. $this->assertTrue(!isset($user->roles[$rid]), 'Role removed from user.');
  1517. // Test assigning a role with user_add_role action.
  1518. $rule = rule(array('user' => array('type' => 'user')));
  1519. $rule->action('user_add_role', array('account:select' => 'user', 'roles' => array($rid)));
  1520. $rule->execute($user);
  1521. $user = user_load($user->uid, TRUE);
  1522. $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
  1523. $this->assertTrue($result, 'Role assigned to user.');
  1524. // Test removing a role with the user_remove_role action.
  1525. $rule = rule(array('user' => array('type' => 'user')));
  1526. $rule->action('user_remove_role', array('account:select' => 'user', 'roles' => array($rid)));
  1527. $rule->execute($user);
  1528. $user = user_load($user->uid, TRUE);
  1529. $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
  1530. $this->assertFalse($result, 'Role removed from user.');
  1531. // Test user blocking.
  1532. rules_action('user_block')->execute($user);
  1533. $user = user_load($user->uid, TRUE);
  1534. $this->assertTrue(rules_condition('user_is_blocked')->execute($user), 'User has been blocked.');
  1535. rules_action('user_unblock')->execute($user);
  1536. $user = user_load($user->uid, TRUE);
  1537. $this->assertFalse(rules_condition('user_is_blocked')->execute($user), 'User has been unblocked.');
  1538. RulesLog::logger()->checkLog();
  1539. }
  1540. /**
  1541. * Test integration for the php module.
  1542. */
  1543. function testPHPIntegration() {
  1544. $node = $this->drupalCreateNode(array('title' => 'foo'));
  1545. $rule = rule(array('var_name' => array('type' => 'node')));
  1546. $rule->condition('php_eval', array('code' => 'return TRUE;'))
  1547. ->action('php_eval', array('code' => 'drupal_set_message("Executed-" . $var_name->title);'))
  1548. ->action('drupal_message', array('message' => 'Title: <?php echo $var_name->title; ?> Token: [var_name:title]'));
  1549. $rule->execute($node);
  1550. $rule->access();
  1551. RulesLog::logger()->checkLog();
  1552. $msg = drupal_get_messages();
  1553. $this->assertEqual(array_pop($msg['status']), "Title: foo Token: foo", 'PHP input evaluation has been applied.');
  1554. $this->assertEqual(array_pop($msg['status']), "Executed-foo", 'PHP code condition and action have been evaluated.');
  1555. // Test PHP data processor
  1556. $rule = rule(array('var_name' => array('type' => 'node')));
  1557. $rule->action('drupal_message', array(
  1558. 'message:select' => 'var_name:title',
  1559. 'message:process' => array(
  1560. 'php' => array('code' => 'return "Title: $value";')
  1561. ),
  1562. ));
  1563. $rule->execute($node);
  1564. $rule->access();
  1565. RulesLog::logger()->checkLog();
  1566. $msg = drupal_get_messages();
  1567. $this->assertEqual(array_pop($msg['status']), "Title: foo", 'PHP data processor has been applied.');
  1568. }
  1569. /**
  1570. * Test the "rules_core" integration.
  1571. */
  1572. function testRulesCoreIntegration() {
  1573. // Make sure the date input evaluator evaluates properly using strtotime().
  1574. $node = $this->drupalCreateNode(array('title' => 'foo'));
  1575. $rule = rule(array('node' => array('type' => 'node')));
  1576. $rule->action('data_set', array('data:select' => 'node:created', 'value' => '+1 day'));
  1577. $rule->execute($node);
  1578. RulesLog::logger()->checkLog();
  1579. $node = node_load($node->nid, NULL, TRUE);
  1580. $now = RulesDateInputEvaluator::gmstrtotime('now');
  1581. // Tolerate a difference of a second.
  1582. $this->assertTrue(abs($node->created - $now - 86400) <= 1, 'Date input has been evaluated.');
  1583. // Test using a numeric offset.
  1584. $rule = rule(array('number' => array('type' => 'decimal')), array('number'));
  1585. $rule->action('data_set', array(
  1586. 'data:select' => 'number',
  1587. 'value:select' => 'number',
  1588. 'value:process' => array(
  1589. 'num_offset' => array('value' => 1),
  1590. ),
  1591. ));
  1592. $rule->integrityCheck();
  1593. list($result) = $rule->execute(10);
  1594. $this->assertTrue($result == 11, 'Numeric offset has been applied');
  1595. // Test using a date offset.
  1596. $set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
  1597. $set->action('data_set', array(
  1598. 'data:select' => 'date',
  1599. 'value:select' => 'date',
  1600. 'value:process' => array(
  1601. 'date_offset' => array('value' => 1000),
  1602. ),
  1603. ));
  1604. $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U');
  1605. list($result) = $set->execute($date);
  1606. $this->assertEqual($result, $date + 1000, 'Date offset in seconds has been added.');
  1607. // Test using a negative offset of 2 months.
  1608. $set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
  1609. $set->action('data_set', array(
  1610. 'data:select' => 'date',
  1611. 'value:select' => 'date',
  1612. 'value:process' => array(
  1613. 'date_offset' => array('value' => - 86400 * 30 * 2),
  1614. ),
  1615. ));
  1616. $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U');
  1617. list($result) = $set->execute($date);
  1618. $this->assertEqual($result, date_create("14 Jan 1984 10:19:23 +01:00")->format('U'), 'Date offset of -2 months has been added.');
  1619. // Test using a positive offset of 1 year 6 months and 30 minutes.
  1620. $set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
  1621. $set->action('data_set', array(
  1622. 'data:select' => 'date',
  1623. 'value:select' => 'date',
  1624. 'value:process' => array(
  1625. 'date_offset' => array('value' => 86400 * 30 * 18 + 30 * 60),
  1626. ),
  1627. ));
  1628. $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U');
  1629. list($result) = $set->execute($date);
  1630. $this->assertEqual($result, date_create("14 Sep 1985 10:49:23 +01:00")->format('U'), 'Date offset of 1 year 6 months and 30 minutes has been added.');
  1631. RulesLog::logger()->checkLog();
  1632. }
  1633. /**
  1634. * Test site/system integration.
  1635. */
  1636. function testSystemIntegration() {
  1637. // Test using the 'site' variable.
  1638. $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => $GLOBALS['user']->name));
  1639. $this->assertTrue($condition->execute(), 'Retrieved the current user\'s name.');
  1640. // Another test using a token replacement.
  1641. $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => '[site:current-user:name]'));
  1642. $this->assertTrue($condition->execute(), 'Replaced the token for the current user\'s name.');
  1643. // Test breadcrumbs and drupal set message.
  1644. $rule = rules_reaction_rule();
  1645. $rule->event('init')
  1646. ->action('breadcrumb_set', array('titles' => array('foo'), 'paths' => array('bar')))
  1647. ->action('drupal_message', array('message' => 'A message.'));
  1648. $rule->save('test');
  1649. $this->drupalGet('node');
  1650. $this->assertLink('foo', 0, 'Breadcrumb has been set.');
  1651. $this->assertText('A message.', 'Drupal message has been shown.');
  1652. // Test the page redirect.
  1653. $node = $this->drupalCreateNode();
  1654. $rule = rules_reaction_rule();
  1655. $rule->event('node_view')
  1656. ->action('redirect', array('url' => 'user'));
  1657. $rule->save('test2');
  1658. $this->drupalGet('node/' . $node->nid);
  1659. $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE)), 'Redirect has been issued.');
  1660. // Also test using a url including a fragment.
  1661. $actions = $rule->actions();
  1662. $actions[0]->settings['url'] = 'user#fragment';
  1663. $rule->save();
  1664. $this->drupalGet('node/' . $node->nid);
  1665. $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE, 'fragment' => 'fragment')), 'Redirect has been issued.');
  1666. // Test sending mail.
  1667. $settings = array('to' => 'mail@example.com', 'subject' => 'subject', 'message' => 'hello.');
  1668. rules_action('mail', $settings)->execute();
  1669. $this->assertMail('to', 'mail@example.com', 'Mail has been sent.');
  1670. $this->assertMail('from', variable_get('site_mail', ini_get('sendmail_from')), 'Default from address has been used');
  1671. rules_action('mail', $settings + array('from' => 'sender@example.com'))->execute();
  1672. $this->assertMail('from', 'sender@example.com', 'Specified from address has been used');
  1673. // Test sending mail to all users of a role. First make sure there is a
  1674. // custom role and a user for it.
  1675. $user = $this->drupalCreateUser(array('administer nodes'));
  1676. $roles = $user->roles;
  1677. // Remove the authenticate role so we only use the new role created by
  1678. // drupalCreateUser().
  1679. unset($roles[DRUPAL_AUTHENTICATED_RID]);
  1680. rules_action('mail_to_users_of_role', $settings + array('roles' => array_keys($roles)))->execute();
  1681. $this->assertMail('to', $user->mail, 'Mail to users of a role has been sent.');
  1682. // Test reacting on new log entries and make sure the log entry is usable.
  1683. $rule = rules_reaction_rule();
  1684. $rule->event('watchdog');
  1685. $rule->action('drupal_message', array('message:select' => 'log_entry:message'));
  1686. $rule->integrityCheck()->save('test_watchdog');
  1687. watchdog('php', 'test %message', array('%message' => 'message'));
  1688. $msg = drupal_get_messages();
  1689. $this->assertEqual(array_pop($msg['status']), t('test %message', array('%message' => 'message')), 'Watchdog event occurred and log entry properties can be used.');
  1690. }
  1691. /**
  1692. * Tests the path module integration.
  1693. */
  1694. function testPathIntegration() {
  1695. rules_action('path_alias')->execute('foo', 'bar');
  1696. $path = path_load('foo');
  1697. $this->assertTrue($path['alias'] == 'bar', 'URL alias has been created.');
  1698. $alias_exists = rules_condition('path_alias_exists', array('alias' => 'bar'))->execute();
  1699. $this->assertTrue($alias_exists, 'Created URL alias exists.');
  1700. $has_alias = rules_condition('path_has_alias', array('source' => 'foo'))->execute();
  1701. $this->assertTrue($has_alias, 'System path has an alias.');
  1702. // Test node alias action.
  1703. $node = $this->drupalCreateNode();
  1704. rules_action('node_path_alias')->execute($node, 'test');
  1705. $path = path_load("node/$node->nid");
  1706. $this->assertTrue($path['alias'] == 'test', 'Node URL alias has been created.');
  1707. // Test term alias action.
  1708. $term = entity_property_values_create_entity('taxonomy_term', array(
  1709. 'name' => $this->randomName(),
  1710. 'vocabulary' => 1,
  1711. ))->value();
  1712. rules_action('taxonomy_term_path_alias')->execute($term, 'term-test');
  1713. $path = path_load("taxonomy/term/$term->tid");
  1714. $this->assertTrue($path['alias'] == 'term-test', 'Term URL alias has been created.');
  1715. RulesLog::logger()->checkLog();
  1716. }
  1717. }