<?php

// $Id: taxonomy_manager.module,v 1.5.2.25 2008/07/25 15:50:25 mh86 Exp $ 

/**
 * 
 * @file
 * Taxonomy Manager
 * 
 * Administration interface for managing taxonomy vocabularies
 * 
 */


/**
 * Implementation of hook_menu
 */
function taxonomy_manager_menu($may_cache) {
  $items = array();
  
  if ($may_cache) {
    $items[] = array('path' => 'admin/content/taxonomy_manager',
      'title' => t('Taxonomy Manager'),
      'description' => t('Administer vocabularies with the Taxonomy Manager'),
      'callback' => 'taxonomy_manager_voc_list',
      'access' => user_access('administer taxonomy'),
      'type' => MENU_NORMAL_ITEM
    );
    
    $items[] = array('path' => 'admin/content/taxonomy_manager/childform',
      'callback' => 'taxonomy_manager_tree_build_child_form',
      'access' => user_access('administer taxonomy'), 
      'type' => MENU_CALLBACK 
    );
  
    $items[] = array('path' => 'admin/content/taxonomy_manager/weight',
      'callback' => 'taxonomy_manager_update_weights',
      'access' => user_access('administer taxonomy'), 
      'type' => MENU_CALLBACK 
    );
    
    $items[] = array('path' => 'admin/content/taxonomy_manager/termdata',
      'callback' => 'taxonomy_manager_update_term_data_form',
      'access' => user_access('administer taxonomy'), 
      'type' => MENU_CALLBACK 
    );
      
    $items[] = array('path' => 'admin/content/taxonomy_manager/siblingsform',
      'callback' => 'taxonomy_manager_tree_build_siblings_form',
      'access' => user_access('administer taxonomy'), 
      'type' => MENU_CALLBACK 
    );

    $items[] = array('path' => 'admin/content/taxonomy_manager/termdata/edit', 
      'callback' => 'taxonomy_manager_term_data_edit', 
      'access' => user_access('administer taxonomy'), 
      'type' => MENU_CALLBACK, 
    );
    
    $items[] = array('path' => 'admin/content/taxonomy_manager/export',
      'callback' => 'taxonomy_manager_export',
      'access' => user_access('administer taxonomy'),
      'type' => MENU_CALLBACK,
    );
    
    $items[] = array('path' => 'admin/settings/taxonomy_manager', 
      'title' => t('Taxonomy Manager'), 
      'description' => t('Advanced settings for the Taxonomy Manager'),
      'callback' => 'drupal_get_form', 
      'callback arguments' => array('taxonomy_manager_settings'), 
      'access' => user_access('administer site configuration'),
    );
    
    //override default term page so that we can consider merged terms
    $items[] = array('path' => 'taxonomy/term',
      'title' => t('Taxonomy term'),
      'callback' => 'taxonomy_manager_term_page',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
  }
  else {
    if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy_manager' && is_numeric(arg(3))) {
      $vid = arg(3);
      $items[] = array('path' => 'admin/content/taxonomy_manager/'. $vid,
        'title' => t('Taxonomy Manager'),
        'callback' => 'taxonomy_manager_voc',
        'callback arguments' => array($vid), 
        'access' => user_access('administer taxonomy'), 
        'type' => MENU_CALLBACK 
      );
    } 
  }
  
  return $items;
}

/**
 * Implementation of hook_help().
 */
