Commit 2bc9e50d authored by Dries's avatar Dries
Browse files

- Patch #413192 by catch et al: make taxonomy terms fieldable (not to be...

- Patch #413192 by catch et al: make taxonomy terms fieldable (not to be confused with taxonomy terms as fields).
parent d0936908
......@@ -109,7 +109,7 @@ Drupal 7.0, xxxx-xx-xx (development version)
* Modules can declare RDF namespaces which are serialized in the <html> tag
for RDFa support.
- Field API:
* Custom data fields may be attached to nodes and users in Drupal.
* Custom data fields may be attached to nodes and users, and taxonomy terms.
* Node bodies and teasers are now Field API fields instead of
being a hard-coded property of node objects.
* In addition, any other object type may register with Field API
......
......@@ -112,24 +112,17 @@ class BlogAPITestCase extends DrupalWebTestCase {
* The vocab ID.
*/
function addVocabulary($vocab) {
$edit = array();
$edit['name'] = $vocab;
$edit['nodes[blog]'] = TRUE;
$this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
$this->assertRaw(t('Created new vocabulary %vocab.', array('%vocab' => $edit['name'])), t('Taxonomy vocabulary added.'));
$vocab_arr = taxonomy_get_vocabularies();
$vid = NULL;
foreach ($vocab_arr as $vocab_item) {
if ($vocab_item->name == $vocab) {
$vid = $vocab_item->vid;
break;
}
}
$this->assertNotNull($vid, t('Vocabulary found in database.'));
return $vid;
// Create a vocabulary.
$vocabulary = new stdClass();
$vocabulary->name = $vocab;
$vocabulary->description = $this->randomName();
$vocabulary->machine_name = $this->randomName();
$vocabulary->help = '';
$vocabulary->nodes = array('blog' => 'blog');
$vocabulary->weight = mt_rand(0, 10);
taxonomy_vocabulary_save($vocabulary);
return $vocabulary->vid;
}
/**
......@@ -142,23 +135,11 @@ class BlogAPITestCase extends DrupalWebTestCase {
* @return integer
* The Term ID.
*/
function addTerm($vid, $term) {
$edit = array();
$edit['name'] = $term;
$this->drupalPost('admin/content/taxonomy/' . $vid . '/add', $edit, t('Save'));
$this->assertRaw(t('Created new term %term.', array('%term' => $edit['name'])), t('Taxonomy term added.'));
$tree = taxonomy_get_tree($vid);
$tid = NULL;
foreach ($tree as $tree_term) {
if ($tree_term->name == $term) {
$tid = $tree_term->tid;
break;
}
}
$this->assertNotNull($tid, t('Term found in database.'));
return $tid;
function addTerm($vid, $term_name) {
$term = new stdClass();
$term->name = $term_name;
$term->vid = $vid;
taxonomy_term_save($term);
return $term->tid;
}
}
......@@ -126,6 +126,7 @@ class ForumTestCase extends DrupalWebTestCase {
$edit = array(
'name' => $title,
'description' => $description,
'machine_name' => drupal_strtolower($this->randomName()),
'help' => '',
);
......
......@@ -102,11 +102,13 @@ function theme_taxonomy_overview_vocabularies($form) {
* @see taxonomy_form_vocabulary_submit()
*/
function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
drupal_add_js(drupal_get_path('module', 'taxonomy') . '/vocabulary.js');
if (!is_array($edit)) {
$edit = (array)$edit;
}
$edit += array(
'name' => '',
'machine_name' => '',
'description' => '',
'help' => '',
'nodes' => array(),
......@@ -117,6 +119,7 @@ function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
'required' => 0,
'weight' => 0,
);
$form['#vocabulary'] = (object) $edit;
// Check whether we need a deletion confirmation form.
if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
return taxonomy_vocabulary_confirm_delete($form_state, $form_state['values']['vid']);
......@@ -128,6 +131,15 @@ function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
'#maxlength' => 255,
'#description' => t('The name for this vocabulary, e.g., <em>"Tags"</em>.'),
'#required' => TRUE,
'#field_suffix' => ' <small id="vocabulary-name-suffix">&nbsp;</small>',
);
$form['machine_name'] = array(
'#type' => 'textfield',
'#title' => t('Machine readable name'),
'#default_value' => $edit['machine_name'],
'#maxlength' => 255,
'#description' => t('The unique machine readable name for this vocabulary, used for theme templates, can only contain lowercase letters, numbers and underscores.'),
'#required' => TRUE,
);
$form['help'] = array(
'#type' => 'textfield',
......@@ -193,10 +205,34 @@ function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
return $form;
}
/**
* Validation handler for the vocabulary form.
*
* @see taxonomy_form_vocabulary()
*/
function taxonomy_form_vocabulary_validate($form, &$form_state) {
if ($form_state['clicked_button']['#value'] != t('Delete') && isset($form_state['values']['machine_name'])) {
// Restrict machine names to appropriate characters.
$machine_name = $form_state['values']['machine_name'];
if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['machine_name'])) {
form_set_error('machine_name', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
}
// Do not allow duplicate machine names.
$vocabularies = taxonomy_get_vocabularies();
foreach ($vocabularies as $vocabulary) {
if ($machine_name == $vocabulary->machine_name && (!(isset($form_state['values']['vid']) && $vocabulary->vid != $form_state['values']['vid']))) {
form_set_error('machine_name', t('This machine-readable name is already in use by another vocabulary and must be unique.'));
}
}
}
}
/**
* Accept the form submission for a vocabulary and save the results.
*/
function taxonomy_form_vocabulary_submit($form, &$form_state) {
$old_vocabulary = $form['#vocabulary'];
if ($form_state['clicked_button']['#value'] == t('Delete')) {
// Rebuild the form to confirm vocabulary deletion.
$form_state['rebuild'] = TRUE;
......@@ -206,6 +242,9 @@ function taxonomy_form_vocabulary_submit($form, &$form_state) {
// Fix up the nodes array to remove unchecked nodes.
$form_state['values']['nodes'] = array_filter($form_state['values']['nodes']);
$vocabulary = (object) $form_state['values'];
if ($vocabulary->machine_name != $old_vocabulary->machine_name) {
field_attach_rename_bundle($old_vocabulary->machine_name, $vocabulary->machine_name);
}
switch (taxonomy_vocabulary_save($vocabulary)) {
case SAVED_NEW:
drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
......@@ -636,15 +675,22 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
$edit += array(
'name' => '',
'description' => '',
'vocabulary_machine_name' => $vocabulary->machine_name,
'tid' => NULL,
'weight' => 0,
);
// Take into account multi-step rebuilding.
if (isset($form_state['term'])) {
$edit = $form_state['term'] + $edit;
}
$parent = array_keys(taxonomy_get_parents($edit['tid']));
$form['#term'] = $edit;
$form['#term']['parent'] = $parent;
$form['#vocabulary'] = $vocabulary;
$form['#vocabulary']->nodes = drupal_map_assoc($vocabulary->nodes);
$form['#builder_function'] = 'taxonomy_form_term_submit_builder';
// Check for confirmation forms.
if (isset($form_state['confirm_delete'])) {
......@@ -672,6 +718,14 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
'#default_value' => $edit['description'],
'#description' => t('A description of the term. To be displayed on taxonomy/term pages and RSS feeds.'));
$form['vocabulary_machine_name'] = array(
'#type' => 'textfield',
'#access' => FALSE,
'#value' => isset($edit['vocabulary_machine_name']) ? $edit['vocabulary_machine_name'] : $vocabulary->name,
);
field_attach_form('taxonomy_term', (object) $edit, $form, $form_state);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced options'),
......@@ -731,11 +785,14 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
}
/**
* Validation handler for the term edit form. Ensure numeric weight values.
* Validation handler for the term form.
*
* @see taxonomy_form_term()
*/
function taxonomy_form_term_validate($form, &$form_state) {
field_attach_form_validate('taxonomy_term', (object) $form_state['values'], $form, $form_state);
// Ensure numeric values.
if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
form_set_error('weight', t('Weight value must be numeric.'));
}
......@@ -764,7 +821,8 @@ function taxonomy_form_term_submit($form, &$form_state) {
return;
}
$term = (object) $form_state['values'];
$term = taxonomy_form_term_submit_builder($form, $form_state);
$status = taxonomy_term_save($term);
switch ($status) {
case SAVED_NEW:
......@@ -804,6 +862,19 @@ function taxonomy_form_term_submit($form, &$form_state) {
return;
}
/**
* Build a term by processing form values and prepare for a form rebuild.
*/
function taxonomy_form_term_submit_builder($form, &$form_state) {
$term = (object) $form_state['values'];
field_attach_submit('taxonomy_term', $term, $form, $form_state);
$form_state['term'] = (array)$term;
$form_state['rebuild'] = TRUE;
return $term;
}
/**
* Form builder for the confirmation of multiple term parents.
*
......@@ -836,6 +907,7 @@ function taxonomy_term_confirm_delete(&$form_state, $tid) {
$form['type'] = array('#type' => 'value', '#value' => 'term');
$form['name'] = array('#type' => 'value', '#value' => $term->name);
$form['tid'] = array('#type' => 'value', '#value' => $tid);
$form['vocabulary_machine_name'] = array('#type' => 'value', '#value' => $term->vocabulary_machine_name);
$form['delete'] = array('#type' => 'value', '#value' => TRUE);
return confirm_form($form,
t('Are you sure you want to delete the term %title?',
......
......@@ -226,6 +226,13 @@ function taxonomy_schema() {
'default' => '',
'description' => 'Name of the vocabulary.',
),
'machine_name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The vocabulary machine name.',
),
'description' => array(
'type' => 'text',
'not null' => FALSE,
......@@ -345,3 +352,29 @@ function taxonomy_update_7001() {
return $ret;
}
/**
* Add vocabulary machine_name column.
*/
function taxonomy_update_7002() {
$ret = array();
$field = array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The vocabulary machine name.',
);
db_add_field($ret, 'taxonomy_vocabulary', 'machine_name', $field);
foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
$machine_name = 'vocabulary_' . $vid;
db_update('taxonomy_vocabulary')
->fields(array('machine_name' => 'vocabulary_' . $vid))
->condition('vid', $vid)
->execute();
field_attach_create_bundle($machine_name);
}
return $ret;
}
......@@ -18,6 +18,36 @@ function taxonomy_perm() {
);
}
/**
* Implement hook_fieldable_info().
*/
function taxonomy_fieldable_info() {
$return = array(
'taxonomy_term' => array(
'name' => t('Taxonomy term'),
'id key' => 'tid',
'bundle key' => 'vocabulary_machine_name',
'bundles' => taxonomy_vocabulary_get_names(),
),
);
return $return;
}
/**
* Implement hook_field_build_modes();
*
* @TODO: build mode for display as a field (when attached to nodes etc.).
*/
function taxonomy_field_build_modes($obj_type) {
$modes = array();
if ($obj_type == 'term') {
$modes = array(
'full' => t('Taxonomy term page'),
);
}
return $modes;
}
/**
* Implement hook_theme().
*/
......@@ -259,6 +289,7 @@ function taxonomy_vocabulary_save($vocabulary) {
}
$query->execute();
}
field_attach_create_bundle($vocabulary->machine_name);
module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
}
......@@ -290,6 +321,7 @@ function taxonomy_vocabulary_delete($vid) {
taxonomy_term_delete($tid);
}
field_attach_delete_bundle($vocabulary['machine_name']);
module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
cache_clear_all();
......@@ -352,13 +384,21 @@ function taxonomy_term_save($term) {
// Prevent leading and trailing spaces in term names.
$term->name = trim($term->name);
}
if (!isset($term->vocabulary_machine_name)) {
$vocabulary = taxonomy_vocabulary_load($term->vid);
$term->vocabulary_machine_name = $vocabulary->machine_name;
}
field_attach_presave('taxonomy_term', $term);
if (!empty($term->tid) && $term->name) {
$status = drupal_write_record('taxonomy_term_data', $term, 'tid');
field_attach_update('taxonomy_term', $term);
module_invoke_all('taxonomy_term_insert', $term);
}
else {
$status = drupal_write_record('taxonomy_term_data', $term);
field_attach_insert('taxonomy_term', $term);
module_invoke_all('taxonomy_term_update', $term);
}
......@@ -484,6 +524,7 @@ function taxonomy_term_delete($tid) {
->condition('tid', $tid)
->execute();
field_attach_delete('taxonomy_term', $term);
module_invoke_all('taxonomy_term_delete', $term);
}
......@@ -570,6 +611,21 @@ function taxonomy_get_vocabularies($type = NULL) {
return taxonomy_vocabulary_load_multiple(array(), $conditions);
}
/**
* Get names for all taxonomy vocabularies.
*
* @return
* An array of vocabulary names in the format 'machine_name' => 'name'.
*/
function taxonomy_vocabulary_get_names() {
$names = array();
$vocabularies = taxonomy_get_vocabularies();
foreach ($vocabularies as $vocabulary) {
$names[$vocabulary->machine_name] = $vocabulary->name;
}
return $names;
}
/**
* Implement hook_form_alter().
* Generate a form for selecting terms to associate with a node.
......@@ -786,6 +842,7 @@ function taxonomy_node_save($node, $terms) {
unset($terms['tags']);
foreach ($typed_input as $vid => $vid_value) {
$vocabulary = taxonomy_vocabulary_load($vid);
$typed_terms = drupal_explode_tags($vid_value);
$inserted = array();
......@@ -801,7 +858,11 @@ function taxonomy_node_save($node, $terms) {
}
if (!$typed_term_tid) {
$edit = array('vid' => $vid, 'name' => $typed_term);
$edit = array(
'vid' => $vid,
'name' => $typed_term,
'vocabulary_machine_name' => $vocabulary->machine_name,
);
$term = (object)$edit;
$status = taxonomy_term_save($term);
$typed_term_tid = $term->tid;
......@@ -1321,7 +1382,7 @@ function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
// Remove any loaded terms from the array if they don't match $conditions.
if ($conditions) {
// Name matching is case insensitive, note that with some collations
// Name matching is case insensitive, note that with some collations
// LOWER() and drupal_strtolower() may return different results.
foreach ($terms as $term) {
$term_values = (array) $term;
......@@ -1338,8 +1399,10 @@ function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
// $tids still to load, or if $conditions was passed without $tids.
if ($tids || ($conditions && !$passed_tids)) {
$query = db_select('taxonomy_term_data', 't');
$query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
$taxonomy_term_data = drupal_schema_fields_sql('taxonomy_term_data');
$query->fields('t', $taxonomy_term_data);
$query->addField('v', 'machine_name', 'vocabulary_machine_name');
// If the $tids array is populated, add those to the query.
if ($tids) {
......@@ -1358,9 +1421,14 @@ function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
}
}
$queried_terms = $query->execute()->fetchAllAssoc('tid');
// Invoke hook_taxonomy_term_load() on the terms loaded from the database
// and add them to the static cache.
if (!empty($queried_terms)) {
// Attach fields.
field_attach_load('taxonomy_term', $queried_terms);
// Invoke hook_taxonomy_term_load() and add the term objects to the
// static cache.
foreach (module_implements('taxonomy_term_load') as $module) {
$function = $module . '_taxonomy_term_load';
$function($queried_terms);
......
......@@ -50,9 +50,11 @@ function taxonomy_term_page($terms, $depth = 0, $op = 'page') {
drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
$build = array();
// Only display the description if we have a single term, to avoid clutter and confusion.
// Only display fields if we have a single term, to avoid clutter and
// confusion.
if (count($tids) == 1) {
$term = taxonomy_term_load($tids[0]);
$build += field_attach_view('taxonomy_term', $term);
if (!empty($term->description)) {
$build['term_description'] = array(
'#markup' => filter_xss_admin($term->description),
......
......@@ -19,6 +19,7 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
$vocabulary = new stdClass();
$vocabulary->name = $this->randomName();
$vocabulary->description = $this->randomName();
$vocabulary->machine_name = drupal_strtolower($this->randomName());
$vocabulary->help = '';
$vocabulary->nodes = array('article' => 'article');
$vocabulary->weight = mt_rand(0, 10);
......@@ -29,10 +30,10 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
/**
* Returns a new term with random properties in vocabulary $vid.
*/
function createTerm($vid) {
function createTerm($vocabulary) {
$term = new stdClass();
$term->name = $this->randomName();
$term->vid = $vid;
$term->vid = $vocabulary->vid;
taxonomy_term_save($term);
return $term;
}
......@@ -70,6 +71,7 @@ class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
$edit = array();
$edit['name'] = $this->randomName();
$edit['description'] = $this->randomName();
$edit['machine_name'] = drupal_strtolower($this->randomName());
$edit['help'] = $this->randomName();
$edit['nodes[article]'] = 'article';
$edit['tags'] = 1;
......@@ -84,10 +86,19 @@ class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
$this->clickLink(t('edit vocabulary'));
$edit = array();
$edit['name'] = $this->randomName();
$edit['machine_name'] = drupal_strtolower($this->randomName());
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('Updated vocabulary %name.', array('%name' => $edit['name'])));
$this->drupalGet('admin/content/taxonomy');
$this->assertText($edit['name'], t('Vocabulary found in the vocabulary overview listing.'));
// Try to submit a vocabulary with a duplicate machine name.
$this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
$this->assertText(t('This machine-readable name is already in use by another vocabulary and must be unique.'), t('Duplicate machine name validation was successful'));
// Try to submit an invalid machine name.
$edit['machine_name'] = '!&^%';
$this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
$this->assertText(t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
}
/**
......@@ -125,13 +136,11 @@ class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
// Delete all vocabularies.
$vocabularies = taxonomy_get_vocabularies();
foreach ($vocabularies as $key => $vocabulary) {
$edit = array();
$this->drupalPost('admin/content/taxonomy/' . $vocabulary->vid, $edit, t('Delete'));
// Submit the confirm form for deletion.
$this->drupalPost(NULL, NULL, t('Delete'));
taxonomy_vocabulary_delete($key);
}
// Confirm that no vocabularies are found in the database.
$this->assertFalse(taxonomy_get_vocabularies(), t('No vocabularies found in the database'));
$this->drupalGet('admin/content/taxonomy');
// Check the default message for no vocabularies.
$this->assertText(t('No vocabularies available.'), t('No vocabularies were found.'));
}
......@@ -143,6 +152,7 @@ class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
// Create a vocabulary.
$edit = array(
'name' => $this->randomName(),
'machine_name' => drupal_strtolower($this->randomName()),
'nodes[article]' => 'article',
);
$this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
......@@ -257,6 +267,12 @@ class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase {
$vocabulary3->weight = 2;
taxonomy_vocabulary_save($vocabulary3);
// Fetch the names for all vocabularies, confirm that they are keyed by
// machine name.
$names = taxonomy_vocabulary_get_names();
$this->assertTrue(in_array($vocabulary1->name, $names), t('Vocabulary 1 name found.'));
$this->assertTrue(isset($names[$vocabulary1->machine_name]), t('Vocabulary names are keyed by machine name.'));
// Fetch all of the vocabularies using taxonomy_get_vocabularies().
// Confirm that the vocabularies are ordered by weight.
$vocabularies = taxonomy_get_vocabularies();
......@@ -306,9 +322,9 @@ class TaxonomyTermUnitTest extends TaxonomyWebTestCase {
function testTaxonomyTermCountNodes() {
// Create a vocabulary with three terms.
$vocabulary = $this->createVocabulary();
$term1 = $this->createTerm($vocabulary->vid);
$term2 = $this->createTerm($vocabulary->vid);
$term3 = $this->createTerm($vocabulary->vid);
$term1 = $this->createTerm($vocabulary);
$term2 = $this->createTerm($vocabulary);
$term3 = $this->createTerm($vocabulary);
// Attach term1 to a node.
$node1 = $this->drupalCreateNode(array('type' => 'page'));
......@@ -396,8 +412,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
*/
function testTaxonomyTermRelations() {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary->vid);
$term2 = $this->createTerm($this->vocabulary->vid);
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Edit $term1 and add $term2 as a relationship.
$edit = array();
......@@ -406,7 +422,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
$related = taxonomy_get_related($term1->tid);
$this->assertTrue(isset($related[$term2->tid]), t('Related term was found'));
// Create a third term.
$term3 = $this->createTerm($this->vocabulary->vid);
$term3 = $this->createTerm($this->vocabulary);
$edit['relations[]'] = $term3->tid;
$this->drupalPost('taxonomy/term/' . $term1->tid . '/edit', $edit, t('Save'));
......@@ -420,7 +436,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
*/
function testTaxonomySynonyms() {
// Create a taxonomy term with one synonym.
$term = $this->createTerm($this->vocabulary->vid);
$term = $this->createTerm($this->vocabulary);
$term->synonyms = $this->randomName();
taxonomy_term_save($term);
......@@ -439,8 +455,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
*/
function testTaxonomyTermHierarchy() {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary->vid);
$term2 = $this->createTerm($this->vocabulary->vid);
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Edit $term2, setting $term1 as parent.
$edit = array();
......@@ -454,7 +470,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
$this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));
// Create a third term and save this as a parent of term2.
$term3 = $this->createTerm($this->vocabulary->vid);
$term3 = $this->createTerm($this->vocabulary);
$term2->parent = array($term1->tid, $term3->tid);
taxonomy_term_save($term2);
$parents = taxonomy_get_parents($term2->tid);
......@@ -468,8 +484,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
*/
function testTaxonomyNode() {
// Create two taxonomy terms.