rules.test 91 KB

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