function taxonomy_manager_help($section) {
  switch ($section) {
    case 'admin/help#taxonomy_manager':
      $output = t('The Taxonomy Manager provides an additional interface for managing vocabularies of the taxonomy module. It\'s especially very useful for long sets of terms. 
                   The vocabulary is represented in a dynamic tree view. 
                   It supports operation like mass adding and deleting of terms, fast weight editing, moving of terms in hierarchies, merging of terms and fast term data editing.
                   For more information on how to use please read the readme file included in the taxonomy_manager directory.'); 
      return $output;
  }
}

/**
 * list of vocabularies, which link to Taxonomy Manager interface
 */
function taxonomy_manager_voc_list() {
  $vocabularies = taxonomy_get_vocabularies();
  $voc_list = array();
  
  foreach ($vocabularies as $vocabulary) {
    $voc_list[] = l($vocabulary->name, 'admin/content/taxonomy_manager/'. $vocabulary->vid);
  }
  
  return theme('item_list', $voc_list, t("Vocabularies:"));
}

/**
 * returns either form for deletion confirm or the taxonomy manager form
 */
function taxonomy_manager_voc($vid, $tid = 0, $filter = NULL) {
  if ($_POST['op'] == t('Delete') || $_POST['confirm']) {
    return drupal_get_form('taxonomy_manager_terms_confirm_delete', $vid, $_POST);
  }
  return drupal_get_form('taxonomy_manager_form', $vid, $tid, $filter);

}

/**
 * defines forms for taxonomy manager interface
 * 
 * @param $vid vocabulary id
 * @param $tid a term id, if not 0, displays term editing from for given tid on right side
 * @param $search_string a string to filter root level terms
 */
function taxonomy_manager_form($vid, $tid = 0, $filter = NULL) {
  $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
  drupal_add_css($module_path .'css/taxonomy_manager.css');
  drupal_add_js($module_path .'js/hideForm.js');
  drupal_add_js($module_path .'js/updateWeight.js');
  drupal_add_js($module_path .'js/termData.js');
  drupal_add_js('misc/textarea.js');  //because of term data form wich gets dynamically loaded
  
  drupal_add_js(array('termData' => array('url' => url("admin/content/taxonomy_manager/termdata/edit/". $vid), 'tid' => $tid, 'term_url' => url(taxonomy_manager_tree_link($vid) .'/'. $tid .'/true'))), 'setting');
  drupal_add_js(array('updateWeight' => array('up' => 'edit-weight-up', 'down' => 'edit-weight-down', 'url' => url('admin/content/taxonomy_manager/weight/'. $vid), 'disable_mouseover' => variable_get('taxonomy_manager_disable_mouseover_weights', 0))), 'setting');
  
  $form = array();
  
  $voc = taxonomy_get_vocabulary($vid);
  
  drupal_set_title(t("Taxonomy Manager - %voc_name", array("%voc_name" => $voc->name)));
  
  if (!is_numeric($voc->vid)) {
    $text = t('No vocabulary with this ID available!. ');
    $text .= t('Check this !link_list for available vocabularies or !link_create a new one', array('!link_list' => l('list', 'admin/content/taxonomy_manager'), '!link_create' => l('create', 'admin/content/taxonomy/add/vocabulary')));
    $form['text'] = array( 
      '#value' => $text,
    );
    return $form;
  }
  
  $form['vid'] = array('#type' => 'value', "#value" => $vid);
  
  if (_taxonomy_manager_voc_is_empty($vid)) {
    $text = t('No terms available');
    $form['text'] = array( 
      '#value' => $text,
    );
    $form += taxonomy_manager_add_form($voc, false);
    return $form;
  }
  
  $form['taxonomy']['#tree'] = TRUE;
  
  $form['taxonomy']['manager'] = array( 
    '#type' => 'fieldset',
    '#title' => $voc->name,
    '#weight' => 10,
    '#tree' => TRUE,
    '#prefix' => '<div id="taxonomy-manager-tree-outer-div">',
    '#suffix' => '</div>',
  );
  
  $form['taxonomy']['manager']['tree'] = array( 
    '#type' => 'taxonomy_manager_tree', 
    '#vid' => $vid, 
    '#pager' => TRUE,
    '#search_string' => ($tid) ? NULL : $filter,
  );
  
  $search_description = t("You can search directly for existing terms. 
      If your input doesn't match an existing term, it will be used for filtering root level terms (useful for non-hierarchical vocabularies).");
  
  $form['search'] = array(  
    '#type' => 'fieldset',
    '#attributes' => array('id' => 'taxonomy-manager-search'), 
    '#title' => t('Search'),
    '#description' => $search_description, 
    '#collapsible' => TRUE, 
    '#collapsed' => TRUE,
    '#tree' => TRUE, 
  );
  
  $form['search']['field'] = array( 
    '#type' => 'textfield', 
    '#title' => t('Search String'), 
    '#autocomplete_path' => 'taxonomy/autocomplete/'. $voc->vid,
    '#prefix' => '<div id="edit-find-field">', 
    '#suffix' => '</div>', 
  );
  
  $form['search']['button'] = array( 
    '#type' => 'submit', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons search'),
    '#value' => t('Search'),
    '#suffix' => '<div class="clear"></div>',
  );
 
  $form['toolbar'] = array( 
    '#type' => 'fieldset', 
    '#title' => t('Toolbar'),
    '#attributes' => array('id' => 'taxonomy-manager-toolbar'),
  );
  
  $form['toolbar']['weight_up'] = array( 
    '#type' => 'button',
    '#attributes' => array('class' => 'taxonomy-manager-buttons'), 
    '#value' => t('Up'),
    '#theme' => 'no_submit_button',
    '#prefix' => '<div id="taxonomy-manager-toolbar-buttons">',
  );
  
  $form['toolbar']['weight-down'] = array( 
    '#type' => 'button', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons'),
    '#value' => t('Down'),
    '#theme' => 'no_submit_button',
  );
   
  $form['toolbar']['delete_confirm'] = array( 
    '#type' => 'button', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons delete'),
    '#value' => t('Delete'),
    '#theme' => 'no_submit_button',
  );

  $form['toolbar']['add_show'] = array( 
    '#type' => 'button',
    '#attributes' => array('class' => 'taxonomy-manager-buttons add'), 
    '#value' => t('Add'), 
    '#theme' => 'no_submit_button',
  );
  
  //moving is only possible in hierarchies...
  if ($voc->hierarchy != 0) {
    $form['toolbar']['move_show'] = array( 
      '#type' => 'button', 
      '#value' => t('Move'),
      '#attributes' => array('class' => 'taxonomy-manager-buttons move'),
       '#theme' => 'no_submit_button', 
    );
  }
  
  $form['toolbar']['merge_show'] = array( 
    '#type' => 'button', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons merge'),
    '#value' => t('Merge'), 
    '#theme' => 'no_submit_button', 
  );
  
  $form['toolbar']['export_show'] = array( 
    '#type' => 'button', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons export'),
    '#value' => t('CSV Export'), 
    '#theme' => 'no_submit_button', 
    '#suffix' => '</div>'
  );
  
  $form['toolbar']['wrapper'] = array( 
    '#type' => 'markup', 
    '#value' => '<div id="taxonomy-manager-toolbar-throbber"></div><div class="clear"></div>', 
    '#weight' => 20,
  );
  
  $form += taxonomy_manager_add_form($voc);
  
  $form += taxonomy_manager_merge_form($voc);
  
  $form += taxonomy_manager_move_form($voc);
  
  $form += taxonomy_manager_confirm_delete($voc);
  
  $form += taxonomy_manager_export_form($voc);
  
  if ($tid) {
    $form += taxonomy_manager_form_term_data($tid);
  }
  
  return $form;
}

/**
 * function gets called by the taxonomy_manager_tree form type (form_id +_operations)
 * return an form array with values to show next to every term value
 */
function taxonomy_manager_tree_operations() {
  $form = array();
  if (!variable_get('taxonomy_manager_disable_mouseover_weights', 0)) {
    $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
    $form['up'] = array('#value' => theme("image", $module_path ."images/go-up-small.png", "go up", NULL, array('class' => 'term-up')));
    $form['down'] = array('#value' => theme("image", $module_path ."images/go-down-small.png", "go down", NULL, array('class' => 'term-down')));
  }
  return $form;
}

/**
 * function gets called by taxonomy_manager_tree form type (form_id +_link)
 * and returns an link, where to go, when a term gets clicked
 *
 * @param $vid vocabulary id
 */
function taxonomy_manager_tree_link($vid) {
  return "admin/content/taxonomy_manager/termdata/". $vid;
}

/**
 * confirmation form for deleting selected terms
 */
function taxonomy_manager_confirm_delete($voc) {
  drupal_add_js(array('hideForm' => array(
    'show_button' => 'edit-delete-confirm', 
    'hide_button' => 'edit-delete-cancel', 
    'div' => 'del-confirm-form')), 'setting');
    
  $form = array();
  
  $form['delete'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Confirmation'),
    '#prefix' => '<div id="del-confirm-form">',
    '#suffix' => '</div>',
  );
  
  $question = t('Are you sure you want to delete all selected terms? ');
  $info = t('Remember all term specific data will be lost. This action cannot be undone.');
  
  $form['delete']['text'] = array('#value' => "<b>". $question ."</b><br/>". $info);
  
  if ($voc->hierarchy != 0) {
    $options = array( 
      'delete_orphans' => t('Delete children of selected terms, if there are any'), 
    );
     
    $form['delete']['options'] = array( 
      '#type' => 'checkboxes', 
      '#title' => t('Options'), 
      '#options' => $options, 
    );
  }
  
  $form['delete']['delete'] = array( 
    '#type' => 'submit', 
    '#value' => t('Delete'),
    '#attributes' => array('class' => 'taxonomy-manager-buttons delete'), 
  );
  
  $form['delete']['cancel'] = array(
    '#type' => 'button', 
    '#attributes' => array('class' => 'taxonomy-manager-buttons cancel'),
    '#value' => t('Cancel'), 
    '#theme' => 'no_submit_button',  
  );
  
  return $form;
}

/**
 * form for adding terms
 */
function taxonomy_manager_add_form($voc, $hide_form = TRUE) {
  if ($hide_form) {
    drupal_add_js(array('hideForm' => array(
      'show_button' => 'edit-add-show', 
      'hide_button' => 'edit-add-cancel', 
      'div' => 'add-form')), 'setting');
  }
  
  $form = array();
  
  $description = "";
  if ($voc->hierarchy >= 1) {
    $description = t("If you have selected one or more terms in the tree view, the new terms are automatically children of those.");
  }
  
  $form['add'] = array( 
    '#type' => 'fieldset', 
    '#tree' => TRUE,
    '#title' => t('Add new terms'),
    '#prefix' => '<div id="add-form">',
    '#suffix' => '</div>',
    '#description' => $description,
  );
  
  for ($i=0; $i<6; $i++) {
    $form['add']['term'][$i] = array( 
      '#type' => 'textfield', 
    );
  }
  $form['add']['mass'] = array(
    '#type' => 'fieldset', 
    '#tree' => TRUE,
    '#title' => t('Mass term import (with textarea)'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['add']['mass']['mass_add'] = array(
    '#type' => 'textarea', 
    '#title' => t('Terms'),
    '#description' => t('One term per line'),
    '#rows' => 10,
  );
  $form['add']['add'] = array( 
    '#type' => 'submit',
    '#attributes' => array('class' => 'taxonomy-manager-buttons add'), 
    '#value' => t('Add'), 
  );
  
  $form['add']['cancel'] = array( 
    '#type' => 'button', 
    '#value' => t('Cancel'),
    '#theme' => 'no_submit_button',
    '#attributes' => array('class' => 'taxonomy-manager-buttons cancel'),  
  );

  return $form;
  
}

/**
 * form for merging terms
 */
function taxonomy_manager_merge_form($voc) {
  drupal_add_js(array('hideForm' => array(
    'show_button' => 'edit-merge-show', 
    'hide_button' => 'edit-merge-cancel', 
    'div' => 'merge-form')), 'setting');
  
  $form = array();
  
  $description .= t("The selected terms get merged into one term. 
    This resulting merged term can either be an existing term or a completely new term. 
    The selected terms will automatically get synomyms of the merged term and will be deleted afterwards.");
  
  $form['merge'] = array( 
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Merging of terms'), 
    '#description' => $description,
    '#prefix' => '<div id="merge-form">',
    '#suffix' => '</div>', 
  );
  
  $form['merge']['main_term'] = array(
    '#type' => 'textfield',
    '#title' => t('Resulting merged term'),
    '#required' => FALSE,
    '#autocomplete_path' => 'taxonomy/autocomplete/'. $voc->vid,
  );
  
  $options = array();
  
  switch ($voc->hierarchy) {
    //multiple hierarchy 
    case 2:  
      $options['collect_parents'] = t('Collect all parents of selected terms an add it to the merged term');
    
    //single hierarchy 
    case 1:  
      $options['collect_children'] = t('Collect all children of selected terms an add it to the merged term');
    break;
  }
  
  if ($voc->relations) {
    $options['collect_relations'] = t('Collect all relations of selected terms an add it to the merged term');
  }
  
  if (count($options) > 0) {
    $form['merge']['options'] = array( 
      '#type' => 'checkboxes', 
      '#title' => t('Options'), 
      '#options' => $options, 
    );
  }
   
  $form['merge']['submit'] = array( 
    '#type' => 'submit', 
    '#value' => t('Merge'),
    '#attributes' => array('class' => 'taxonomy-manager-buttons merge'), 
  );
   
  $form['merge']['cancel'] = array( 
    '#type' => 'button', 
    '#value' => t('Cancel'),
    '#attributes' => array('class' => 'taxonomy-manager-buttons cancel'), 
    '#theme' => 'no_submit_button', 
  );
  
  return $form;
}

/**
 * form for moving terms in hierarchies
 */
function taxonomy_manager_move_form($voc) {
  drupal_add_js(array('hideForm' => array(
    'show_button' => 'edit-move-show', 
    'hide_button' => 'edit-move-cancel',
    'div' => 'move-form')), 'setting');
  
  $form = array();
  
  $description = t("You can change the parent of one or more selected terms. 
      If you leave the autocomplete field empty, the term will be a root term.");
  
  $form['move'] = array( 
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Moving of terms'),
    '#description' => $description,
    '#prefix' => '<div id="move-form">',
    '#suffix' => '</div>',
  );
  
  if ($voc->hierarchy == 2) {
    $auto_description .= t("Separate parent terms with a comma. ");
  }
  
  $form['move']['parents'] = array(
    '#type' => 'textfield',
    '#title' => t('Parent term(s)'),
    '#description' => $auto_description,
    '#required' => FALSE,
    '#autocomplete_path' => 'taxonomy/autocomplete/'. $voc->vid,
  );
  
  $options = array();
  
  if ($voc->hierarchy == 2) {
    $options['keep_old_parents'] = t('Keep old parents and add new ones (multi-parent). Otherwise old parents get replaced.');
  }
  
  if (count($options) > 0) {
    $form['move']['options'] = array( 
      '#type' => 'checkboxes', 
      '#title' => t('Options'), 
      '#options' => $options, 
    );
  }
  
  $form['move']['submit'] = array( 
    '#type' => 'submit',
    '#attributes' => array('class' => 'taxonomy-manager-buttons move'), 
    '#value' => t('Move'),
  );
   
  $form['move']['cancel'] = array( 
    '#type' => 'button', 
    '#value' => t('Cancel'),
    '#attributes' => array('class' => 'taxonomy-manager-buttons cancel'),
    '#theme' => 'no_submit_button',  
  );
  
  return $form;
}

/**
 * form for exporting terms
 */
function taxonomy_manager_export_form($voc) {
  drupal_add_js(array('hideForm' => array(
    'show_button' => 'edit-export-show', 
    'hide_button' => 'edit-export-cancel',
    'div' => 'export-form')), 'setting');
  
  $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
  drupal_add_js($module_path .'js/csv_export.js');
  
  drupal_add_js(array('exportCSV' => array('url' => url("admin/content/taxonomy_manager/export"))), 'setting');
  
  $form = array();
  
  $form['export'] = array( 
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('CSV Export'),
    '#description' => $description,
    '#prefix' => '<div id="export-form">',
    '#suffix' => '</div>',
  );

  
  $form['export']['delimiter'] = array(
    '#type' => 'textfield',
    '#title' => t('Delimiter for CSV File'),
    '#required' => FALSE,
    '#default_value' => ";",
  );

  $options['whole_voc'] = t('Whole Vocabulary');
  $options['children'] = t('Child terms of a selected term');
  $options['root_terms'] = t('Root level terms only');

  $form['export']['options'] = array( 
    '#type' => 'radios', 
    '#title' => t('Terms to export'), 
    '#options' => $options, 
    '#default_value' => 'whole_voc',
    '#prefix' => '<div id="taxonomy_manager_export_options">',
    '#suffix' => '</div>',
  );
  
  $form['export']['csv'] = array(
    '#type' => 'textarea', 
    '#title' => t('Exported CSV'),
    '#description' => t('The generated code will appear here (per AJAX). You can copy and paste the code into a .csv file. The csv has following columns: voc id | term id | term name | description | parent id 1 | ... | parent id n'),
    '#rows' => 8,
  );
  
  $form['export']['submit'] = array( 
    '#type' => 'submit',
    '#attributes' => array('class' => 'taxonomy-manager-buttons export'), 
    '#value' => t('Export now'),
    '#theme' => 'no_submit_button', 
  );
   
  $form['export']['cancel'] = array( 
    '#type' => 'button', 
    '#value' => t('Cancel'),
    '#attributes' => array('class' => 'taxonomy-manager-buttons cancel'),
    '#theme' => 'no_submit_button',  
  );
  
  return $form;
}

/**
 * menu callback for displaying term data form
 * 
 * if this function gets called by ahah, then the term data form gets 
 * generated, rendered and return
 * otherwise, if no ahah call, redirect to original form with $vid and $tid as parameters
 *
 * @param $vid
 * @param $tid
 * @param $ahah if true, return rendered form, else redirect
 */
function taxonomy_manager_update_term_data_form($vid, $tid, $ahah = FALSE) {
  if (!$ahah) {
    drupal_goto('admin/content/taxonomy_manager/'. $vid .'/'. $tid);
  }
  $GLOBALS['devel_shutdown'] = FALSE; //prevent devel queries footprint
  
  $form = taxonomy_manager_form_term_data($tid);
  $form = form_builder('taxonomy_manager_form', $form);
  $output = drupal_render($form);
  
  print $output;
  exit();
}

/**
 * term data editing form
 *
 * @param $tid
 */
function taxonomy_manager_form_term_data($tid) {
  $term = taxonomy_get_term($tid);
  $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
  $vocabulary = taxonomy_get_vocabulary($term->vid);
  
  //prevent that title of the fieldset is too long
  $title = check_plain($term->name);
  if (strlen($title) >= 33) {
    $title = substr($title, 0, 33) . "...";
  }
  $title .= " (". $term->tid .")";
  
  $form['term_data'] = array( 
    '#type' => 'fieldset', 
    '#title' => $title,
    '#attributes' => array('id' => 'taxonomy-term-data-fieldset'),
    '#tree' => TRUE,
  );
  
  $form['term_data']['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#default_value' => $term->name,
    '#size' => 35,
    '#maxlength' => 255,
    '#required' => TRUE,
    '#weight' => -20,
    '#prefix' => '<div id="term-data-name-field">',
    '#suffix' => '</div>',
  );
  
  $form['term_data']['save_name'] = array( 
    '#value' => theme("image", $module_path ."images/document-save.png", "save", NULL),
    '#prefix' => '<div id="term-data-name-save">',
    '#suffix' => '</div><div class="clear"></div>', 
  );
  
  $form['term_data']['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $term->description,
    '#cols' => 35,
    '#rows' => 4,
    '#prefix' => '<div id="term-data-description"><div id="term-data-description-field">',
    '#suffix' => '</div>',
  );
  
  $form['term_data']['save_description'] = array( 
    '#value' => theme("image", $module_path ."images/document-save.png", "save", NULL),
    '#prefix' => '<div id="term-data-description-save">',
    '#suffix' => '</div></div><div class="clear"></div>', 
  );
  
  $form['term_data']['synonyms'] = _taxonomy_manager_form_term_data_lists($term, taxonomy_get_synonyms($term->tid), t('Synonyms'), 'synonym', FALSE); 
  $form['term_data']['synonyms']['#tree'] = TRUE;
  
  if ($vocabulary->relations) {
    $form['term_data']['relations'] = _taxonomy_manager_form_term_data_lists($term, taxonomy_get_related($term->tid), t('Relations'), 'related'); 
    $form['term_data']['relations']['#tree'] = TRUE;
  }
  
  if ($vocabulary->hierarchy) {
    $parents = taxonomy_get_parents($term->tid);
    $p_add = TRUE;
    if ((count($parents) >= 1) && ($vocabulary->hierarchy == 1)) {
      $p_add = FALSE;
    }
    $form['term_data']['parents'] = _taxonomy_manager_form_term_data_lists($term, $parents, t('Parents'), 'parent', TRUE, $p_add); 
    $form['term_data']['parents']['#tree'] = TRUE;
  }
  
  $form['term_data']['weight'] = array( 
    '#type' => 'weight', 
    '#default_value' => $term->weight, 
    '#delta' => 40,
    '#prefix' => '<div id="term-data-weight">', 
    '#suffix' => '</div>', 
    '#title' => t('Weight'), 
  );
  $form['term_data']['link'] = array('#value' => '<br />'. l(t('Go to the term page site'), taxonomy_term_path($term), array('rel' => 'tag', 'title' => $term->description, 'target' => '_blank')));
  return $form;
}

/**
 * helper function for generating tables with values and delete op and field for adding
 *
 * @param $term term object which is going to be displayed
 * @param $values array of values to show, e.g related terms, synonyms, parents, children
 * @param $header_type string to display as header
 * @param $attr attribute type to show, can be 'related', 'synonym', 'parent', 'child'
 * @param $autocomplete if true, adds autocomplete, else a textfield
 * @param $add if true, shows add operation
 * @return an form array
 */
function _taxonomy_manager_form_term_data_lists($term, $values, $header_type, $attr, $autocomplete = TRUE, $add = TRUE) {
  $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
  $rows = array();
  
  foreach ($values as $tid => $value) {
    $row = array();
    if (is_object($value)) {
      $name = $value->name;
      $id = $value->tid;
    }
    else {
      $name = $value;
      $id = $value;
    }
    $row[] = array(
      'data' => $name, 
      'class' => 'taxonomy-term-data-name',
      'id' => 'term-'. $id, 
    );
    $row[] = array(
      'data' => theme("image", $module_path ."images/list-remove.png", "term-remove", NULL, array('class' => $attr)), 
      'class' => 'taxonomy-term-data-operations', 
    );
    $rows[] = $row;
  }
  
  $headers = array(t('!type', array('!type' => $header_type)), '');
  
  
  $form['list'] = array( 
    '#value' => theme('table', $headers, $rows, array('class' => 'taxonomy-term-data-table')),
  );
  if ($add) {
    $form['add'] = array( 
      '#type' => 'textfield',
      '#prefix' => '<div class="term-data-autocomplete-line"><div class="term-data-autocomplete">',
      '#suffix' => '</div>',
      '#size' => 35,
    );
    if ($autocomplete) {
      $form['add']['#autocomplete_path'] = 'taxonomy/autocomplete/'. $term->vid;
    }
  
    $form['add_button'] = array( 
      '#value' => theme("image", $module_path ."images/list-add.png", "term-remove", NULL, array('class' => $attr)), 
      '#prefix' => '<div class="term-data-autocomplete-add">',
      '#suffix' => '</div><div class="clear"></div></div>',
    );
  }
  
  return $form;
}

/** 
 * Additional confirmation form for deletion
 */
function taxonomy_manager_terms_confirm_delete($vid, $edit) {
  $selected = array();
  if (is_array($edit['taxonomy']['manager']['tree'])) {
    $selected = _taxonomy_manager_tree_get_selected_terms($edit['taxonomy']['manager']['tree']);
  }
  else if (is_array($edit['selected_terms'])) {
    $selected = $edit['selected_terms'];
  }
  $form['selected_terms'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
  foreach ($selected as $tid) {
    $term = taxonomy_get_term($tid);
    $form['selected_terms'][$tid] = array('#type' => 'hidden', '#value' => $tid, '#prefix' => '<li>', '#suffix' => check_plain($term->name) ."</li>\n");
  }

  if (!count($selected)) {
    return array('info' => array('#value' => t('No terms selected.') .'<br />'. l(t('Back'), 'admin/content/taxonomy_manager/'. $vid))); 
  }
  if (isset($edit['delete']['options']['delete_orphans']) || isset($edit['options']['delete_orphans'])) {
    $form['options'] = array('#tree' => TRUE);
    $form['options']['delete_orphans'] = array('#type' => 'hidden', '#value' => TRUE);
  }
  $form['vid'] = array('#type' => 'hidden', '#value' => $vid);
  
  $msg = isset($form['options']['delete_orphans']) ? t('Deleting a term will delete all its children if there are any. ') : '';
  $msg .= t('This action cannot be undone.');
  return confirm_form($form,
                  t('Are you sure you want to delete the following terms: '),
                  'admin/content/taxonomy_manager/'. $vid,
                  $msg,
                  t('Delete terms'),
                  t('Cancel'));
}

/**
 * Submit handler for deletion confirmation form
 */
function taxonomy_manager_terms_confirm_delete_submit($form_id, $form_values) {
  taxonomy_manager_delete_terms($form_values['selected_terms'], $form_values['options']);
  drupal_set_message("Selected terms deleted"); 
  return 'admin/content/taxonomy_manager/'. $form_values['vid'];
}

/**
 * validates the form
 */
function taxonomy_manager_form_validate($form_id, $form_values) {
  $selected_tids = array();
  $selected_tids = $form_values['taxonomy']['manager']['tree']['selected_terms'];

  switch ($form_values['op']) {
    case t('Add'): 
      //check for parents concerning voc settings
      $voc = taxonomy_get_vocabulary($form_values['vid']);
      
      if ($voc->hierarchy == 0 && count($selected_tids) > 0) {
        form_set_error('add', t('Please unselect terms in the list before adding new terms. This vocabulary doesn\'t provide hierarchies.'));
      }
      else if ($voc->hierarchy == 1 && count($selected_tids) > 1) {
        form_set_error('add', t('Please only select one term in the list (at maximum) as parent. This vocabulary provides only single hirarchies.'));
      }
      
      break;
    
    case t('Merge'): 
      $main_terms = array();
      $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
      preg_match_all($regexp, $form_values['merge']['main_term'], $matches);
      $main_terms = $matches[1];

      if (!is_array($main_terms) || count($main_terms) == 0 || empty ($main_terms[0])) {
        form_set_error('merge][main_term', t("Please enter a name into %title", array('%title' => "'". t('Main term') ."'")));
      }
      else if (count($main_terms) > 1) {
        form_set_error('merge][main_term', t("Please only enter single names into %title", array('%title' => "'". t('Main term') ."'")));
      }
      
      if (count($selected_tids) < 1) {
        form_set_error('merge', t("Please selected terms you want to merge"));
      }
      else if (count($selected_tids) > 50) {
        form_set_error('merge', t("Please select less than 50 terms to merge. Merging to many terms in one step can cause timeouts and inconsistent database states"));
      }
      break; 
    
    case t('Move'): 
      if (count($selected_tids) < 1) {
        form_set_error('move', t("Please selected terms you want to move in the hierarchy"));
      }
      break;
  }
}

/**
 * submits the taxonomy manager form
 */
function taxonomy_manager_form_submit($form_id, $form_values) {  
  $selected_tids = array(); 
  $selected_tids = $form_values['taxonomy']['manager']['tree']['selected_terms'];

  switch ($form_values['op']) {
    case t('Search'): 
      $search_string = $form_values['search']['field'];
      $terms = array();
      $terms = taxonomy_manager_autocomplete_tags_get_tids($search_string, $form_values['vid'], FALSE);
      $term = array_shift($terms);
      $tid = $term['tid'];
      if ($tid) {
        drupal_goto('admin/content/taxonomy_manager/'. $form_values['vid'] .'/'. $tid);
      }
      else {
        drupal_goto('admin/content/taxonomy_manager/'. $form_values['vid'] .'/0/'. $search_string);
      }
      break;
      
    case t('Add'):
      $terms = array();
      foreach ($form_values['add']['term'] as $value) {
        if (!empty($value)) {
          $terms[] = $value;
        }
      }
      if ($form_values['add']['mass']['mass_add']) {
        foreach (explode ("\n", str_replace("\r", '', $form_values['add']['mass']['mass_add'])) as $term) {
          if ($term) {
            $terms[] = $term;
          }
        }
      }
      foreach ($terms as $name) {
        $term = array();
        $term['name'] = $name;
        $term['vid'] = $form_values['vid'];
        $term['parent'] = $selected_tids;
        taxonomy_save_term($term);
      }
      drupal_set_message("Terms added: ". implode(', ', $terms));
      break;
      
    case t('Merge'):   
      $main_terms = taxonomy_manager_autocomplete_tags_get_tids($form_values['merge']['main_term'], $form_values['vid']);
      $main_term = array_shift($main_terms);
      $new_inserted = false;
      if ($main_term['new']) $new_inserted = true;
      $main_term_tid = $main_term['tid'];
      taxonomy_manager_merge($main_term_tid, $selected_tids, $form_values['merge']['options'], $new_inserted);
      drupal_set_message("Terms merged");
      break;
    
    case t('Move'): 
      $typed_parents = taxonomy_manager_autocomplete_tags_get_tids($form_values['move']['parents'], $form_values['vid']);
      $parents = array();
      foreach ($typed_parents as $parent_info) {
        $parents[] = $parent_info['tid'];
      }
      if (count($parents) == 0) $parents[0] = 0; //if empty, delete all parents
      taxonomy_manager_move($parents, $selected_tids, $form_values['move']['options']);
      
      drupal_set_message("Terms moved");
      break;
      
    case t('Export now'):
      drupal_set_message('Needs JavaScript enabled');
      break;
      
  }
  
  drupal_goto('admin/content/taxonomy_manager/'. $form_values['vid']);
}

/**
 * callback handler for updating term data
 *
 * @param $vid
 */
function taxonomy_manager_term_data_edit($vid) {
  $param = $_POST;
  
  $tid = $param['tid'];
  $values = $param['value'];
  $attr_type = $param['attr_type'];
  $op = $param['op'];
  
  $term = taxonomy_get_term($tid);
  
  if ($term->vid != $vid) {
    return;
  }
  
  if ($op == 'add') {
    if ($attr_type == 'synonym') {
      $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
      preg_match_all($regexp, $values, $matches);
      $synonyms = array_unique($matches[1]);
      $values = array();
      foreach ($synonyms as $syn) {
        $values[] = trim($syn);
      }
    }
    else {
      $typed_term_tids = array();
      $typed_term_tids = taxonomy_manager_autocomplete_tags_get_tids($values, $term->vid);
      $values = array();
      foreach ($typed_term_tids as $term_info) {
        $values[] = $term_info['tid'];
      }
    }
  }
  
  switch ($attr_type) {
    case 'name': 
      db_query("UPDATE {term_data} SET name = '%s' WHERE tid = %d", $values, $tid);
      break;
      
    case 'description': 
      db_query("UPDATE {term_data} SET description = '%s' WHERE tid = %d", $values, $tid);
      break;
    
    case 'parent':
      if (!is_array($values)) $values = array($values);
      foreach ($values as $value) {
        db_query("DELETE FROM {term_hierarchy} WHERE parent = %d AND tid = %d", $value, $tid);
        if ($op == 'add') {
          db_query("INSERT INTO {term_hierarchy} (parent, tid) VALUES (%d, %d)", $value, $tid);
        }
      }
      if ($op == 'delete') {
        $parents = taxonomy_get_parents($tid);
        if (count($parents) == 0) {
          //ensure that a term has a least parent 0
          db_query("DELETE FROM {term_hierarchy} WHERE parent = 0 AND tid = %d", $tid);
          db_query("INSERT INTO {term_hierarchy} (parent, tid) VALUES (0, %d)", $tid);
        }
      }
      else if ($op == 'add') {
        db_query("DELETE FROM {term_hierarchy} WHERE parent = 0 AND tid = %d", $tid);
      }
      break; 
    
    case 'related': 
      if (!is_array($values)) $values = array($values);
      foreach ($values as $value) {
        if ($value != 0) {
          db_query("DELETE FROM {term_relation} WHERE tid1 = %d AND tid2 = %d", $tid, $value);
          db_query("DELETE FROM {term_relation} WHERE tid2 = %d AND tid1 = %d", $tid, $value);
          if ($op == 'add') {
            db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $tid, $value);
          }
        }
      }
      break;
    
    case 'synonym': 
      if (!is_array($values)) $values = array($values);
      foreach ($values as $value) { 
        db_query("DELETE FROM {term_synonym} WHERE tid = %d AND name = '%s'", $tid, $value);
        if ($op == 'add') {
          db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $tid, $value);  
        }
      }
      break;
    
    case 'weight': 
      if (is_numeric($values)) {
        db_query("UPDATE {term_data} SET weight = %d WHERE tid = %d", $values, $tid);
      }
      break;
  }

  module_invoke_all('taxonomy', 'update', 'term', (array) taxonomy_get_term($tid));
  
  exit();
}


 /**
 * Defines a settings form.
 */
function taxonomy_manager_settings() {
  $form['taxonomy_manager_disable_mouseover_weights'] = array( 
    '#type' => 'checkbox', 
    '#title' => t('Disable mouse-over effect for weight arrows'), 
    '#default_value' => variable_get('taxonomy_manager_disable_mouseover_weights', 0),
    '#description' => t('Disabeling this feature speeds up the Taxonomy Manager'),
  );
  $form['taxonomy_manager_pager_tree_page_size'] = array( 
    '#type' => 'select', 
    '#title' => t('Select how many terms get listet on one page (pager count)'), 
    '#options' => array(25 => 25, 50 => 50, 75 => 75, 100 => 100, 150 => 150, 200 => 200, 250 => 250, 300 => 300, 400 => 400, 500 => 500),
    '#default_value' => variable_get('taxonomy_manager_pager_tree_page_size', 50),
    '#description' => t('Huge page counts can slow down the Taxonomy Manager'),
  );
  return system_settings_form($form);
}

/**
 * checks if voc has terms
 *
 * @param $vid voc id
 * @return true, if terms already exists, else false
 */
function _taxonomy_manager_voc_is_empty($vid) {
  $count = db_result(db_query_range("SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE vid = %d AND h.parent = 0", $vid, 0, 1));
  if ($count == 0) return true;
  return false;
}


/**
 * deletes terms from the database
 * optional orphans (terms where parent get deleted) can be deleted as well
 * 
 * (difference to taxonomy_del_term: deletion of orphans optional)
 *
 * @param $tids array of term id to delete
 * @param $options associative array with options
 *   if $options['delete_orphans'] is true, orphans get deleted
 */
function taxonomy_manager_delete_terms($tids, $options = array()) {
  if (!is_array($tids)) array($tids);
  while (count($tids) > 0) {
    $orphans = array();
    foreach ($tids as $tid) {      
      if ($children = taxonomy_get_children($tid)) {
        foreach ($children as $child) {
          $parents = taxonomy_get_parents($child->tid);
          if ($options['delete_orphans']) {
            if (count($parents) == 1) {
              $orphans[] = $child->tid;
            }
          }
          else {
            db_query("DELETE FROM {term_hierarchy} WHERE tid = %d AND parent = %d", $child->tid, $tid);
            if (count($parents) == 1) {
              if (!db_result(db_query("SELECT COUNT(*) FROM {term_hierarchy} WHERE tid = %d AND parent = 0", $child->tid))) {
                db_query("INSERT INTO {term_hierarchy} (parent, tid) VALUES(0, %d)", $child->tid);          
              }
            }
          }
        }
      }
      
      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
    
      $term = (array) taxonomy_get_term($tid);
      module_invoke_all('taxonomy', 'delete', 'term', $term);
      $tids = $orphans;
    }
  } 
}


/**
 * moves terms in hierarchies to other parents
 *
 * @param $parents 
 *   array of parent term ids to where children can be moved
 *   array should only contain more parents if multi hiearchy enabled 
 *   if array contains 0, terms get placed to first (root) level
 * @param $children
 *   array of term ids to move
 * @param $options
 *   array of additional options for moving
 *   'keep_old_parents': if true, existing parents don't get deleted (only possible with multi hierarchies)
 */
function taxonomy_manager_move($parents, $children, $options = array()) {
  if (!is_array($parents)) array($parents);
  
  foreach ($children as $child) {
    if (!$options['keep_old_parents']) {
      db_query("DELETE FROM {term_hierarchy} WHERE tid = %d", $child);
    }
    foreach ($parents as $parent) {
      db_query("INSERT INTO {term_hierarchy} (parent, tid) VALUES (%d, %d)", $parent, $child);
    }
  }
}

/**
 * merges terms into another term (main term), all merged term get added
 * to the main term as synonyms. 
 * term_node relations are updated automatically (node with one of merging terms gets main term assigned)
 * after all opterions are done (adding of hierarchies, relations is optional) merging
 * terms get deleted
 *
 * @param $main_term
 *   id of term where other terms get merged into
 * @param $merging_terms
 *   array of term ids, which get merged into main term and afterwards deleted
 * @param $options
 *   array with additional options, possible values:
 *   'collect_parents': if true, all parents of merging terms get added to main term (only possible with multi hierarchies)
 *   'collect_children': if true, all children of merging terms get added to main term
 *   'collect_relations': if true, all relations of merging terms are transfered to main term
 */
function taxonomy_manager_merge($main_term, $merging_terms, $options = array(), $new_inserted = TRUE) {
  $vid = db_result(db_query("SELECT vid FROM {term_data} WHERE tid = %d", $main_term));
  $voc = taxonomy_get_vocabulary($vid);
  $merging_terms_parents = array();
  
  if ($voc->hierarchy == 2 && $new_inserted && $options['collect_parents']) {
    db_query("DELETE FROM {term_hierarchy} WHERE parent = 0 AND tid = %d", $main_term);
  }
  
  //TODO: add hook, so that other modules can consider changes
  foreach ($merging_terms as $merge_term) {
    if ($merge_term != $main_term) {
      //update node-relations
      $sql = db_query("SELECT nid FROM {term_node} WHERE tid = %d", $merge_term);
      while ($obj = db_fetch_object($sql)) {
        $nid = $obj->nid;
        db_query("DELETE FROM {term_node} WHERE tid = %d AND nid = %d", $merge_term, $nid);
        if (!db_result(db_query("SELECT COUNT(*) FROM {term_node} WHERE tid = %d AND nid = %d", $main_term, $nid))) {
          db_query("INSERT INTO {term_node} (tid, nid) VALUES (%d, %d)", $main_term, $nid);
       }
      }
    
      if ($voc->hierarchy == 1) {  //sinlge hierarchy
        $parents = taxonomy_get_parents($merge_term);
        $parent = array_shift($parents);
        $merging_terms_parents[$parent->tid] = $parent->tid;
      }
      if ($options['collect_parents']) {
        $parents = taxonomy_get_parents($merge_term);
        foreach ($parents as $parent_tid => $parent_term) {
          if (!db_result(db_query("SELECT COUNT(*) FROM {term_hierarchy} WHERE tid = %d AND parent = %d", $main_term, $parent_tid))) {
             db_query("INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)", $main_term, $parent_tid);
          }
        }
      }
    
      if ($options['collect_children']) {
        $children = taxonomy_get_children($merge_term);
        foreach ($children as $child_tid => $child_term) {
          if (!db_result(db_query("SELECT COUNT(*) FROM {term_hierarchy} WHERE tid = %d AND parent = %d", $child_tid, $main_term))) {
            db_query("INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)", $child_tid, $main_term);
          }
        }
      }
    
      if ($options['collect_relations']) {
        $relations = taxonomy_get_related($merge_term);
        foreach ($relations as $related_tid => $relation) {
          if ($relation->tid1 == $merge_term) {
            if (!db_result(db_query("SELECT COUNT(*) FROM {term_relation} WHERE tid1 = %d AND tid2 = %d", $main_term, $related_tid))) {
              db_query("INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)", $main_term, $related_tid);
            }
          }
          else if ($relation->tid2 == $merge_term) {
            if (!db_result(db_query("SELECT COUNT(*) FROM {term_relation} WHERE tid2 = %d AND tid1 = %d", $main_term, $related_tid))) {
              db_query("INSERT INTO {term_relation} (tid2, tid1) VALUES (%d, %d)", $main_term, $related_tid);
            }
          }
        }
      }
    
      //save merged term (and synonomys of merged term) as synonym
      $term = taxonomy_get_term($merge_term);
      $merge_term_synonyms = taxonomy_get_synonyms($merge_term);
      $merge_term_synonyms[] = $term->name;
      foreach ($merge_term_synonyms as $syn) {
        if (!db_result(db_query("SELECT COUNT(*) FROM {term_synonym} WHERE tid = %d AND name = '%s'", $main_term, $syn))) {
          db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $main_term, $syn);
        }
      }
    
      taxonomy_manager_delete_terms(array($merge_term));
    }
  }

  if ($voc->hierarchy == 1 && count($merging_terms_parents) == 1 && $new_inserted) {
    db_query("UPDATE {term_hierarchy} SET parent = %d WHERE tid = %d", array_shift($merging_terms_parents), $main_term);
  }
  
  taxonomy_manager_merge_history_update($main_term, $merging_terms);
}

/**
 * inserts merging information (main_tid - merged_tid ) into taxonomy_manager_merge
 * and updates cache, which is used to reconstructs taxonomy/term pages
 *
 * @param $main_tid term if of main term
 * @param $merged_tids array of merged term ids
 */
function taxonomy_manager_merge_history_update($main_tid, $merged_tids) {
  if (!is_array($merged_tids)) (array) $merged_tids;
  
  foreach ($merged_tids as $merged_tid) {
    if ($merged_tid != $main_tid) {
      //check if merged term has been a main term once before
      $check_merged = db_result(db_query("SELECT COUNT(*) FROM {taxonomy_manager_merge} WHERE main_tid = %d", $merged_tid));
  
      if ($check_merged) {
        db_query("UPDATE {taxonomy_manager_merge} SET main_tid = %d WHERE main_tid = %d", $main_tid, $merged_tid);
      }
      //insert into merging history
      db_query("INSERT INTO {taxonomy_manager_merge} (main_tid, merged_tid) VALUES (%d, %d)", $main_tid, $merged_tid);
    }
  }
  taxonomy_manager_merge_history_update_cache();
}

/**
 * sets / updates cache for merging history
 */
function taxonomy_manager_merge_history_update_cache() {
  $merged_terms = array();
  
  $result = db_query("SELECT * FROM {taxonomy_manager_merge}");
  while ($data = db_fetch_object($result)) {
    $merged_terms[$data->merged_tid] = $data->main_tid; 
  }
  cache_set('taxonomy_manager_merge', 'cache', serialize($merged_terms));
}

/**
 * helper function for getting out the main term of former merged term (which no 
 * long exists)
 *
 * @param $tid of which the main term has to be evaluated
 * @return term id of main term, if exists, else 0
 */
function taxonomy_manager_merge_get_main_term($tid) {
  $merged_terms = array();
  $cache = cache_get('taxonomy_manager_merge', 'cache');
  if (!$cache) {
    taxonomy_manager_merge_history_update_cache();
  }
  $merged_terms = unserialize($cache->data);
  return $merged_terms[$tid];
}

/**
 * menu callback 
 * 
 * replaces taxonomy_mangager_term_page, because we have to consider that the
 * url may contain former merged terms, which no longer exists
 * every given tid gets checked, if it has been merged. if yes, the tid gets replaced
 * with tid of main term and afterwards passed to default taxonomy_manager_term_page
 *
 * @param $str_tids
 * @param $depth
 * @param $op
 */
function taxonomy_manager_term_page($str_tids = '', $depth = 0, $op = 'page') {
  $tids = taxonomy_terms_parse_string($str_tids);
  if ($tids['operator'] == 'and' || $tids['operator'] == 'or') {
   $new_tids = array();
   foreach ($tids['tids'] as $tid) {
     //get cached main term, if not merged, returns 0
     $main_term = taxonomy_manager_merge_get_main_term($tid);
     $new_tids[] = ($main_term) ? $main_term : $tid;
   }
   if ($tids['operator'] == 'and') {
     $operator = ',';
   }
   else if ($tids['operator'] == 'or') {
     $operator = '+';
   }
   $new_tids_str = implode($operator, $new_tids);
   return taxonomy_term_page($new_tids_str, $depth, $op);
  }
  else {
    drupal_not_found();
  }
}

/**
 * helper function for getting out of term ids from autocomplete fields
 * non-exsiting terms get inserted autmatically
 * (part of taxonomy_node_save)
 *
 * @param $typed_input input string of form field
 * @param $vid vocabulary id
 * @return array of term ids
 */
function taxonomy_manager_autocomplete_tags_get_tids($typed_input, $vid, $insert_new = TRUE) {
  $tids = array();
  
  $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
  preg_match_all($regexp, $typed_input, $matches);
  $typed_terms = array_unique($matches[1]);

  foreach ($typed_terms as $typed_term) {
    $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
    $typed_term = trim($typed_term);
    if ($typed_term == "") { continue; }

    $possibilities = taxonomy_get_term_by_name($typed_term);
    $typed_term_tid = NULL; // tid match if any.
    foreach ($possibilities as $possibility) {
      if ($possibility->vid == $vid) {
        $typed_term_tid = $possibility->tid;
        $tids[$typed_term_tid]['tid'] = $typed_term_tid;
      }
    }

    if (!$typed_term_tid && $insert_new) {
      $edit = array('vid' => $vid, 'name' => $typed_term);
      $status = taxonomy_save_term($edit);
      $typed_term_tid = $edit['tid'];
      $tids[$typed_term_tid]['tid'] = $typed_term_tid;
      $tids[$typed_term_tid]['new'] = TRUE;
    }
  }
  return $tids;
}

/**
 * callback for updating weights
 * data send through AJAX, $_POST
 * $_POST[$tid] => $weight
 *
 */
function taxonomy_manager_update_weights($vid) {
  $weights = $_POST;
  if (is_array($weights)) {
    foreach ($weights as $tid => $weight) {
      if (is_numeric($tid) && is_numeric($weight)) {
        if(_taxonomy_manager_tree_term_valid($tid, $vid)) {
          db_query("UPDATE {term_data} SET weight = %d WHERE tid = %d", $weight, $tid);
        }
      }
    }
  }
  exit();
}

/** 
 * AJAX Callback that returns the CSV Output
 */
function taxonomy_manager_export() {
  $edit = $_POST;
  $output = taxonomy_manager_export_csv($edit['delimiter'], $edit['vid'], $edit['tid'], array($edit['option'] => TRUE));
  print $output;
  exit();
}

/** 
 * Generates the CVS Ouput
 */
function taxonomy_manager_export_csv($delimiter = ";", $vid, $selected_tid = 0, $options = array()) {
  $tree = taxonomy_manager_export_get_tree($vid, $selected_tid, $options);

  foreach ($tree as $term) {
    $array = array();
    $array[] = '"'. $term->vid .'"';
    $array[] = '"'. $term->tid .'"';
    $array[] = '"'. $term->name .'"';
    $array[] = '"'. $term->description .'"';
    foreach ($term->parents as $parent) {
      $array[] = '"'. $parent .'"';
    }
    $output .= implode($delimiter, $array) ."\n";
  }
  return $output;
}

/**
 * Helper for cvs export to get taxonomy tree
 */
function taxonomy_manager_export_get_tree($vid, $selected_tid, $options) {
  $tree = array();

  if ($options['whole_voc']) {
    $tree = taxonomy_get_tree($vid);
  }
  else if ($options['children'] && $selected_tid) {
    $tree = taxonomy_get_tree($vid, $selected_tid);
  }
  else if ($options['root_terms']) {
    $tree = taxonomy_get_tree($vid, 0, -1, 1);
  }
  
  return $tree;
}

/**
 * theme function for taxonomy manager form
 */
function theme_taxonomy_manager_form($form) {
  $pager = theme('pager', NULL, variable_get('taxonomy_manager_pager_tree_page_size', 50), 0);
  $tree = drupal_render($form['taxonomy']);
  $term_data = drupal_render($form['term_data']);
  $top = drupal_render($form);
  $output = $top . $pager;
  $output .= '<div id="taxonomy-manager">';
  $output .= $tree;
  $output .= '<div id="taxonomy-term-data">';
  $output .= $term_data;
  $output .= '</div>';
  $output .= '<div class="clear" />';
  $output .= '</div>';
  
  return $output;
}

/**
 * themes a real button form type (no form submit)
 */
function theme_no_submit_button($element) {
  // Make sure not to overwrite classes.
  if (isset($element['#attributes']['class'])) {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
  }
  else {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'];
  }

  return '<input type="button" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ')  .'id="'. $element['#id'].'" value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
}

/**
 * themes a image type button
 */
function theme_taxonomy_manager_image_button($element) {

  //Make sure not to overwrite classes
  if (isset($element['#attributes']['class'])) {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
  }
  else {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'];
  }

  // here the novelty begins: check if #button_type is normal submit button or image button
  $return_string = '<input ';
  if ($element['#button_type'] == 'image') {
    $return_string .= 'type="image" ';
  }
  else {
    $return_string .= 'type="submit" ';
  }
  $return_string .= (empty($element['#id']) ? '' : 'id="'. $element['#id'] .'" ');
  $return_string .= (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ');
  $return_string .= 'value="'. check_plain($element['#value']) .'" ';
  $return_string .= drupal_attributes($element['#attributes']) ." />\n";
 
  return $return_string;

}

/******************************************
 * TAXONOMY TREE FORM ELEMENT DEFINITION
 * 
 * how to use:
 * $form['name'] = array( 
 *   '#type' => 'taxonomy_manager_tree', 
 *   '#vid' => $vid,
 * );
 * 
 * additional parameter:
 *   #pager: TRUE / FALSE, 
 *     whether to use pagers (drupal pager, load of nested children, load of siblings) 
 *     or to load the whole tree on page generation
 *   #parent: only children on this parent will be loaded
 *   #siblings_page: current page for loading pf next siblings, internal use
 * 
 * defining term operations:
 *   to add values (operations,..) to each term, add a function, which return a form array
 *   'tree_form_id'_operations
 * 
 * how to retrieve selected values:
 *   selected terms ids are available in validate / submit function in
 *   $form_values['name']['selected_terms'];
 * 
 ******************************************/

/**
 * Implementation of hook_elements
 */
function taxonomy_manager_elements() {
  $type['taxonomy_manager_tree'] = array( 
    '#input' => TRUE, 
    '#process' => array('taxonomy_manager_tree_process_elements' => array()), 
    '#tree' => TRUE,
  );
  
  return $type;
}

/**
 * Processes the tree form element
 * 
 * @param $element
 * @return the tree element
 */
function taxonomy_manager_tree_process_elements($element) {
  $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
  $id = form_clean_id(implode('-', $element['#parents']));
  $vid = $element['#vid'];
  
  if (!$element['#parent'] && !$element['#siblings_page']) {
    drupal_add_css($module_path .'css/taxonomy_manager.css');
    drupal_add_js($module_path .'js/tree.js');
    drupal_add_js($module_path .'js/childForm.js');
    drupal_add_js($module_path .'js/siblingsForm.js');
    
    drupal_add_js(array('siblingsForm' => array('url' => url('admin/content/taxonomy_manager/siblingsform'), 'modulePath' => $module_path)), 'setting');
    drupal_add_js(array('childForm' => array('url' => url('admin/content/taxonomy_manager/childform'), 'modulePath' => $module_path)), 'setting');
    drupal_add_js(array('taxonomy_manager' => array('modulePath' => (url($module_path) == $module_path) ? $module_path : (base_path() . $module_path))), 'setting');
    drupal_add_js(array('taxonomytree' => array('id' => $id, 'vid' => $vid)), 'setting');
  }
  
  if (!is_array($element['#operations'])) {
    $opertions_callback = implode('_', $element['#parents']) .'_operations';
    if (function_exists($opertions_callback)) {
      $element['#operations'] = $opertions_callback();
    }
  }
  if (!isset($element['#link'])) {
    $link_callback = implode('_', $element['#parents']) .'_link';
    if (function_exists($link_callback)) {
      $element['#link'] = $link_callback($vid);
    }
  }
  
  
  $tree = _taxonomy_manager_tree_get_item($element['#vid'], $element['#parent'], $element['#pager'], $element['#siblings_page'], $element['#search_string']);
  
  if ($element['#pager'] && !($element['#parent'] || $element['#siblings_page'])) {
    $element['pager'] = array('#value' => theme('pager', NULL, variable_get('taxonomy_manager_pager_tree_page_size', 50)));
  }
  $element['#tree'] = TRUE;
  $element['#id'] = $id;
  $element['#validate'] = array('taxonomy_manager_tree_validate' => array());
  
  $element['selected_terms'] = array('#type' => 'value', '#value' => array());
  
  taxonomy_manager_tree_build_form($index = 0, $tree, $element['#elements'], $element, $element['#parents'], !$element['#pager'], $element['#siblings_page']);

  return $element;
}

/**
 * loads tree with terms (depending on various settings)
 *
 * @param $vid
 * @param $parent
 * @param $pager
 * @param $siblings_page
 * @return array with term elements
 */
function _taxonomy_manager_tree_get_item($vid, $parent = 0, $pager = FALSE, $siblings_page = 0, $search_string = NULL) {
  $tree = array();
  if ($pager) {
    if ($parent || $siblings_page) {
      $start = ($siblings_page-1) * variable_get('taxonomy_manager_pager_tree_page_size', 50);
      $result = db_query_range("SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE vid = %d AND h.parent = %d ORDER BY weight, name", $vid, $parent, $start, variable_get('taxonomy_manager_pager_tree_page_size', 50));
    }
    else {
      if ($search_string) {
        $result = pager_query("SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE vid = %d AND h.parent = 0 AND name LIKE ('%%%s%%') ORDER BY weight, name", variable_get('taxonomy_manager_pager_tree_page_size', 50), 0, NULL, array($vid, $search_string));
      }
      else {
        $result = pager_query("SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE vid = %d AND h.parent = 0 ORDER BY weight, name", variable_get('taxonomy_manager_pager_tree_page_size', 50), 0, NULL, array($vid));
      }
    }
    
    while ($term = db_fetch_object($result)) {
      $tree[] = $term;
    }
  }
  else {
    $tree = taxonomy_get_tree($vid, $parent);
  }

  return $tree;
}

/**
 * recursive function for building nested form array 
 * with checkboxes and weight forms for each term
 * 
 * nested form array are allways appended to parent-form['children'] 
 *
 * @param $index current index in tree, start with 0
 * @param $tree of terms (generated by taxonomy_get_tree)
 * @return a form array 
 */
function taxonomy_manager_tree_build_form(&$index, $tree, &$form, $root_form, $parents = array(), $build_subtrees = TRUE, $page = 0) {

  $current_depth = $tree[$index]->depth;
  while ($index < count($tree) && $tree[$index]->depth >= $current_depth) {
    $term = $tree[$index];
    
    $attributes = array();
    
    $this_parents = $parents;
    $this_parents[] = $term->tid;
    
    $form[$term->tid]['checkbox'] = array(
      '#type' => 'checkbox', 
      '#title' => $term->name, 
      '#return_value' => 1,
      '#theme' => 'taxonomy_manager_tree_checkbox',
    );
    if ($root_form['#link']) {
      $form[$term->tid]['checkbox']['#link'] = $root_form['#link'] .'/'. $term->tid;
    }
    
    $form[$term->tid]['weight'] = array('#type' => 'hidden', '#value' => $term->weight, '#attributes' => array('class' => 'weight-form'));
    $form[$term->tid]['tid'] = array('#type' => 'hidden', '#value' => $term->tid, '#attributes' => array('class' => 'term-id'));
    
    if (is_array($root_form['#operations'])) {
      $form[$term->tid]['operations'] = $root_form['#operations'];
    }
    
    if ($page) {
      if ($index == (variable_get('taxonomy_manager_pager_tree_page_size', 50) - 1)) {
        $module_path = drupal_get_path('module', 'taxonomy_manager') .'/';
        $form[$term->tid]['has-more-siblings'] = array( 
          '#type' => 'markup', 
          '#value' => theme("image", $module_path ."images/2downarrow.png", "more", NULL, array('class' => 'load-siblings')), 
        );
        $form[$term->tid]['page'] = array( 
          '#type' => 'hidden', 
          '#value' => $page, 
          '#attributes' => array('class' => 'page'), 
        );
        $next_count = _taxonomy_manager_tree_get_next_siblings_count($term->vid, $page, $root_form['#parent']);
        $form[$term->tid]['next_count'] = array('#value' => $next_count);
        $form[$term->tid]['#attributes']['class'] .= 'has-more-siblings ';
      }
    }
    
    _taxonomy_manager_tree_element_set_params($this_parents, $form[$term->tid]);
    
    $index++;
    
    if ($build_subtrees) {
      if ($tree[$index]->depth > $current_depth) {
        $form[$term->tid]['#attributes']['class'] .= _taxonomy_manager_tree_term_get_class($index-1, $tree);
        taxonomy_manager_tree_build_form($index, $tree, $form[$term->tid]['children'], $root_form, array_merge($this_parents, array('children')));
      }
      else {
        if ((count($tree)-1 == $index-1) || ($tree[$index]->depth < $current_depth)) {
          $form[$term->tid]['#attributes']['class'] .= 'last ';
        }
      }
    }
    else {
      if (_taxonomy_manager_tree_term_has_children($term->tid)) {
        $form[$term->tid]['#attributes']['class'] .= 'has-children ';
        if ($index-1 == count($tree)-1) {
          $form[$term->tid]['#attributes']['class'] .= 'lastExpandable ';
        }
        else {
          $form[$term->tid]['#attributes']['class'] .= 'expandable ';
        }
      }
      else {
        if ($index-1 == count($tree)-1) {
          $form[$term->tid]['#attributes']['class'] .= 'last ';
        }
      }
    }
  }
}

/**
 * adds #id and #name to all form elements
 *
 * @param $parents
 * @param $form
 */
function _taxonomy_manager_tree_element_set_params($parents, &$form) {
  foreach (element_children($form) as $field_name) {
    $field_parents = array_merge($parents, array($field_name));
    $form[$field_name]['#tree'] = TRUE;
    $form[$field_name]['#post'] = array();
    $form[$field_name]['#parents'] = $field_parents;
    $form[$field_name]['#id'] = form_clean_id('edit-'. implode('-', $field_parents));
    $form[$field_name]['#name'] = array_shift($field_parents) .'['. implode('][', $field_parents) .']';
  }
}

/**
 * calculates class type (expandable, lastExpandable) for current element
 *
 * @param $current_index in tree array
 * @param $tree array with terms
 * @return expandable or lastExpandable
 */
function _taxonomy_manager_tree_term_get_class($current_index, $tree) {
  $class = '';
  $term = $tree[$current_index];
  $next = $tree[++$current_index];
  $children = false;
  while ($next->depth > $term->depth) {
    $children = true;
    $next = $tree[++$current_index];
  }
  
  if ($next->depth == $term->depth) {
      $class = 'expandable ';
  }
  else {
      $class = 'lastExpandable ';
  }
  return $class;
}

/**
 * checks if a term has children
 *
 * @param $tid
 * @return true, if term has children, else false
 */
function _taxonomy_manager_tree_term_has_children($tid) {
  $count = db_result(db_query("SELECT COUNT(tid) FROM {term_hierarchy} WHERE parent = %d", $tid));
  if ($count == 0) {
    return false;
  }
  return true;
}

/**
 * calculates number of next siblings if using paging
 *
 * @param $vid
 * @param $page
 * @param $parent
 * @return next page size
 */
function _taxonomy_manager_tree_get_next_siblings_count($vid, $page, $parent = 0) {
  $count = db_result(db_query("SELECT COUNT(t.tid) FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE vid = %d AND h.parent = %d", $vid, $parent));
  $current_count = variable_get('taxonomy_manager_pager_tree_page_size', 50) * $page;
  $diff = $count - $current_count;
  
  if ($diff > variable_get('taxonomy_manager_pager_tree_page_size', 50)) {
    $diff = variable_get('taxonomy_manager_pager_tree_page_size', 50);
  }
  
  return $diff;
}

/**
 * callback for generating and rendering nested child forms (AHAH)
 *
 * @param $tree_id 
 * @param $parent term id of parent, that is expanded and of which children have to be loaded
 */
function taxonomy_manager_tree_build_child_form($tree_id, $vid, $parent) { 
  $GLOBALS['devel_shutdown'] = FALSE; //prevent devel queries footprint
  
  $child_form = array( 
    '#type' => 'taxonomy_manager_tree', 
    '#vid' => $vid, 
    '#parent' => $parent, 
    '#pager' => TRUE,
  );
  if (!$root_level) {
    $child_form['#siblings_page'] = 1;
  }
  
  $opertions_callback = str_replace('-', '_', $tree_id) .'_operations';
  if (function_exists($opertions_callback)) {
    $child_form['#operations'] = $opertions_callback();
  }
  
  $link_callback = str_replace('-', '_', $tree_id) .'_link';
  if (function_exists($link_callback)) {
    $child_form['#link'] = $link_callback($vid);
  }
  
  _taxonomy_manager_tree_sub_forms_set_parents($tree_id, $parent, $child_form);
  
  $child_form = form_builder('taxonomy_manager_form', $child_form);
  
  print drupal_render($child_form);

  exit(); 
}

/**
 * callback for generating and rendering next siblings terms form (AHAH)
 *
 * @param $tree_id
 * @param $page current page
 * @param $prev_tid last sibling, that appears
 * @param $parent if in hierarchies, parent id
 */
function taxonomy_manager_tree_build_siblings_form($tree_id, $page, $prev_tid, $parent = 0) {
  $GLOBALS['devel_shutdown'] = FALSE; //prevent devel queries footprint
  
  $vid = db_result(db_query("SELECT vid FROM {term_data} WHERE tid = %d", $prev_tid));
    
  $siblings_form = array( 
    '#type' => 'taxonomy_manager_tree', 
    '#vid' => $vid, 
    '#parent' => $parent, 
    '#pager' => TRUE, 
    '#siblings_page' => $page+1,
  );
  
  $opertions_callback = str_replace('-', '_', $tree_id) .'_operations';
  if (function_exists($opertions_callback)) {
    $siblings_form['#operations'] = $opertions_callback();
  }
  
  $link_callback = str_replace('-', '_', $tree_id) .'_link';
  if (function_exists($link_callback)) {
    $siblings_form['#link'] = $link_callback($vid);
  }
  
  _taxonomy_manager_tree_sub_forms_set_parents($tree_id, $parent, $siblings_form);
  
  $siblings_form = form_builder('taxonomy_manager_form', $siblings_form);
  
  $output = drupal_render($siblings_form);
  
  //cutting of <ul> and ending </ul> ... can this be done cleaner?
  $output = drupal_substr($output, 4, -5);

  print $output;
  exit();
}

/**
 * sets parents depending on form_id and hierarchical parents
 *
 * @param $tree_id
 * @param $parent term id
 * @param $form
 */
function _taxonomy_manager_tree_sub_forms_set_parents($tree_id, $parent, &$form) {
  $tree_ids = explode('-', $tree_id);
  foreach ($tree_ids as $key => $id) {
    $form['#parents'][] = $id;
  }
  if ($parent) {
    $all_parents = taxonomy_get_parents_all($parent);
    for ($i=count($all_parents)-1; $i >= 0; $i--) {
      $form['#parents'][] = $all_parents[$i]->tid;
      $form['#parents'][] = 'children';
    }
  }
}

/**
 * validates submitted form values
 * checks if selected terms really belong to initial voc, if not --> form_set_error
 * 
 * if all is valid, selected values get added to 'selected_terms' for easy use in submit
 *
 * @param $form
 */
function taxonomy_manager_tree_validate($form) {
  $selected = array();
  $selected = _taxonomy_manager_tree_get_selected_terms($form['#value']);
  $vid = $form['#vid'];
  foreach ($selected as $tid) {
    if (!_taxonomy_manager_tree_term_valid($tid, $vid)) {
      form_set_error('', t('An illegal choice has been detected. Please contact the site administrator.'));
    }
  }
  form_set_value($form['selected_terms'], $selected);
}

/**
 * checks if term id belongs to vocabulary
 *
 * @param $tid term id
 * @param $vid voc id
 * @return true, if term belongs to voc, else false
 */
function _taxonomy_manager_tree_term_valid($tid, $vid) {
  $term = taxonomy_get_term($tid);
  if ($term->vid != $vid) return false;
  return true;
}

/**
 * returns term ids of selected checkboxes
 * 
 * goes through nested form array recursivly
 * 
 * @param $form_values
 * @return an array with ids of selected terms
 */
function _taxonomy_manager_tree_get_selected_terms($form_values) {
  $tids = array();
  if (is_array($form_values)) {
    foreach ($form_values as $tid => $form_value) {
      if ($form_value['checkbox']) {
        $tids[$tid] = $tid;
      }
      if (is_array($form_value['children'])) {
        $tids += _taxonomy_manager_tree_get_selected_terms($form_value['children']);
      }
    }
  }
  return $tids;
}


/**
 * theme function for root element
 *
 * @param $element
 * @return html output
 */
function theme_taxonomy_manager_tree(&$element) {
  $tree = theme('taxonomy_manager_tree_elements', $element['#elements']);
  if(!$element['#parent'] && !$element['#siblings_page']) {
    $output = '<div id="'. $element['#id'] .'">';
    $output .= $tree;
    $output .= '</div>';
    return theme('form_element', $element, $output);
  }
  
  return $tree;
}

/**
 * recursive theme function for term elements
 *
 * @param $element
 * @return html lists
 */
function theme_taxonomy_manager_tree_elements($element) {
  $output .= "<ul>";
  
  if (is_array($element)) {  
    foreach (element_children($element) as $tid) {
      if (is_numeric($tid)) {
        $output .= '<li'. drupal_attributes($element[$tid]['#attributes']) .'>';
        $output .='<div class="term-line">';
        $output .='<div class="term-item">';
        $output .= drupal_render($element[$tid]['checkbox']);
        $output .= '</div>';
        
        $output .= '<div class="term-operations">';
        $output .= drupal_render($element[$tid]['operations']);
        $output .= '</div>';
        
        $output .= drupal_render($element[$tid]['weight']);
        $output .= drupal_render($element[$tid]['tid']); 
             
        if (is_array($element[$tid]['has-more-siblings'])) {
          $output .= '<div class="term-has-more-siblings">';
          $output .= '<div class="term-next-count">next '. drupal_render($element[$tid]['next_count']) .'</div>';
          $output .= '<div class="term-downarrow-img">';
          $output .= drupal_render($element[$tid]['has-more-siblings']);
          $output .= drupal_render($element[$tid]['page']);
          $output .= '</div><div class="clear"></div></div>';
        }
        
        $output .= '<div class="clear"> </div></div>';
        
        if (is_array($element[$tid]['children'])) {
          $output .= theme('taxonomy_manager_tree_elements', $element[$tid]['children']);
        }
        
        if(strstr($element[$tid]['#attributes']['class'], 'has-children')) {
          $output .= "<ul><li></li></ul>";
        }
        
        $output .='</li>';
      }
    } 
  }
  
  $output .= "</ul>";
  return $output;
}

/**
 * themes a checkbox, where a label can optional contain a link
 */
function theme_taxonomy_manager_tree_checkbox($element) {
  _form_set_class($element, array('form-checkbox'));
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';
  
  $title = $element['#title'];
  if ($element['#link']) {
    $title = l($title, $element['#link'], array("class" => "term-data-link"));
  }
  
  if (!is_null($title)) {
    $checkbox = '<label class="option">'. $checkbox .' '. $title .'</label>';
  }

  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
}
