term_merge.test 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548
  1. <?php
  2. /**
  3. * @file
  4. * Test the Term Merge module.
  5. */
  6. /**
  7. * Base class for all tests of Term Merge module.
  8. */
  9. class TermMergeWebTestCase extends DrupalWebTestCase {
  10. /**
  11. * Fully loaded Drupal user who has access to all required parts of the
  12. * website for testing.
  13. *
  14. * @var object
  15. */
  16. protected $admin;
  17. /**
  18. * Fully loaded Drupal taxonomy vocabulary object on which all tests are run.
  19. *
  20. * @var object
  21. */
  22. protected $vocabulary;
  23. /**
  24. * SetUp method.
  25. *
  26. * @param array $modules
  27. * Array of modules that need to be enabled for test case
  28. */
  29. public function setUp(array $modules = array()) {
  30. $modules[] = 'term_merge';
  31. parent::setUp($modules);
  32. $this->admin = $this->drupalCreateUser(array(
  33. 'administer taxonomy',
  34. 'merge terms',
  35. 'administer content types',
  36. 'bypass node access',
  37. ));
  38. // Creating vocabularies.
  39. $this->drupalLogin($this->admin);
  40. $name = $this->randomName();
  41. $this->drupalPost('admin/structure/taxonomy/add', array(
  42. 'name' => $name,
  43. 'machine_name' => 'vocabulary',
  44. 'description' => $this->randomName(),
  45. ), 'Save');
  46. $this->vocabulary = taxonomy_vocabulary_machine_name_load('vocabulary');
  47. // Flushing static cache.
  48. _field_info_collate_fields(TRUE);
  49. }
  50. /**
  51. * Return last inserted term into the specified vocabulary.
  52. *
  53. * @param object $vocabulary
  54. * Fully loaded taxonomy vocabulary object
  55. *
  56. * @return object
  57. * Fully loaded taxonomy term object of the last inserted term into
  58. * the specified vocabulary
  59. */
  60. protected function getLastTerm($vocabulary) {
  61. drupal_static_reset();
  62. $tree = taxonomy_get_tree($vocabulary->vid);
  63. $max = 0;
  64. $term = NULL;
  65. foreach ($tree as $v) {
  66. if ($v->tid > $max) {
  67. $max = $v->tid;
  68. $term = $v;
  69. }
  70. }
  71. $term = entity_load_unchanged('taxonomy_term', $term->tid);
  72. return $term;
  73. }
  74. }
  75. /**
  76. * Test the functionality of Term Merge module.
  77. */
  78. class TermMergeTermMergeWebTestCase extends TermMergeWebTestCase {
  79. /**
  80. * GetInfo method.
  81. */
  82. public function getInfo() {
  83. return array(
  84. 'name' => 'Term Merge',
  85. 'description' => 'Ensure that the module Term Merge works correctly.',
  86. 'group' => 'Term Merge',
  87. );
  88. }
  89. /**
  90. * Test merging two terms.
  91. */
  92. public function testTermMerge() {
  93. // Checking whether parent's relationship is handled as it should.
  94. // At the same time we make sure 'term_branch_keep' property functions.
  95. $terms = array(
  96. 'trunk' => FALSE,
  97. 'branch' => FALSE,
  98. 'another_parent' => FALSE,
  99. 'branch_child' => FALSE,
  100. );
  101. foreach ($terms as $term_type => $tmp) {
  102. $url = 'admin/structure/taxonomy/vocabulary/add';
  103. $name = $this->randomName();
  104. $edit = array(
  105. 'name' => $name,
  106. );
  107. // Putting "branch" to be parent of "branch_child".
  108. if ($term_type == 'branch_child') {
  109. $edit['parent[]'] = array($terms['branch']->tid, $terms['another_parent']->tid);
  110. }
  111. $this->drupalPost($url, $edit, 'Save');
  112. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  113. }
  114. // Firstly we try to merge without deleting the branch term and make sure
  115. // branch's children are not reassigned to the trunk term nor the branch
  116. // term itself is deleted.
  117. actions_do('term_merge_action', $terms['branch'], array(
  118. 'term_trunk' => $terms['trunk']->tid,
  119. 'merge_fields' => array(),
  120. 'term_branch_keep' => TRUE,
  121. ));
  122. $this->drupalGet('taxonomy/term/' . $terms['branch']->tid);
  123. $this->assertText($terms['branch']->name);
  124. drupal_static_reset();
  125. $parents = array();
  126. foreach (taxonomy_get_parents_all($terms['branch_child']->tid) as $parent) {
  127. $parents[] = $parent->tid;
  128. }
  129. $valid_parents = array(
  130. $terms['branch_child']->tid,
  131. $terms['branch']->tid,
  132. $terms['another_parent']->tid,
  133. );
  134. $intersection = array_intersect($parents, $valid_parents);
  135. $this->assertTrue(count($intersection) == count($valid_parents), 'The parents of children of term branch are not updated if property "term_branch_keep" is set to FALSE.');
  136. // Now we merge with deletion of branch term, thus the parents of its
  137. // children have to be updated.
  138. actions_do('term_merge_action', $terms['branch'], array(
  139. 'term_trunk' => $terms['trunk']->tid,
  140. 'merge_fields' => array(),
  141. 'term_branch_keep' => FALSE,
  142. ));
  143. $this->drupalGet('taxonomy/term/' . $terms['branch']->tid);
  144. $this->assertResponse(404, 'The branch term has been deleted.');
  145. drupal_static_reset();
  146. $parents = array();
  147. foreach (taxonomy_get_parents_all($terms['branch_child']->tid) as $parent) {
  148. $parents[] = $parent->tid;
  149. }
  150. $valid_parents = array(
  151. $terms['branch_child']->tid,
  152. $terms['trunk']->tid,
  153. $terms['another_parent']->tid,
  154. );
  155. $intersection = array_intersect($parents, $valid_parents);
  156. $this->assertTrue(count($intersection) == count($valid_parents), 'The parents of children of term branch are updated if property "term_branch_keep" is set to TRUE.');
  157. // Now testing 'merge_fields' property. Attaching fields to taxonomy terms.
  158. $bundle = field_extract_bundle('taxonomy_term', $this->vocabulary);
  159. $fields_map = array(
  160. 'term_merge_test_single' => 1,
  161. 'term_merge_test_unlimited' => FIELD_CARDINALITY_UNLIMITED,
  162. 'term_merge_do_not_merge' => 10,
  163. );
  164. foreach ($fields_map as $field_name => $cardinality) {
  165. $field = array(
  166. 'field_name' => $field_name,
  167. 'cardinality' => $cardinality,
  168. 'locked' => TRUE,
  169. 'type' => 'text',
  170. );
  171. field_create_field($field);
  172. field_create_instance(array(
  173. 'field_name' => $field_name,
  174. 'entity_type' => 'taxonomy_term',
  175. 'bundle' => $bundle,
  176. 'label' => $this->randomName(),
  177. 'description' => $this->randomName(),
  178. ));
  179. }
  180. $terms = array(
  181. 'trunk' => FALSE,
  182. 'branch' => FALSE,
  183. );
  184. foreach ($terms as $term_type => $tmp) {
  185. $url = 'admin/structure/taxonomy/vocabulary/add';
  186. $name = $this->randomName();
  187. $edit = array(
  188. 'name' => $name,
  189. );
  190. foreach ($fields_map as $field_name => $cardinality) {
  191. switch ($field_name) {
  192. case 'term_merge_test_single':
  193. $edit[$field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
  194. break;
  195. case 'term_merge_test_unlimited':
  196. case 'term_merge_do_not_merge':
  197. $count = rand(1, 3);
  198. for ($i = 0; $i < $count; $i++) {
  199. $edit[$field_name . '[' . LANGUAGE_NONE . '][' . $i . '][value]'] = $this->randomName();
  200. $this->drupalPost($url, $edit, 'Add another item');
  201. $url = NULL;
  202. }
  203. break;
  204. }
  205. }
  206. $this->drupalPost($url, $edit, 'Save');
  207. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  208. }
  209. // Firstly we make sure if 'merge_fields' is disabled, the fields are not
  210. // merged.
  211. actions_do('term_merge_action', $terms['branch'], array(
  212. 'term_trunk' => $terms['trunk']->tid,
  213. 'merge_fields' => array(),
  214. 'term_branch_keep' => TRUE,
  215. ));
  216. $this->drupalGet('taxonomy/term/' . $terms['trunk']->tid);
  217. foreach ($fields_map as $field_name => $cardinality) {
  218. foreach (field_get_items('taxonomy_term', $terms['branch'], $field_name) as $item) {
  219. $this->assertNoText($item['value'], 'Values of field ' . $field_name . ' have not been added to the trunk term with disabled "merge_fields" option.');
  220. }
  221. }
  222. // Now we try merging with merging 2 of 3 fields. The values of the branch
  223. // term should be added to the trunk term's values only in those 2 fields.
  224. actions_do('term_merge_action', $terms['branch'], array(
  225. 'term_trunk' => $terms['trunk']->tid,
  226. 'merge_fields' => array('term_merge_test_single', 'term_merge_test_unlimited'),
  227. 'term_branch_keep' => TRUE,
  228. ));
  229. $this->drupalGet('taxonomy/term/' . $terms['trunk']->tid);
  230. foreach ($fields_map as $field_name => $cardinality) {
  231. switch ($field_name) {
  232. case 'term_merge_test_single':
  233. case 'term_merge_do_not_merge':
  234. // Make sure if cardinality limit is hit, firstly original trunk term
  235. // values are stored. And make sure values of fields that are not
  236. // instructed to be added to trunk term's values are actually not
  237. // added.
  238. foreach (field_get_items('taxonomy_term', $terms['branch'], $field_name) as $item) {
  239. $this->assertNoText($item['value'], 'Values of field ' . $field_name . ' (cardinality ' . $cardinality . ') have not been added to the trunk term with enabled "merge_fields" option.');
  240. }
  241. break;
  242. case 'term_merge_test_unlimited':
  243. // Make sure values of fields that are instructed to be added to trunk
  244. // term's values are actually added.
  245. foreach (field_get_items('taxonomy_term', $terms['branch'], $field_name) as $item) {
  246. $this->assertText($item['value'], 'Values of field ' . $field_name . ' (cardinality ' . $cardinality . ') have been added to the trunk term with enabled "merge_fields" option.');
  247. }
  248. break;
  249. }
  250. }
  251. // Make sure that all taxonomy term reference fields are updated to point
  252. // from a branch term to a trunk term in other entities that have taxonomy
  253. // term reference fields.
  254. $terms = array(
  255. 'trunk' => FALSE,
  256. 'branch' => FALSE,
  257. );
  258. foreach ($terms as $term_type => $tmp) {
  259. $url = 'admin/structure/taxonomy/vocabulary/add';
  260. $name = $this->randomName();
  261. $edit = array(
  262. 'name' => $name,
  263. );
  264. $this->drupalPost($url, $edit, 'Save');
  265. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  266. }
  267. // Firstly we need to create a new content type and assign term reference
  268. // field to this new content type.
  269. $this->drupalPost('admin/structure/types/add', array(
  270. 'name' => $this->randomName(),
  271. 'type' => 'term_merge_node',
  272. ), 'Save content type');
  273. $this->drupalPost('admin/structure/types/manage/term-merge-node/fields', array(
  274. 'fields[_add_new_field][label]' => 'Term Reference',
  275. 'fields[_add_new_field][field_name]' => 'term_reference',
  276. 'fields[_add_new_field][type]' => 'taxonomy_term_reference',
  277. 'fields[_add_new_field][widget_type]' => 'taxonomy_autocomplete',
  278. ), 'Save');
  279. $this->drupalPost(NULL, array(
  280. 'field[settings][allowed_values][0][vocabulary]' => $this->vocabulary->machine_name,
  281. ), 'Save field settings');
  282. $this->drupalPost(NULL, array(
  283. 'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED,
  284. ), 'Save settings');
  285. // Flushing fields API cache.
  286. _field_info_collate_fields(TRUE);
  287. // Creating a new node and settings its term reference field to point to
  288. // the term branch.
  289. $title = $this->randomName();
  290. $this->drupalPost('node/add/term-merge-node', array(
  291. 'title' => $title,
  292. 'field_term_reference[' . LANGUAGE_NONE . ']' => $terms['branch']->name,
  293. ), 'Save');
  294. $node = $this->drupalGetNodeByTitle($title, TRUE);
  295. actions_do('term_merge_action', $terms['branch'], array(
  296. 'term_trunk' => $terms['trunk']->tid,
  297. 'merge_fields' => array(),
  298. 'term_branch_keep' => TRUE,
  299. ));
  300. $this->drupalGet('node/' . $node->nid);
  301. $this->assertText($terms['trunk']->name, 'Taxonomy term reference field gets updated to point from term branch to term trunk after merging terms.');
  302. // Testing 'Keep only unique' setting for merging. We create a node assigned
  303. // to both branch and trunk terms, and merge with, and then without 'Keep
  304. // only unique' setting, asserting each result.
  305. $terms = array(
  306. 'trunk' => FALSE,
  307. 'branch' => FALSE,
  308. );
  309. foreach ($terms as $term_type => $tmp) {
  310. $url = 'admin/structure/taxonomy/vocabulary/add';
  311. $name = $this->randomName();
  312. $edit = array(
  313. 'name' => $name,
  314. );
  315. $this->drupalPost($url, $edit, 'Save');
  316. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  317. }
  318. $title = $this->randomName();
  319. $this->drupalPost('node/add/term-merge-node', array(
  320. 'title' => $title,
  321. 'field_term_reference[' . LANGUAGE_NONE . ']' => $terms['branch']->name . ', ' . $terms['trunk']->name,
  322. ), 'Save');
  323. actions_do('term_merge_action', $terms['branch'], array(
  324. 'term_trunk' => $terms['trunk']->tid,
  325. 'merge_fields' => array(),
  326. 'term_branch_keep' => TRUE,
  327. 'keep_only_unique' => FALSE,
  328. ));
  329. $node = $this->drupalGetNodeByTitle($title);
  330. $is_first_trunk = $node->field_term_reference[LANGUAGE_NONE][0]['tid'] == $terms['trunk']->tid;
  331. $is_second_trunk = $node->field_term_reference[LANGUAGE_NONE][1]['tid'] == $terms['trunk']->tid;
  332. $this->assertTrue($is_first_trunk && $is_second_trunk, 'The same terms are kept in term reference field values if "Keep only unique" is off.');
  333. // We switch roles of 'trunk' and 'branch' now. We have a node with 2 terms,
  334. // if we merge them into another with "Keep only unique" on we are supposed
  335. // to have only 1 term after merging.
  336. actions_do('term_merge_action', $terms['trunk'], array(
  337. 'term_trunk' => $terms['branch']->tid,
  338. 'merge_fields' => array(),
  339. 'term_branch_keep' => TRUE,
  340. 'keep_only_unique' => TRUE,
  341. ));
  342. $node = $this->drupalGetNodeByTitle($title, TRUE);
  343. $is_single = count($node->field_term_reference[LANGUAGE_NONE]) == 1;
  344. $is_expected_term = $node->field_term_reference[LANGUAGE_NONE][0]['tid'] == $terms['branch']->tid;
  345. $this->assertTrue($is_single && $is_expected_term, 'Only one term is kept in term reference field values if "Keep only unique" is on.');
  346. }
  347. /**
  348. * Test all cases for potentially "buggy" input.
  349. *
  350. * Test the functionality of the action "Term Merge" with various suspicious
  351. * input arguments, and testing the web UI of the module with suspicious
  352. * input.
  353. */
  354. public function testTermMergeResistance() {
  355. drupal_static_reset();
  356. // Trying to merge 2 terms from 2 different vocabularies.
  357. $this->drupalPost('admin/structure/taxonomy/add', array(
  358. 'name' => $this->randomName(),
  359. 'machine_name' => 'vocabulary2',
  360. ), 'Save');
  361. $terms = array(
  362. 'vocabulary' => FALSE,
  363. 'vocabulary2' => FALSE,
  364. );
  365. foreach ($terms as $term_type => $tmp) {
  366. $url = 'admin/structure/taxonomy/' . $term_type . '/add';
  367. $edit = array(
  368. 'name' => $this->randomName(),
  369. );
  370. $this->drupalPost($url, $edit, 'Save');
  371. $terms[$term_type] = $this->getLastTerm(taxonomy_vocabulary_machine_name_load($term_type));
  372. }
  373. actions_do('term_merge_action', $terms['vocabulary'], array(
  374. 'term_trunk' => $terms['vocabulary2']->tid,
  375. 'term_branch_keep' => FALSE,
  376. ));
  377. $this->termMergeResistanceAssert($terms, 'Testing merging 2 terms from 2 different vocabularies.');
  378. // Trying to merge a parent into its child.
  379. $terms = array(
  380. 'parent' => FALSE,
  381. 'child' => FALSE,
  382. );
  383. drupal_static_reset();
  384. foreach ($terms as $term_type => $tmp) {
  385. $url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add';
  386. $edit = array(
  387. 'name' => $this->randomName(),
  388. );
  389. if ($term_type == 'child') {
  390. $edit['parent[]'] = array($terms['parent']->tid);
  391. }
  392. $this->drupalPost($url, $edit, 'Save');
  393. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  394. }
  395. actions_do('term_merge_action', $terms['parent'], array(
  396. 'term_trunk' => $terms['child']->tid,
  397. 'term_branch_keep' => FALSE,
  398. ));
  399. $this->termMergeResistanceAssert($terms, 'Testing merging a parent into its child.');
  400. // Trying to merge a term into itself.
  401. $terms = array(
  402. 'single' => FALSE,
  403. );
  404. foreach ($terms as $term_type => $tmp) {
  405. $url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add';
  406. $name = $this->randomName();
  407. $edit = array(
  408. 'name' => $name,
  409. );
  410. $this->drupalPost($url, $edit, 'Save');
  411. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  412. }
  413. actions_do('term_merge_action', $terms['single'], array(
  414. 'term_trunk' => $terms['single']->tid,
  415. 'term_branch_keep' => FALSE,
  416. ));
  417. $this->termMergeResistanceAssert($terms, 'Testing merging a term into itself.');
  418. // Making sure the access rights are respected.
  419. $account = $this->drupalCreateUser(array('merge vocabulary2 terms'));
  420. $this->drupalLogin($account);
  421. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge');
  422. $this->assertResponse(403, 'Per vocabulary term merge permissions are respected in the module - an account cannot merge terms in the vocabulary in which he is not supposed to be able to merge.');
  423. $this->drupalGet('admin/structure/taxonomy/vocabulary2/merge');
  424. $this->assertResponse(200, 'Per vocabulary term merge permissions are respected in the module - an account can merge terms in the vocabulary in which he is supposed to be able to merge.');
  425. }
  426. /**
  427. * Test all cases of usage of Term Merge Batch.
  428. */
  429. public function testTermMergeBatch() {
  430. // Adding fields with unlimited cardinality to our vocabulary.
  431. $this->drupalPost('admin/structure/taxonomy/vocabulary/fields', array(
  432. 'fields[_add_new_field][label]' => 'Test Unlimited Text',
  433. 'fields[_add_new_field][field_name]' => 'test_text',
  434. 'fields[_add_new_field][type]' => 'text',
  435. 'fields[_add_new_field][widget_type]' => 'text_textfield',
  436. ), 'Save');
  437. $this->drupalPost(NULL, array(), 'Save field settings');
  438. $this->drupalPost(NULL, array(
  439. 'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED,
  440. ), 'Save settings');
  441. // Additionally we need to create a new content type and assign term
  442. // reference field to this new content type.
  443. $this->drupalPost('admin/structure/types/add', array(
  444. 'name' => $this->randomName(),
  445. 'type' => 'term_merge_node',
  446. ), 'Save content type');
  447. $this->drupalPost('admin/structure/types/manage/term-merge-node/fields', array(
  448. 'fields[_add_new_field][label]' => 'Term Reference',
  449. 'fields[_add_new_field][field_name]' => 'term_reference',
  450. 'fields[_add_new_field][type]' => 'taxonomy_term_reference',
  451. 'fields[_add_new_field][widget_type]' => 'taxonomy_autocomplete',
  452. ), 'Save');
  453. $this->drupalPost(NULL, array(
  454. 'field[settings][allowed_values][0][vocabulary]' => $this->vocabulary->machine_name,
  455. ), 'Save field settings');
  456. $this->drupalPost(NULL, array(), 'Save settings');
  457. // Flushing fields API cache.
  458. _field_info_collate_fields(TRUE);
  459. // Array of cases for which we test the Term Merge batch.
  460. $cases = array(
  461. 'taxonomy_vocabulary_tab',
  462. 'taxonomy_term_tab',
  463. 'via_term_trunk_widget_select',
  464. 'via_term_trunk_widget_select_creating_new_term_trunk',
  465. 'via_term_trunk_widget_autocomplete',
  466. 'merge_fields',
  467. 'do_not_merge_fields',
  468. );
  469. foreach ($cases as $case) {
  470. // Creating a necessary set of terms in the vocabulary.
  471. drupal_static_reset();
  472. $terms = array(
  473. 'parent' => FALSE,
  474. 'another_parent' => FALSE,
  475. 'child' => FALSE,
  476. 'term1' => FALSE,
  477. 'term2' => FALSE,
  478. 'term3' => FALSE,
  479. 'term_trunk_parent' => FALSE,
  480. 'term_trunk' => FALSE,
  481. );
  482. foreach ($terms as $term_type => $tmp) {
  483. $url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add';
  484. $edit = array(
  485. 'name' => $term_type . '_' . $this->randomName(),
  486. 'field_test_text[' . LANGUAGE_NONE . '][0][value]' => $term_type,
  487. );
  488. switch ($term_type) {
  489. case 'child':
  490. $edit['parent[]'] = array($terms['parent']->tid, $terms['another_parent']->tid);
  491. break;
  492. case 'term_trunk':
  493. $edit['parent[]'] = array($terms['term_trunk_parent']->tid);
  494. break;
  495. }
  496. $this->drupalPost($url, $edit, 'Save');
  497. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  498. }
  499. // The initial URL from where the form that kicks off batch is submitted.
  500. $init_url = '';
  501. // What widget to use for choosing term trunk.
  502. $term_trunk_widget = '';
  503. // Value for term trunk in the format, expected by the widget
  504. // $term_trunk_widget. Additionally, if any test case requires any extra
  505. // fields to be submitted, input those fields into this array and they
  506. // won't be taken out from this array, then it will get merged into $edit,
  507. // and this way eventually your values will be successfully submitted.
  508. $term_trunk_edit = array();
  509. // Setting up controlling vars based on case and doing any specific
  510. // assertions for each case.
  511. switch ($case) {
  512. case 'taxonomy_vocabulary_tab':
  513. $init_url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge';
  514. // It doesn't really matter which widget we use, we test widgets
  515. // throughout in other cases.
  516. $term_trunk_widget = array_rand(drupal_map_assoc(array('select', 'autocomplete')));
  517. break;
  518. case 'taxonomy_term_tab':
  519. $init_url = 'taxonomy/term/' . $terms['parent']->tid . '/merge';
  520. // It doesn't really matter which widget we use, we test widgets
  521. // throughout in other cases.
  522. $term_trunk_widget = array_rand(drupal_map_assoc(array('select', 'autocomplete')));
  523. // Assert that the term, for which the tab was clicked, is selected as
  524. // term branch by default.
  525. $this->drupalGet($init_url);
  526. $this->assertOptionSelected('edit-term-branch', $terms['parent']->tid, 'Clicking the "Merge Terms" tab from a term view page sets the viewed term as a term branch by default.');
  527. break;
  528. case 'via_term_trunk_widget_select':
  529. $init_url = 'taxonomy/term/' . $terms['parent']->tid . '/merge';
  530. $term_trunk_widget = 'select';
  531. // Making sure for the term trunk select the selected term branch are
  532. // not available, nor their children.
  533. $this->drupalGet($init_url);
  534. $matches = array();
  535. preg_match('#\<select[^>]+name="term_trunk\[tid\]"[^>]*\>.+?\</select\>#si', $this->content, $matches);
  536. $term_trunk_options = $matches[0];
  537. $str_pos = strpos($term_trunk_options, $terms['child']->name);
  538. $this->assertIdentical(FALSE, $str_pos, 'Child is not available as option for term trunk if its parent is chosen among term branches.');
  539. $str_pos = strpos($term_trunk_options, $terms['parent']->name);
  540. $this->assertIdentical(FALSE, $str_pos, 'Selected branch term is not available as an option for term trunk.');
  541. // At the same time asserting if we choose "New Term" option, among
  542. // the available parents for the new term there are no children of the
  543. // selected branch terms, nor the branch terms themselves.
  544. $this->drupalPostAJAX(NULL, array(
  545. 'term_branch[]' => array($terms['parent']->tid),
  546. 'term_trunk[widget]' => $term_trunk_widget,
  547. 'term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK,
  548. ), 'term_trunk[tid]');
  549. $matches = array();
  550. preg_match('#\<select[^>]+name="relations\[parent\]\[\]"[^>]*\>.+?\</select\>#si', $this->content, $matches);
  551. $new_term_parent_options = $matches[0];
  552. $str_pos = strpos($new_term_parent_options, $terms['child']->name);
  553. $this->assertIdentical(FALSE, $str_pos, 'Child is not available as option for parent term for a new term trunk if its parent is chosen among term branches.');
  554. $str_pos = strpos($new_term_parent_options, $terms['parent']->name);
  555. $this->assertIdentical(FALSE, $str_pos, 'Selected branch term is not available as an option for parent term for a new term trunk.');
  556. break;
  557. case 'via_term_trunk_widget_select_creating_new_term_trunk':
  558. $init_url = 'taxonomy/term/' . $terms['parent']->tid . '/merge';
  559. $term_trunk_widget = 'select';
  560. break;
  561. case 'via_term_trunk_widget_autocomplete':
  562. $init_url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge';
  563. $term_trunk_widget = 'autocomplete';
  564. // Test autocomplete widget menu path to make sure it does reply
  565. // with valid suggestions.
  566. $response = $this->drupalGet('term-merge/autocomplete/term-trunk/' . $this->vocabulary->machine_name . '/' . drupal_strtoupper($terms['term_trunk']->name));
  567. $response = drupal_json_decode($response);
  568. $this->assertTrue(isset($response[$terms['term_trunk']->name]), 'Autocomplete menu path replies with valid suggestions for term trunk autocomplete widget.');
  569. // Making sure for the term trunk autocomplete widget doesn't allow to
  570. // submit any of the selected term branches nor their children.
  571. $prohibited_terms = array(
  572. 'parent' => 'Merging into the same term is not allowed in autocomplete widget for term trunk.',
  573. 'child' => 'Merging into any of child of selected branch terms is not allowed in autocomplete widget for term trunk.',
  574. );
  575. foreach ($prohibited_terms as $term => $assert_message) {
  576. $term = $terms[$term];
  577. $this->drupalGet($init_url);
  578. $this->drupalPostAJAX(NULL, array(
  579. 'term_branch[]' => array($terms['parent']->tid),
  580. 'term_trunk[widget]' => $term_trunk_widget,
  581. ), 'term_trunk[widget]');
  582. $this->drupalPost(NULL, array(
  583. 'term_branch[]' => array($terms['parent']->tid),
  584. 'term_trunk[widget]' => $term_trunk_widget,
  585. 'term_trunk[tid]' => $term->name,
  586. ), 'Submit');
  587. $this->assertText('Trunk term cannot be one of the selected branch terms or their children', $assert_message);
  588. }
  589. break;
  590. case 'merge_fields':
  591. $init_url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge';
  592. // It doesn't really matter which widget we use, we test widgets
  593. // throughout in other cases.
  594. $term_trunk_widget = array_rand(drupal_map_assoc(array('select', 'autocomplete')));
  595. // We embed extra info related to field values merging into
  596. // $term_trunk_edit.
  597. $term_trunk_edit['merge_fields[field_test_text]'] = TRUE;
  598. break;
  599. case 'do_not_merge_fields':
  600. $init_url = 'admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge';
  601. // It doesn't really matter which widget we use, we test widgets
  602. // throughout in other cases.
  603. $term_trunk_widget = array_rand(drupal_map_assoc(array('select', 'autocomplete')));
  604. break;
  605. }
  606. // Creating a new node and setting its term reference field to point to
  607. // the term branch.
  608. $title = $this->randomName();
  609. $this->drupalPost('node/add/term-merge-node', array(
  610. 'title' => $title,
  611. 'field_term_reference[' . LANGUAGE_NONE . ']' => $terms['term1']->name,
  612. ), 'Save');
  613. $node = $this->drupalGetNodeByTitle($title, TRUE);
  614. // Calling the Term Merge form.
  615. $this->drupalGet($init_url);
  616. // Choosing term branches.
  617. $term_branches = array('term1', 'term2', 'term3');
  618. $term_branches_edit = array();
  619. foreach ($term_branches as $term_type) {
  620. $term_branches_edit[] = $terms[$term_type]->tid;
  621. }
  622. $this->drupalPostAJAX(NULL, array(
  623. 'term_branch[]' => $term_branches_edit,
  624. ), 'term_branch[]');
  625. // Choosing the widget for trunk term.
  626. $this->drupalPostAJAX(NULL, array(
  627. 'term_branch[]' => $term_branches_edit,
  628. 'term_trunk[widget]' => $term_trunk_widget,
  629. ), 'term_trunk[widget]');
  630. // Choosing term trunk.
  631. switch ($term_trunk_widget) {
  632. case 'select':
  633. $term_trunk_edit += array('term_trunk[tid]' => $terms['term_trunk']->tid);
  634. if ($case == 'via_term_trunk_widget_select_creating_new_term_trunk') {
  635. // This is special case, we are gonna create a new term trunk using
  636. // taxonomy create term embedded form.
  637. $term_trunk_edit = array('term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK) + $term_trunk_edit;
  638. $this->drupalPostAJAX(NULL, array(
  639. 'term_branch[]' => $term_branches_edit,
  640. 'term_trunk[widget]' => $term_trunk_widget,
  641. ) + $term_trunk_edit, 'term_trunk[tid]');
  642. // Adding another delta for text field. This way we make sure Field
  643. // API gets embedded into our form without errors.
  644. $this->drupalPostAJAX(NULL, array(
  645. 'term_branch[]' => $term_branches_edit,
  646. 'term_trunk[widget]' => $term_trunk_widget,
  647. ) + $term_trunk_edit, array('field_test_text_add_more' => 'Add another item'));
  648. // We store into $term_trunk_edit array the info about the just
  649. // created new trunk term for further assertions down below in the
  650. // code.
  651. $term_trunk_edit += array(
  652. 'name' => $this->randomName(),
  653. 'description[value]' => $this->randomName(),
  654. 'field_test_text[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(),
  655. 'field_test_text[' . LANGUAGE_NONE . '][1][value]' => $this->randomName(),
  656. 'relations[parent][]' => array($terms['term_trunk_parent']->tid),
  657. );
  658. }
  659. break;
  660. case 'autocomplete':
  661. $term_trunk_edit += array('term_trunk[tid]' => $terms['term_trunk']->name);
  662. break;
  663. }
  664. // Submitting the form.
  665. $edit = $term_trunk_edit + array(
  666. 'term_branch[]' => $term_branches_edit,
  667. 'term_trunk[widget]' => $term_trunk_widget,
  668. 'term_branch_keep' => FALSE,
  669. 'step' => 2,
  670. );
  671. $this->drupalPost(NULL, $edit, 'Submit');
  672. $this->drupalPost(NULL, array(), 'Confirm');
  673. // Making sure all the branches are deleted.
  674. foreach ($term_branches as $term_type) {
  675. $term = $terms[$term_type];
  676. $this->drupalGet('taxonomy/term/' . $term->tid);
  677. $this->assertResponse(404, 'Branch term ' . $term_type . ' has been deleted after merging.');
  678. }
  679. $text_assertions = array();
  680. $term_trunk = $terms['term_trunk'];
  681. // Adding any extra text assertions on per test-case basis.
  682. switch ($case) {
  683. case 'via_term_trunk_widget_select_creating_new_term_trunk':
  684. $term_trunk = $this->getLastTerm($this->vocabulary);
  685. // Making sure the parent property of the just created trunk term is
  686. // correct.
  687. $parents = taxonomy_get_parents_all($term_trunk->tid);
  688. $this->assertTrue(count($parents) == 2 && $parents[0]->tid == $term_trunk->tid && $parents[1]->tid == $terms['term_trunk_parent']->tid, 'Parent property of the just created new term trunk is correct.');
  689. // Adding the submitted field values for further assertions too. This
  690. // way we test whether the embedded form adds a new term along with
  691. // any fields we have submitted into it.
  692. $text_assertions['New Term name'] = $term_trunk_edit['name'];
  693. $text_assertions['New Term description'] = $term_trunk_edit['description[value]'];
  694. $text_assertions['New Term text field delta #0'] = $term_trunk_edit['field_test_text[' . LANGUAGE_NONE . '][0][value]'];
  695. $text_assertions['New Term text field delta #1'] = $term_trunk_edit['field_test_text[' . LANGUAGE_NONE . '][1][value]'];
  696. break;
  697. case 'merge_fields':
  698. // Making sure the term trunk has been merged all the fields from term
  699. // branches into itself.
  700. foreach ($term_branches as $term_type) {
  701. $items = field_get_items('taxonomy_term', $terms[$term_type], 'field_test_text');
  702. foreach ($items as $delta => $item) {
  703. $text_assertions[$term_type . ' text field delta#' . $delta . ' has been merged when instructed to merge field values.'] = $item['value'];
  704. }
  705. }
  706. break;
  707. case 'do_not_merge_fields':
  708. // We need to assert that no values for field have been merged from
  709. // branch terms into the values of trunk term.
  710. $this->drupalGet('taxonomy/term/' . $term_trunk->tid);
  711. foreach ($term_branches as $term_type) {
  712. $items = field_get_items('taxonomy_term', $terms[$term_type], 'field_test_text');
  713. foreach ($items as $delta => $item) {
  714. $this->assertNoText($item['value'], $term_type . ' text field delta#' . $delta . ' has not been merged when instrcuted not to merge field values.');
  715. }
  716. }
  717. break;
  718. }
  719. $this->drupalGet('taxonomy/term/' . $term_trunk->tid);
  720. foreach ($text_assertions as $k => $v) {
  721. $this->assertText($v, 'Term trunk has the property ' . $k);
  722. }
  723. // Making sure the taxonomy term reference in other entities are updated
  724. // to point from term branches to the just created term trunk.
  725. $this->drupalGet('node/' . $node->nid);
  726. $this->assertText($term_trunk->name, 'Taxonomy term reference fields in other entities are updated to point from term branches to the term trunk.');
  727. }
  728. }
  729. /**
  730. * Supportive function for the main test "testTermMergeResistance".
  731. *
  732. * Assert that each term of the array $terms is available.
  733. *
  734. * @param array $terms
  735. * Array of taxonomy terms objects
  736. * @param string $message
  737. * Assertion message to be shown on the test results page
  738. */
  739. protected function termMergeResistanceAssert($terms, $message) {
  740. foreach ($terms as $term) {
  741. $this->drupalGet('taxonomy/term/' . $term->tid);
  742. $this->assertResponse(200, $message);
  743. }
  744. }
  745. }
  746. /**
  747. * Test the Merge Duplicate Terms feature of the Term Merge module.
  748. */
  749. class DuplicatesTermMergeWebTestCase extends TermMergeWebTestCase {
  750. /**
  751. * GetInfo method.
  752. */
  753. public function getInfo() {
  754. return array(
  755. 'name' => 'Duplicate terms merge',
  756. 'description' => 'Ensure that the feature <i>merge duplicate terms</i> of module Term Merge works correctly.',
  757. 'group' => 'Term Merge',
  758. );
  759. }
  760. /**
  761. * Test access rights.
  762. */
  763. public function testDisabledAndPermissions() {
  764. // Trying a user who doesn't have enough permissions.
  765. $account = $this->drupalCreateUser();
  766. $this->drupalLogin($account);
  767. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge/duplicates');
  768. $this->assertResponse(403, 'Access to Merge Duplicate Terms is denied for a user who does not have enough permissions.');
  769. // Trying a user who have enough permissions.
  770. $this->drupalLogin($this->admin);
  771. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge/duplicates');
  772. $this->assertResponse(200, 'Access to Merge Duplicate Terms is granted for a user who has enough permissions.');
  773. }
  774. /**
  775. * Test merging duplicates feature of Term Merge module.
  776. *
  777. * Test the following features:
  778. * - Correctness of merging a group of duplicate terms, namely:
  779. * - Correctness of merge operation when duplicates feature is invoked on
  780. * the entire vocabulary
  781. * - Correctness of merge operation when duplicates feature is invoked on a
  782. * term (merge its children one into another)
  783. * - Correctness of the mechanism that groups terms into sets of duplicate
  784. * entries, namely:
  785. * - Correctness of grouping by term name, i.e. unique terms should not be
  786. * listed in any set of duplicate terms
  787. * - Correctness of the initial set of terms, on which the duplicate tool is
  788. * invoked, i.e. when invoked on a vocabulary, we search for duplicates
  789. * in the whole vocabulary, but when invoked on a term, the tool should
  790. * only search for duplicate among the children of that term
  791. */
  792. public function testDuplicates() {
  793. // Creating duplicate terms firstly.
  794. $groups = array(
  795. 'single' => 1,
  796. 'triple_different_parent' => 3,
  797. 'random' => rand(2, 5),
  798. // We need some term, that will be a parent of some other terms.
  799. 'parent' => 1,
  800. );
  801. $groups = $this->createTerms($groups);
  802. // Let us make two of 'triple_different_parent' terms children of 'parent'
  803. // term.
  804. $groups['triple_different_parent'][1]->parent = $groups['parent'][0]->tid;
  805. taxonomy_term_save($groups['triple_different_parent'][1]);
  806. $groups['triple_different_parent'][2]->parent = $groups['parent'][0]->tid;
  807. taxonomy_term_save($groups['triple_different_parent'][2]);
  808. // Assuring the single term is not listed as duplicate.
  809. $this->drupaLGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge/duplicates');
  810. $this->assertNoText($groups['single'][0]->name, 'Single term is not listed as a duplicate.');
  811. // Making sure the term in 'triple_different_parent' that does not have a
  812. // parent, is not listed when we invoke duplicate tool on a parent term.
  813. $this->drupalGet('taxonomy/term/' . $groups['parent'][0]->tid . '/merge/duplicates');
  814. $this->assertNoFieldByName('group[' . $this->duplicateProcessName('triple_different_parent') . '][duplicates][' . $groups['triple_different_parent'][0]->tid . ']', 'Duplicate term is not listed when it is not among children of a term, on which Term Merge module was invoked.');
  815. $edit = array();
  816. // Trying to merge a term into another, invoking Duplicate tool on a parent
  817. // term of both. Important note: we do not test merging options, because
  818. // supposedly those are tested in the main test of this module.
  819. $edit['group[' . $this->duplicateProcessName('triple_different_parent') . '][trunk_tid]'] = $groups['triple_different_parent'][1]->tid;
  820. $edit['group[' . $this->duplicateProcessName('triple_different_parent') . '][duplicates][' . $groups['triple_different_parent'][2]->tid . ']'] = TRUE;
  821. $groups['triple_different_parent'][2]->merged = TRUE;
  822. $this->drupalPost('taxonomy/term/' . $groups['parent'][0]->tid . '/merge/duplicates', $edit, 'Submit');
  823. // Trying to merge multiple terms. We merge all but the 1st term.
  824. $edit = array();
  825. $edit['group[' . $this->duplicateProcessName('random') . '][trunk_tid]'] = $groups['random'][0]->tid;
  826. foreach ($groups['random'] as $k => $term) {
  827. if ($k != 0) {
  828. $edit['group[' . $this->duplicateProcessName('random') . '][duplicates][' . $term->tid . ']'] = TRUE;
  829. $groups['random'][$k]->merged = TRUE;
  830. }
  831. }
  832. $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge/duplicates', $edit, 'Submit');
  833. // Asserting results of merging.
  834. foreach ($groups as $group) {
  835. foreach ($group as $term) {
  836. $this->drupalGet('taxonomy/term/' . $term->tid);
  837. $code = isset($term->merged) && $term->merged ? 404 : 200;
  838. $message = isset($term->merged) && $term->merged ? 'Term #' . $term->tid . ' has been successfully merged.' : 'Term #' . $term->tid . ' has been successfully untouched during merging.';
  839. $this->assertResponse($code, $message);
  840. }
  841. }
  842. }
  843. /**
  844. * Supportive method.
  845. *
  846. * Create taxonomy terms with similar names.
  847. *
  848. * @param array $groups
  849. * Key should be a name of the group (terms' names in this group may only
  850. * differ in case, but will always use this string as their names), while
  851. * corresponding value to that key should denote how many terms in each
  852. * group should be created
  853. *
  854. * @return array
  855. * Array of fully loaded taxonomy terms objects of the just created terms,
  856. * grouped by their group name
  857. */
  858. private function createTerms($groups) {
  859. foreach ($groups as $name => $quantity) {
  860. $groups[$name] = array();
  861. for ($i = 0; $i < $quantity; $i++) {
  862. $term_name = '';
  863. // Randomizing case of the group name.
  864. foreach (str_split($name) as $symbol) {
  865. $symbol = rand(0, 1) ? drupal_strtoupper($symbol) : drupal_strtolower($symbol);
  866. $term_name .= $symbol;
  867. }
  868. $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', array(
  869. 'name' => $term_name,
  870. ), 'Save');
  871. $groups[$name][] = $this->getLastTerm($this->vocabulary);
  872. }
  873. }
  874. return $groups;
  875. }
  876. /**
  877. * Supportive method.
  878. *
  879. * Manipulate supplied var $name and by the output of this function terms in a
  880. * vocabulary are grouped as duplicates. This method should be identical to
  881. * the function term_merge_duplicates_process_name(), test case replies on the
  882. * fact that both manipulate the string in the identical way.
  883. *
  884. * @param string $name
  885. * String that needs to be manipulated
  886. *
  887. * @return string
  888. * Processed string (normally it implies making it upper case, stripping
  889. * down any special chars, etc.)
  890. */
  891. private function duplicateProcessName($name) {
  892. // Making upper case.
  893. $name = drupal_strtoupper($name);
  894. // Trying transliteration, if available.
  895. if (module_exists('transliteration')) {
  896. $name = transliteration_get($name);
  897. // Keeping only ASCII chars.
  898. $name = preg_replace('#\W#', '', $name);
  899. }
  900. return $name;
  901. }
  902. }
  903. /**
  904. * Test the integration between Term Merge module and Path/Redirect modules.
  905. */
  906. class RedirectTermMergeWebTestCase extends TermMergeWebTestCase {
  907. /**
  908. * Fully loaded Drupal user object of the user who has access to configure
  909. * redirects.
  910. *
  911. * @var object
  912. */
  913. protected $superAdmin;
  914. /**
  915. * SetUp method.
  916. */
  917. public function setUp() {
  918. parent::setUp(array('redirect', 'path'));
  919. $this->superAdmin = $this->drupalCreateUser(array(
  920. 'administer taxonomy',
  921. 'merge terms',
  922. 'administer content types',
  923. 'bypass node access',
  924. 'administer redirects',
  925. 'administer url aliases',
  926. ));
  927. }
  928. /**
  929. * GetInfo method.
  930. */
  931. public function getInfo() {
  932. return array(
  933. 'name' => 'Redirect module integration',
  934. 'description' => 'Ensure that the module Term Merge integrates with ' . l('Redirect', 'http://drupal.org/project/redirect') . '/Path modules correctly.',
  935. 'group' => 'Term Merge',
  936. );
  937. }
  938. /**
  939. * Test disabled Redirect module and access rights.
  940. */
  941. public function testDisabledAndPermissions() {
  942. // Checking access rights required to set up redirection during term
  943. // merging.
  944. $this->drupalLogin($this->admin);
  945. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge');
  946. $this->assertNoPattern('#\<select[^>]+name="redirect"[^>]*\>#i', 'No redirection settings are available for a user that does not possess corresponding permissions.');
  947. $this->drupalLogin($this->superAdmin);
  948. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge');
  949. $this->assertPattern('#\<select[^>]+name="redirect"[^>]*\>#i', 'Redirection settings are available for a user that possesses corresponding permissions.');
  950. // Making sure redirect settings are not available during merging when
  951. // merging with disabled Redirect module.
  952. module_disable(array('redirect'));
  953. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge');
  954. $this->assertNoPattern('#\<select[^>]+name="redirect"[^>]*\>#i', 'No redirection settings are available when the redirect module is disabled.');
  955. }
  956. /**
  957. * Test the action 'term_merge_action' in terms of integration with Redirect.
  958. */
  959. public function testTermMergeAction() {
  960. $this->drupalLogin($this->superAdmin);
  961. $terms = $this->createTerms(array('branch', 'trunk'));
  962. // Testing default value.
  963. actions_do('term_merge_action', $terms['branch'], array(
  964. 'term_trunk' => $terms['trunk']->tid,
  965. 'term_branch_keep' => TRUE,
  966. ));
  967. $this->assertRedirectIntegration($terms, 'By default no redirects should be made.');
  968. // Testing no redirection.
  969. actions_do('term_merge_action', $terms['branch'], array(
  970. 'term_trunk' => $terms['trunk']->tid,
  971. 'term_branch_keep' => TRUE,
  972. 'redirect' => TERM_MERGE_NO_REDIRECT,
  973. ));
  974. $this->assertRedirectIntegration($terms, 'No redirects are made, if action is not instructed to make ones.');
  975. // Testing 301 redirection. Besides redirecting 'taxonomy/term/[branch-tid]'
  976. // to 'taxonomy/term/[trunk-tid]' and their path aliases we want to
  977. // additionally assert that all existing redirects to branch term will be
  978. // replaced with redirects to trunk term in Redirect module. Lastly, we also
  979. // assert that 'taxonomy/term/[branch-tid]/feed' path and all pointing there
  980. // redirects now point to 'taxonomy/term/[trunk-tid]/feed.
  981. $redirect_source = $this->randomName();
  982. $redirect = new stdClass();
  983. redirect_object_prepare($redirect, array(
  984. 'source' => $redirect_source,
  985. 'redirect' => 'taxonomy/term/' . $terms['branch']->tid,
  986. ));
  987. redirect_hash($redirect);
  988. redirect_save($redirect);
  989. $redirect = new stdClass();
  990. redirect_object_prepare($redirect, array(
  991. 'source' => $redirect_source . '/feed',
  992. 'redirect' => 'taxonomy/term/' . $terms['branch']->tid . '/feed',
  993. ));
  994. redirect_hash($redirect);
  995. redirect_save($redirect);
  996. actions_do('term_merge_action', $terms['branch'], array(
  997. 'term_trunk' => $terms['trunk']->tid,
  998. 'redirect' => 301,
  999. ));
  1000. $terms['branch']->redirect = $terms['trunk'];
  1001. $this->assertRedirectIntegration($terms, 'Redirects are made, if action is instructed to make ones.');
  1002. $this->drupalGet($redirect_source);
  1003. $this->assertUrl('taxonomy/term/' . $terms['trunk']->tid, array(), 'Redirect pointing to <em>taxonomy/term/[branch-tid]</em> now points to <em>taxonomy/term/[trunk-tid]</em>.');
  1004. $this->drupalGet($redirect_source . '/feed');
  1005. $this->assertUrl('taxonomy/term/' . $terms['trunk']->tid . '/feed', array(), 'Redirect pointing to <em>taxonomy/term/[branch-tid]/feed</em> now points to <em>taxonomy/term/[trunk-tid]/feed</em>.');
  1006. }
  1007. /**
  1008. * Test Term Merge batch in terms of integration with Redirect/Path modules.
  1009. */
  1010. public function testTermMergeBatch() {
  1011. $this->drupalLogin($this->superAdmin);
  1012. // Trying to merge without redirection.
  1013. $terms = $this->createTerms(array('branch', 'trunk'));
  1014. $this->drupalPost('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1015. 'term_branch[]' => array($terms['branch']->tid),
  1016. 'term_trunk[widget]' => 'select',
  1017. 'term_trunk[tid]' => $terms['trunk']->tid,
  1018. 'term_branch_keep' => TRUE,
  1019. 'redirect' => TERM_MERGE_NO_REDIRECT,
  1020. ), 'Submit');
  1021. $this->drupalPost(NULL, array(), 'Confirm');
  1022. $this->assertRedirectIntegration($terms, 'No redirections made after running merge batch when not instructed to make redirections.');
  1023. // Trying to merge into an existing term with redirection.
  1024. $this->drupalPost('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1025. 'term_branch[]' => array($terms['branch']->tid),
  1026. 'term_trunk[widget]' => 'select',
  1027. 'term_trunk[tid]' => $terms['trunk']->tid,
  1028. 'redirect' => 0,
  1029. ), 'Submit');
  1030. $terms['branch']->redirect = $terms['trunk'];
  1031. $this->drupalPost(NULL, array(), 'Confirm');
  1032. $this->assertRedirectIntegration($terms, 'Redirection is made after running merge batch merging into an existing term, when instructed to make redirections.');
  1033. // Trying to merge into a new term with redirection.
  1034. $terms = $this->createTerms(array('branch'));
  1035. $this->drupalPostAJAX('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1036. 'term_branch[]' => array($terms['branch']->tid),
  1037. 'term_trunk[widget]' => 'select',
  1038. 'term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK,
  1039. ), 'term_trunk[tid]');
  1040. $this->drupalPost(NULL, array(
  1041. 'term_branch[]' => array($terms['branch']->tid),
  1042. 'term_trunk[widget]' => 'select',
  1043. 'term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK,
  1044. 'name' => $this->randomName(),
  1045. 'redirect' => 0,
  1046. ), 'Submit');
  1047. $this->drupalPost(NULL, array(), 'Confirm');
  1048. $terms['trunk'] = $this->getLastTerm($this->vocabulary);
  1049. $terms['branch']->redirect = $terms['trunk'];
  1050. $this->assertRedirectIntegration($terms, 'Redirection is made after running merge batch merging into a new term, when instructed to make redirections.');
  1051. }
  1052. /**
  1053. * Supportive method.
  1054. *
  1055. * Assert expected results after doing any test actions.
  1056. *
  1057. * @param array $terms
  1058. * Array of terms as returned by $this->createTerms(). Those terms that have
  1059. * been merged and redirected to another terms, besides all normal keys
  1060. * should have property 'redirect' which should be equal to the fully loaded
  1061. * taxonomy term which they were redirected to
  1062. * @param string $message
  1063. * Assert message to be shown on test results page
  1064. */
  1065. protected function assertRedirectIntegration($terms, $message) {
  1066. foreach ($terms as $term) {
  1067. if (isset($term->redirect)) {
  1068. $sources = array('taxonomy/term/' . $term->tid);
  1069. // Additionally checking path alias.
  1070. if (!in_array(drupal_get_path_alias($sources[0]), $sources)) {
  1071. $sources[] = drupal_get_path_alias($sources[0]);
  1072. }
  1073. foreach ($sources as $source) {
  1074. $this->drupalGet($source);
  1075. $this->assertUrl('taxonomy/term/' . $term->redirect->tid, array(), $message);
  1076. }
  1077. // Additionally assert the 'taxonomy/term/*/feed' path.
  1078. $sources = array('taxonomy/term/' . $term->tid . '/feed');
  1079. if (!in_array(drupal_get_path_alias($sources[0]), $sources)) {
  1080. $sources[] = drupal_get_path_alias($sources[0]);
  1081. }
  1082. foreach ($sources as $source) {
  1083. $this->drupalGet($source);
  1084. $this->assertUrl('taxonomy/term/' . $term->redirect->tid . '/feed', array(), $message);
  1085. }
  1086. }
  1087. }
  1088. }
  1089. /**
  1090. * Supportive method.
  1091. *
  1092. * Create a list of terms, assigning path aliases according to the values
  1093. * of the supplied array.
  1094. *
  1095. * @param array $terms
  1096. * Array of machine readable term keys based on which is generated output
  1097. *
  1098. * @return array
  1099. * Array of taxonomy term objects path alias of which is equal to the value
  1100. * that corresponds to its position in the supplied array
  1101. */
  1102. protected function createTerms($terms) {
  1103. $return = array();
  1104. foreach ($terms as $v) {
  1105. $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', array(
  1106. 'name' => $this->randomName(),
  1107. 'path[alias]' => $v . $this->randomName(),
  1108. ), 'Save');
  1109. $return[$v] = $this->getLastTerm($this->vocabulary);
  1110. }
  1111. return $return;
  1112. }
  1113. }
  1114. /**
  1115. * Test the integration between Term Merge module and Synonyms module.
  1116. */
  1117. class SynonymsTermMergeWebTestCase extends TermMergeWebTestCase {
  1118. /**
  1119. * SetUp method.
  1120. */
  1121. public function setUp() {
  1122. parent::setUp(array('synonyms'));
  1123. // Additionally we enable default synonyms field in the vocabulary.
  1124. $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/edit', array(
  1125. 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => TRUE,
  1126. ), 'Save');
  1127. }
  1128. /**
  1129. * GetInfo method.
  1130. */
  1131. public function getInfo() {
  1132. return array(
  1133. 'name' => 'Synonyms module integration',
  1134. 'description' => 'Ensure that the module Term Merge integrates with ' . l('Synonyms', 'http://drupal.org/project/synonyms') . ' module correctly.',
  1135. 'group' => 'Term Merge',
  1136. );
  1137. }
  1138. /**
  1139. * Test disabled Synonyms module.
  1140. */
  1141. public function testDisabled() {
  1142. // Making sure synonyms settings are not available during merging when
  1143. // Synonyms module is disabled.
  1144. module_disable(array('synonyms'));
  1145. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/merge');
  1146. $this->assertNoText(t('Add as Synonyms'), 'No synonyms settings are available when the Synonyms module is disabled.');
  1147. }
  1148. /**
  1149. * Test the action 'term_merge_action' in terms of integration with Synonyms.
  1150. */
  1151. public function testTermMergeAction() {
  1152. $this->drupalLogin($this->admin);
  1153. $terms = $this->createTerms(array('branch', 'trunk'));
  1154. // Testing default value.
  1155. actions_do('term_merge_action', $terms['branch'], array(
  1156. 'term_trunk' => $terms['trunk']->tid,
  1157. 'term_branch_keep' => TRUE,
  1158. ));
  1159. $this->assertSynonymsIntegration($terms, 'By default no synonyms should be added.');
  1160. // Testing no synonyms adding.
  1161. actions_do('term_merge_action', $terms['branch'], array(
  1162. 'term_trunk' => $terms['trunk']->tid,
  1163. 'term_branch_keep' => TRUE,
  1164. 'synonyms' => array(),
  1165. ));
  1166. $this->assertSynonymsIntegration($terms, 'No synonyms are added, if action is not instructed to make ones.');
  1167. // Testing adding as a synonym.
  1168. actions_do('term_merge_action', $terms['branch'], array(
  1169. 'term_trunk' => $terms['trunk']->tid,
  1170. 'synonyms' => array(SYNONYMS_DEFAULT_FIELD_NAME),
  1171. ));
  1172. $terms['trunk']->synonyms = array($terms['branch']->name);
  1173. $this->assertSynonymsIntegration($terms, 'Synonyms are added, if action is instructed to add ones.');
  1174. }
  1175. /**
  1176. * Test Term Merge batch in terms of integration with Synonyms module.
  1177. */
  1178. public function testTermMergeBatch() {
  1179. // Trying to merge without synonyms adding.
  1180. $terms = $this->createTerms(array('branch', 'trunk'));
  1181. $this->drupalPost('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1182. 'term_branch[]' => array($terms['branch']->tid),
  1183. 'term_trunk[widget]' => 'select',
  1184. 'term_trunk[tid]' => $terms['trunk']->tid,
  1185. 'term_branch_keep' => TRUE,
  1186. 'synonyms[' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => FALSE,
  1187. ), 'Submit');
  1188. $this->drupalPost(NULL, array(), 'Confirm');
  1189. $this->assertSynonymsIntegration($terms, 'No synonyms are added after running merge batch when not instructed to add synonyms.');
  1190. // Trying to merge into an existing term with synonyms adding.
  1191. $this->drupalPost('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1192. 'term_branch[]' => array($terms['branch']->tid),
  1193. 'term_trunk[widget]' => 'select',
  1194. 'term_trunk[tid]' => $terms['trunk']->tid,
  1195. 'term_branch_keep' => TRUE,
  1196. 'synonyms[' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => TRUE,
  1197. ), 'Submit');
  1198. $terms['trunk']->synonyms = array($terms['branch']->name);
  1199. $this->drupalPost(NULL, array(), 'Confirm');
  1200. $this->assertSynonymsIntegration($terms, 'Synonyms are added after running merge batch merging into an existing term, when instructed to add synonyms.');
  1201. // Trying to merge into a new term with synonyms adding.
  1202. $terms = $this->createTerms(array('branch'));
  1203. $this->drupalPostAJAX('taxonomy/term/' . $terms['branch']->tid . '/merge', array(
  1204. 'term_branch[]' => array($terms['branch']->tid),
  1205. 'term_trunk[widget]' => 'select',
  1206. 'term_branch_keep' => TRUE,
  1207. 'term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK,
  1208. ), 'term_trunk[tid]');
  1209. $this->drupalPost(NULL, array(
  1210. 'term_branch[]' => array($terms['branch']->tid),
  1211. 'term_trunk[widget]' => 'select',
  1212. 'term_trunk[tid]' => TERM_MERGE_NEW_TERM_TRUNK,
  1213. 'term_branch_keep' => TRUE,
  1214. 'name' => $this->randomName(),
  1215. 'synonyms[' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => TRUE,
  1216. ), 'Submit');
  1217. $this->drupalPost(NULL, array(), 'Confirm');
  1218. $terms['trunk'] = $this->getLastTerm($this->vocabulary);
  1219. $terms['trunk']->synonyms = array($terms['branch']->name);
  1220. $this->assertSynonymsIntegration($terms, 'Synonyms are added after running merge batch merging into a new term, when instructed to add synonyms.');
  1221. }
  1222. /**
  1223. * Supportive method.
  1224. *
  1225. * Assert expected results after doing any test actions.
  1226. *
  1227. * @param array $terms
  1228. * Array of terms as returned by $this->createTerms(). Those term trunks
  1229. * that have merged any branch terms with "Synonyms" option on, besides all
  1230. * normal keys should have property 'synonyms' which should be an array of
  1231. * expected synonyms of this term
  1232. * @param string $message
  1233. * Assert message to be shown on test results page
  1234. */
  1235. protected function assertSynonymsIntegration($terms, $message) {
  1236. drupal_static_reset();
  1237. foreach ($terms as $term) {
  1238. // Getting an array of synonyms according to Synonyms module.
  1239. $synonyms = array();
  1240. foreach (synonyms_get_term_synonyms(taxonomy_term_load($term->tid)) as $tmp) {
  1241. $synonyms[] = $tmp['value'];
  1242. }
  1243. $expected_synonyms = isset($term->synonyms) ? $term->synonyms : array();
  1244. // Comparing $synonyms to $expected_synonyms.
  1245. if (count($expected_synonyms) != count(array_intersect($expected_synonyms, $synonyms))) {
  1246. $this->fail($message);
  1247. return;
  1248. }
  1249. }
  1250. // If we got here, then all expected synonyms were found.
  1251. $this->pass($message);
  1252. }
  1253. /**
  1254. * Supportive method.
  1255. *
  1256. * Create a list of terms, assigning names according to the values of the
  1257. * supplied array.
  1258. *
  1259. * @param array $terms
  1260. * Array of machine readable term keys based on which is generated output
  1261. *
  1262. * @return array
  1263. * Array of taxonomy term objects name of which is equal to the value that
  1264. * corresponds to its position in the supplied array
  1265. */
  1266. protected function createTerms($terms) {
  1267. $return = array();
  1268. foreach ($terms as $v) {
  1269. $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', array(
  1270. 'name' => $v,
  1271. ), 'Save');
  1272. $return[$v] = $this->getLastTerm($this->vocabulary);
  1273. }
  1274. return $return;
  1275. }
  1276. }
  1277. /**
  1278. * Test the integration between Term Merge module and Views module.
  1279. */
  1280. class ViewsTermMergeWebTestCase extends TermMergeWebTestCase {
  1281. /**
  1282. * View object on which all tests happen.
  1283. *
  1284. * @var view
  1285. */
  1286. protected $view;
  1287. /**
  1288. * SetUp method.
  1289. */
  1290. public function setUp() {
  1291. parent::setUp(array('views'));
  1292. // Additionally we create a view.
  1293. $view = views_new_view();
  1294. $view->name = 'term_merge_view_test';
  1295. $view->description = 'Test view to test Term Merge module.';
  1296. $view->tag = '';
  1297. $view->base_table = 'node';
  1298. $view->api_version = '3.0';
  1299. $view->core = 7;
  1300. $display_id = 'default';
  1301. $view->set_display($display_id);
  1302. views_save_view($view);
  1303. $this->view = &$view;
  1304. }
  1305. /**
  1306. * GetInfo method.
  1307. */
  1308. public function getInfo() {
  1309. return array(
  1310. 'name' => 'Views module integration',
  1311. 'description' => 'Ensure that the module Term Merge integrates with ' . l('Views', 'http://drupal.org/project/views') . ' module correctly.',
  1312. 'group' => 'Term Merge',
  1313. );
  1314. }
  1315. /**
  1316. * Test integration with Views Taxonomy Term reference filter.
  1317. */
  1318. public function testTermReferenceFieldFilter() {
  1319. // We need to create a content type and attach a term reference field to
  1320. // that bundle in order to have some term reference filter available in
  1321. // Views.
  1322. $this->drupalPost('admin/structure/types/add', array(
  1323. 'name' => $this->randomName(),
  1324. 'type' => 'term_merge_node',
  1325. ), 'Save content type');
  1326. $field_name = 'term_reference';
  1327. $this->drupalPost('admin/structure/types/manage/term-merge-node/fields', array(
  1328. 'fields[_add_new_field][label]' => 'Term Reference',
  1329. 'fields[_add_new_field][field_name]' => $field_name,
  1330. 'fields[_add_new_field][type]' => 'taxonomy_term_reference',
  1331. 'fields[_add_new_field][widget_type]' => 'taxonomy_autocomplete',
  1332. ), 'Save');
  1333. $field_name = 'field_' . $field_name;
  1334. $this->drupalPost(NULL, array(
  1335. 'field[settings][allowed_values][0][vocabulary]' => $this->vocabulary->machine_name,
  1336. ), 'Save field settings');
  1337. $this->drupalPost(NULL, array(
  1338. 'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED,
  1339. ), 'Save settings');
  1340. // Flushing fields API cache.
  1341. _field_info_collate_fields(TRUE);
  1342. // Loading field definition array of the term reference field we just
  1343. // created.
  1344. $field = field_info_field($field_name);
  1345. // Creating terms to have stuff to work with.
  1346. $terms = array(
  1347. 'branch' => FALSE,
  1348. 'trunk' => FALSE,
  1349. );
  1350. foreach ($terms as $term_type => $tmp) {
  1351. $url = 'admin/structure/taxonomy/vocabulary/add';
  1352. $name = $this->randomName();
  1353. $edit = array(
  1354. 'name' => $name,
  1355. );
  1356. $this->drupalPost($url, $edit, 'Save');
  1357. $terms[$term_type] = $this->getLastTerm($this->vocabulary);
  1358. }
  1359. // Adding a taxonomy term reference filter to the view.
  1360. $this->view->set_display('default');
  1361. // We use Field API info to look up necessary tables and columns.
  1362. $table = reset(array_keys($field['storage']['details']['sql']['FIELD_LOAD_CURRENT']));
  1363. $columns = $field['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$table];
  1364. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['id'] = $columns['tid'];
  1365. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['table'] = $table;
  1366. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['field'] = $columns['tid'];
  1367. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['value'] = array(
  1368. $terms['branch']->tid => $terms['branch']->tid,
  1369. );
  1370. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['type'] = 'select';
  1371. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['vocabulary'] = $this->vocabulary->machine_name;
  1372. $this->view->display_handler->display->display_options['filters'][$columns['tid']]['hierarchy'] = 1;
  1373. views_save_view($this->view);
  1374. // After such merge we expect the view's filter to be changed from branch
  1375. // term to trunk term.
  1376. actions_do('term_merge_action', $terms['branch'], array(
  1377. 'term_trunk' => $terms['trunk']->tid,
  1378. 'term_branch_keep' => FALSE,
  1379. ));
  1380. // Loading again the view after merging action.
  1381. $this->view = views_get_view($this->view->name);
  1382. $this->view->set_display('default');
  1383. $filter = $this->view->display_handler->display->display_options['filters'][$columns['tid']]['value'];
  1384. $this->assertTrue(count($filter) == 1 && in_array($terms['trunk']->tid, array_keys($filter)), 'Views term reference filter gets updated to filter on trunk term instead of filtering on branch term if the branch term is instructed to be deleted during merging of terms.');
  1385. }
  1386. }