Browse Source

Adding tripal_phylogeny to the legacy folder of Tripal 3

Stephen Ficklin 8 years ago
parent
commit
a57bf65ac1
25 changed files with 5074 additions and 0 deletions
  1. 528 0
      legacy/tripal_phylogeny/api/tripal_phylogeny.api.inc
  2. 331 0
      legacy/tripal_phylogeny/includes/parsers/tripal_phylogeny.newick_parser.inc
  3. 320 0
      legacy/tripal_phylogeny/includes/tripal_phylogeny.admin.inc
  4. 782 0
      legacy/tripal_phylogeny/includes/tripal_phylogeny.chado_node.inc
  5. 326 0
      legacy/tripal_phylogeny/includes/tripal_phylogeny.import_tree.inc
  6. 411 0
      legacy/tripal_phylogeny/includes/tripal_phylogeny.taxonomy.inc
  7. 0 0
      legacy/tripal_phylogeny/theme/css/tripal_phylogeny.css
  8. BIN
      legacy/tripal_phylogeny/theme/images/ajax-loader.gif
  9. 402 0
      legacy/tripal_phylogeny/theme/js/d3.phylogram.js
  10. 130 0
      legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js
  11. 80 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_analysis.tpl.php
  12. 72 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_base.tpl.php
  13. 11 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_help.tpl.php
  14. 8 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_organisms.tpl.php
  15. 19 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_phylogram.tpl.php
  16. 56 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_references.tpl.php
  17. 12 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_taxonomic_tree.tpl.php
  18. 18 0
      legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_teaser.tpl.php
  19. 102 0
      legacy/tripal_phylogeny/theme/tripal_phylogeny.theme.inc
  20. 243 0
      legacy/tripal_phylogeny/tripal_phylogeny.drush.inc
  21. 12 0
      legacy/tripal_phylogeny/tripal_phylogeny.info
  22. 514 0
      legacy/tripal_phylogeny/tripal_phylogeny.install
  23. 398 0
      legacy/tripal_phylogeny/tripal_phylogeny.module
  24. 23 0
      legacy/tripal_phylogeny/tripal_phylogeny.views.inc
  25. 276 0
      legacy/tripal_phylogeny/tripal_phylogeny.views_default.inc

+ 528 - 0
legacy/tripal_phylogeny/api/tripal_phylogeny.api.inc

@@ -0,0 +1,528 @@
+<?php
+
+/**
+ * Validates an $options array for insert or update of a phylotree record.
+ *
+ * If validation passes then any values that needed validation lookups
+ * (such as the dbxref, analysis, leaf_type, etc) will have their approriate
+ * primary_keys added to the $options array, and missing default values
+ * will also be added.
+ *
+ * @param $val_type
+ *   The type of validation. Can be either 'insert' or 'update'.
+ * @param $options
+ *   An array of key/value pairs containing any of the valid keys for
+ *   either the tripal_insert_phylotree() or tripal_update_phylotree()
+ *   functions.
+ * @param $errors
+ *   An empty array where validation error messages will be set. The keys
+ *   of the array will be name of the field from the options array and the
+ *   value is the error message.
+ * @param $warnings
+ *   An empty array where validation warning messagges will be set. The
+ *   warnings should not stop an insert or an update but should be provided
+ *   to the user as information by a drupal_set_message() if appropriate. The
+ *   keys of the array will be name of the field from the options array and the
+ *   value is the error message.
+ * @return
+ *   If validation failes then FALSE is returned.  Any options that do not pass
+ *   validation checks will be added in the $errors array with the key being
+ *   the option and the value being the error message.  If validation
+ *   is successful then TRUE is returned.
+ *
+ */
+function tripal_validate_phylotree($val_type, &$options, &$errors, &$warnings) {
+
+  if ($val_type != 'insert' and $val_type != 'update') {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR, "The $val_type argument must be either 'update or 'insert'.");
+  }
+
+  // Set Defaults.
+  if ($val_type == 'insert') {
+    // Match by feature name.
+    if (!array_key_exists('match', $options)) {
+      $options['match'] = 'name';
+    }
+    // The regular expression is to match the entire node name.
+    if (!array_key_exists('name_re', $options)) {
+      $options['name_re'] = '^(.*)$';
+    }
+    // A dbxref is not required by Tripal but is required by the database
+    // field in the phylotree table.  Therefore, if the dbxref is not provided
+    // we can set this to be the null database and null dbxref which
+    // is represented as 'null:local:null'
+    if (!array_key_exists('dbxref', $options)) {
+      $options['dbxref'] = "null:local:null";
+    }
+  }
+
+  // Make sure required values are set.
+  if ($val_type == 'insert') {
+    if (!array_key_exists('name', $options)) {
+      $errors['name'] = t('Please provide the name of the tree.');
+      return FALSE;
+    }
+    if (!array_key_exists('description', $options)) {
+      $errors['description'] = t('Please provide a description for this tree.');
+      return FALSE;
+    }
+    if (!array_key_exists('analysis', $options) and !array_key_exists('analysis_id', $options)) {
+      $errors['analysis'] = t('Please provide an analysis or analysis_id for this tree.');
+      return FALSE;
+    }
+    if (!array_key_exists('tree_file', $options)) {
+      $errors['tree_file'] = t('Please provide either the full path to the tree_file or a Drupal managed file ID number.');
+      return FALSE;
+    }
+    if (!array_key_exists('format', $options) or !$options['format']) {
+      $errors['format'] = t('Please provide a file format for the tree file.');
+      return FALSE;
+    }
+    // Make sure the file format is correct
+    if ($options['format'] != 'newick' and $options['format'] != 'taxonomy') {
+      $errors['format'] = t('The file format is not supported. Currently only the "newick" file format is supported.');
+      return FALSE;
+    }
+  }
+  else {
+    // Does the phylotree ID exist and is it valid
+    if (!array_key_exists('phylotree_id', $options)) {
+      $errors['phylotree_id'] = t('Please provide the ID for the tree.');
+      return FALSE;
+    }
+    $exists = chado_select_record('phylotree', array('phylotree_id'),
+        array('phylotree_id' => $options['phylotree_id']), array('has_record' => 1));
+    if (!$exists) {
+      $errors['phylotree_id'] = t('The phylotree_id does not exist.');
+      return FALSE;
+    }
+
+  }
+
+  // Make sure the file exists if one is specified
+  if (array_key_exists('tree_file', $options) and $options['tree_file']) {
+    // If this is a numeric Drupal file then all is good, no need to check.
+    if (!is_numeric($options['tree_file'])) {
+      if (!file_exists($options['tree_file'])) {
+        $errors['tree_file'] = t('The file provided does not exists.');
+        return FALSE;
+      }
+    }
+    // Make sure the file format is correct
+    if (!array_key_exists('format', $options) or
+        ($options['format'] != 'newick' and $options['format'] != 'taxonomy')) {
+      $errors['format'] = t('Please provide a supported file format. Currently only the "newick" file format is supported.');
+      return FALSE;
+    }
+
+    // If no leaf type is provided then use the polypeptide term.
+    if (!array_key_exists('leaf_type', $options) or !$options['leaf_type']) {
+      $options['leaf_type'] = 'polypeptide';
+    }
+  }
+
+  // Make sure the analysis exists.
+  $analysis = NULL;
+  if (array_key_exists('analysis_id', $options) and $options['analysis_id']) {
+    $analysis = chado_select_record('analysis', array('analysis_id'), array('analysis_id' => $options['analysis_id']));
+    if (!$analysis) {
+      $errors['analysis_id'] = t('The analysis name provided does not exist.');
+      return FALSE;
+    }
+    $options['analysis_id'] = $analysis[0]->analysis_id;
+  }
+  if (array_key_exists('analysis', $options) and $options['analysis']) {
+    $analysis = chado_select_record('analysis', array('analysis_id'), array('name' => $options['analysis']));
+    if (!$analysis) {
+      $errors['analysis'] = t('The analysis ID provided does not exist.');
+      return FALSE;
+    }
+    $options['analysis_id'] = $analysis[0]->analysis_id;
+  }
+
+  // Make sure the leaf type exists.
+  $type = NULL;
+  if (array_key_exists('leaf_type', $options) and $options['leaf_type']) {
+    if ($options['leaf_type'] == 'taxonomy') {
+      $values = array(
+        'cv_id' => array(
+           'name' => 'tripal_phylogeny'
+        ),
+        'name' => 'taxonomy'
+      );
+      $type = chado_select_record('cvterm', array('cvterm_id'), $values);
+    }
+    else {
+      $values = array(
+        'cv_id' => array(
+           'name' => 'sequence'
+        ),
+        'name' => $options['leaf_type']
+      );
+      $type = chado_select_record('cvterm', array('cvterm_id'), $values);
+      if (!$type) {
+        $errors['leaf_type'] = t('The leaf_type provided is not a valid Sequence Ontology term: %term.');
+        return FALSE;
+      }
+    }
+    $options['type_id'] = $type[0]->cvterm_id;
+  }
+
+  // A Dbxref is required by the phylotree module, but if the
+  // tree was generated in-house and the site admin doens't want to
+  // assign a local dbxref then we will set it to the null db
+  // and the local:null dbxref.
+  if (array_key_exists('dbxref', $options)) {
+    if (!$options['dbxref']) {
+        $options['dbxref'] = 'null:local:null';
+    }
+    $matches = array();
+    preg_match('/^(.*?):(.*)$/', $options['dbxref'], $matches);
+    $db_name = $matches[1];
+    $accession = $matches[2];
+    $values = array(
+      'accession' => $accession,
+      'db_id' => array(
+        'name' => $db_name
+      ),
+    );
+    $dbxref = chado_generate_var('dbxref', $values);
+    if (!$dbxref) {
+      $errors['dbxref'] = t('The dbxref provided does not exist in the database: %dbxref.', array('%dbxref' => $dbxref));
+      return FALSE;
+    }
+    $options['dbxref_id'] = $dbxref->dbxref_id;
+  }
+
+  // Make sure the tree name is unique
+  if (array_key_exists('name', $options) and $options['name']) {
+    $sql = "
+      SELECT *
+      FROM {phylotree} P
+      WHERE
+        P.name = :name
+    ";
+    $args = array(':name' => $options['name']);
+    if ($val_type == 'update') {
+      $sql .= " AND NOT P.phylotree_id = :phylotree_id";
+      $args[':phylotree_id'] = $options['phylotree_id'];
+    }
+    $result = chado_query($sql, $args)->fetchObject();
+    if ($result) {
+      $errors['name'] = t("The tree name is in use by another tree. Please provide a different unique name for this tree.");
+    }
+  }
+
+  return TRUE;
+}
+/**
+ * Inserts a phylotree record into Chado.
+ *
+ * This function validates the options passed prior to insertion of the record,
+ * and if validation passes then any values in the options array that needed
+ * validation lookups (such as the dbxref, analysis, leaf_type, etc) will have
+ * their approriate primary key values added to the options array.
+ *
+ * @param $options
+ *  An array of key value pairs with the following keys required:
+ *     'name':       The name of the tree. This will be displayed to users.
+ *     'description: A description about the tree
+ *     'anlaysis_id: The ID of the analysis to which this phylotree should be
+ *                   associated.
+ *     'analysis':   If the analysis_id key is not used then the analysis name
+ *                   may be provided to identify the analysis to which the tree
+ *                   should be associated.
+ *     'leaf_type':  A sequence ontology term or the word 'organism'. If the
+ *                   type is 'organism' then this tree represents a
+ *                   taxonomic tree.  The default, if not specified, is the
+ *                   term 'polypeptide'.
+ *     'tree_file':  The path of the file containing the phylogenetic tree to
+ *                   import or a Drupal managed_file numeric ID.
+ *     'format':     The file format. Currently only 'newick is supported'
+ *  Optional keys:
+ *     'dbxref':     A database cross-reference of the form DB:ACCESSION.
+ *                   Where DB is the database name, which is already present
+ *                   in Chado, and ACCESSION is the unique identifier for
+ *                   this tree in the remote database.
+ *     'name_re':    If the leaf type is NOT 'taxonomy', then the value of
+ *                   this field can be a regular expression to pull out
+ *                   the name of the feature from the node label in the
+ *                   intput tree. If no value is provided the entire label is
+ *                   used.
+ *     'match':      Set to 'uniquename' if the leaf nodes should be matched
+ *                   with the feature uniquename.
+ *     'load_now':   If set, the tree will be loaded immediately if a tree_file
+ *                   is provided. Otherwise, the tree will be loaded via
+ *                   a Tripal jobs call.
+ *     'no_load':    If set the tree file will not be loaded.
+ * @param $errors
+ *   An empty array where validation error messages will be set. The keys
+ *   of the array will be name of the field from the options array and the
+ *   value is the error message.
+ * @param $warnings
+ *   An empty array where validation warning messagges will be set. The
+ *   warnings should not stop an insert or an update but should be provided
+ *   to the user as information by a drupal_set_message() if appropriate. The
+ *   keys of the array will be name of the field from the options array and the
+ *   value is the error message.
+ * @return
+ *   TRUE for success and FALSE for failure.
+ */
+function tripal_insert_phylotree(&$options, &$errors, &$warnings) {
+  global $user;
+
+  $options['name_re'] = trim($options['name_re']);
+  $options['leaf_type'] = trim($options['leaf_type']);
+  $options['name'] = trim($options['name']);
+  $options['format'] = trim($options['format']);
+  $options['tree_file'] = trim($options['tree_file']);
+
+  // Validate the incoming options.
+  $success = tripal_validate_phylotree('insert', $options, $errors, $warnings);
+  if (!$success) {
+    foreach ($errors as $field => $message) {
+      tripal_report_error('tripal_phylogeny', TRIPAL_ERROR, $message);
+    }
+    return FALSE;
+  }
+
+  // If we're here then all is good, so add the phylotree record.
+  $values = array(
+    'analysis_id' => $options['analysis_id'],
+    'name' => $options['name'],
+    'dbxref_id' => $options['dbxref_id'],
+    'comment' => $options['description'],
+    'type_id' => $options['type_id'],
+  );
+  $phylotree = chado_insert_record('phylotree', $values);
+  if (!$phylotree) {
+    drupal_set_message(t('Unable to add phylotree.'), 'warning');
+    tripal_report_error('tripal_phylogeny', TRIPAL_WARNING, 'Insert phylotree: Unable to create phylotree where values: %values',
+        array('%values' => print_r($values, TRUE)));
+    return FALSE;
+  }
+  $phylotree_id = $phylotree['phylotree_id'];
+  $options['phylotree_id'] = $phylotree_id;
+
+  // If the tree_file is numeric then it is a Drupal managed file and
+  // we want to make the file permanent and associated with the tree.
+  if (is_numeric($options['tree_file'])) {
+    $file = NULL;
+    $file = file_load($options['tree_file']);
+    $file->status = FILE_STATUS_PERMANENT;
+    $file = file_save($file);
+    file_usage_add($file, 'tripal_phylogeny', $options['format'], $phylotree_id);
+    $real_file_path = drupal_realpath($file->uri);
+  }
+  else {
+    $real_file_path = $options['tree_file'];
+  }
+
+  // If caller has requested to load the file now then do so, otherwise
+  // submit using a Tripal job.
+  if (!array_key_exists('no_load', $options) or !$options['no_load']) {
+    if (array_key_exists('load_now', $options) and $options['load_now']) {
+      $args = array(
+        'phylotree_id' => $phylotree_id,
+        'leaf_type' => $options['leaf_type'],
+        'match' => $options['match'] ? 'uniquename' : 'name',
+        'name_re' => $options['name_re'],
+      );
+      tripal_phylogeny_import_tree_file($real_file_path, $options['format'], $args);
+    }
+    else {
+      $args = array(
+          $real_file_path,
+          'newick',
+          array(
+            'phylotree_id' => $phylotree_id,
+            'leaf_type' => $options['leaf_type'],
+            'match' => $options['match'] ? 'uniquename' : 'name',
+            'name_re' => $options['name_re'],
+          ),
+      );
+      if (tripal_add_job("Import Tree File: " . $file->filename, 'tripal_phylogeny',
+          'tripal_phylogeny_import_tree_file', $args, $user->uid)) {
+          drupal_set_message(t('The tree visualizations will appear once the tree is fully imported.'));
+      }
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Updates a phylotree record into Chado.
+ *
+ * This function validates the options passed prior to update of the record
+ * and if validation passes then any values in the options array that needed
+ * validation lookups (such as the dbxref, analysis, leaf_type, etc) will have
+ * their approriate primary key values added to the options array. A Drupal
+ * File object will be added to the options array for the tree file if one
+ * is provided.
+ *
+ *
+ * @param $phylotree_id
+ *   The ID of the phylotree to update.
+ * @param $options
+ *  An array of key value pairs with the following optional keys:
+ *     'name':       The name of the tree. This will be displayed to users.
+ *     'description: A description about the tree
+ *     'anlaysis_id: The ID of the analysis to which this phylotree should be
+ *                   associated.
+ *     'analysis':   If the analysis_id key is not used then the analysis name
+ *                   may be provided to identify the analysis to which the tree
+ *                   should be associated.
+ *     'leaf_type':  A sequence ontology term or the word 'organism'. If the
+ *                   type is 'organism' then this tree represents a
+ *                   taxonomic tree.  The default, if not specified, is the
+ *                   term 'polypeptide'.
+ *     'tree_file':  The path of the file containing the phylogenetic tree to
+ *                   import or a Drupal managed_file numeric ID.
+ *     'format':     The file format. Currently only 'newick is supported'
+ *     'dbxref':     A database cross-reference of the form DB:ACCESSION.
+ *                   Where DB is the database name, which is already present
+ *                   in Chado, and ACCESSION is the unique identifier for
+ *                   this tree in the remote database.
+ *     'name_re':    If the leaf type is NOT 'taxonomy', then the value of
+ *                   this field can be a regular expression to pull out
+ *                   the name of the feature from the node label in the
+ *                   intput tree. If no value is provided the entire label is
+ *                   used.
+ *     'match':      Set to 'uniquename' if the leaf nodes should be matched
+ *                   with the feature uniquename.
+ *     'load_now':   If set, the tree will be loaded immediately if a tree_file
+ *                   is provided. Otherwise, the tree will be loaded via
+ *                   a Tripal jobs call.
+ */
+function tripal_update_phylotree($phylotree_id, &$options) {
+  global $user;
+
+  // Validate the incoming options.
+  $errors = array();
+  $warnings = array();
+  $success = tripal_validate_phylotree('update', $options, $errors, $warnings);
+  if (!$success) {
+    foreach ($errors as $field => $message) {
+      tripal_report_error('tripal_phylogeny', TRIPAL_ERROR, $message);
+    }
+    return FALSE;
+  }
+
+  // If we're here then all is good, so update the phylotree record.
+  $match = array(
+      'phylotree_id' => $phylotree_id,
+  );
+  if (array_key_exists('name', $options) and $options['name']) {
+    $values['name'] = $options['name'];
+  }
+  if (array_key_exists('analysis_id', $options) and $options['analysis_id']) {
+    $values['analysis_id'] = $options['analysis_id'];
+  }
+  if (array_key_exists('dbxref_id', $options) and $options['dbxref_id']) {
+    $values['dbxref_id'] = $options['dbxref_id'];
+  }
+  if (array_key_exists('description', $options) and $options['description']) {
+    $values['comment'] = $options['description'];
+  }
+  if (array_key_exists('type_id', $options) and $options['type_id']) {
+    $values['type_id'] = $options['type_id'];
+  }
+
+  $phylotree = chado_update_record('phylotree', $match, $values, array('return_record' => TRUE));
+  if (!$phylotree) {
+    drupal_set_message(t('Unable to update phylotree.'), 'warning');
+    tripal_report_error('tripal_phylogeny', TRIPAL_WARNING,
+        'Update phylotree: Unable to update phylotree where values: %values',
+        array('%values' => print_r($values, TRUE))
+    );
+  }
+
+  // If we have a tree file, then import the tree
+  if (array_key_exists('tree_file', $options) and $options['tree_file']) {
+
+    // Remove any existing nodes
+    chado_delete_record('phylonode', array('phylotree_id' => $options['phylotree_id']));
+
+    // Make sure if we already have a file that we remove the old one.
+    $sql = "
+      SELECT FM.fid
+      FROM {file_managed} FM
+        INNER JOIN {file_usage} FU on FM.fid = FU.fid
+      WHERE FU.id = :id and FU.module = 'tripal_phylogeny'
+    ";
+    $fid = db_query($sql, array(':id' => $options['phylotree_id']))->fetchField();
+    if ($fid) {
+      $file = file_load($fid);
+      file_delete($file, TRUE);
+    }
+
+    // If the tree_file is numeric then it is a Drupal managed file and
+    // we want to make the file permanent and associated with the tree.
+    if (is_numeric($options['tree_file'])) {
+      $file = file_load($options['tree_file']);
+      $file->status = FILE_STATUS_PERMANENT;
+      $file = file_save($file);
+      file_usage_add($file, 'tripal_phylogeny', 'newick', $options['phylotree_id']);
+
+      // Add a job to parse the new node tree.
+      $real_file_path = drupal_realpath($file->uri);
+    }
+    else {
+      $real_file_path = $options['tree_file'];
+    }
+
+    // If caller has requested to load the file now then do so, otherwise
+    // submit using a Tripal job.
+    if (array_key_exists('load_now', $options) and $options['load_now']) {
+      $args = array(
+        'phylotree_id' => $options['phylotree_id'],
+        'leaf_type' => $options['leaf_type'],
+        'match' => $options['match'] ? 'uniquename' : 'name',
+        'name_re' => $options['name_re'],
+      );
+      tripal_phylogeny_import_tree_file($real_file_path, $options['format'], $args);
+    }
+    else {
+      $args = array(
+        $real_file_path,
+        'newick',
+        array(
+          'phylotree_id' => $options['phylotree_id'],
+          'leaf_type' => $options['leaf_type'],
+          'match' => $options['match'] ? 'uniquename' : 'name',
+          'name_re' => $options['name_re'],
+        ),
+      );
+      if (tripal_add_job("Import Tree File: " . $file->filename, 'tripal_phylogeny',
+          'tripal_phylogeny_import_tree_file', $args, $user->uid)) {
+        drupal_set_message(t('The tree visualizations will appear once the tree is fully imported.'));
+      }
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Deletes a phylotree record from Chado.
+ *
+ * @param $phylotree_id
+ *
+ * @return
+ *   TRUE on success, FALSE on failure.
+ */
+function tripal_delete_phylotree($phylotree_id) {
+
+  // if we don't have a phylotree id for this node then this isn't a node of
+  // type chado_phylotree or the entry in the chado_phylotree table was lost.
+  if (!$phylotree_id) {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+        'Please provide a phylotree_id to delete a tree.');
+    return FALSE;
+  }
+
+  // Remove the tree
+  $values = array('phylotree_id' => $phylotree_id);
+  return chado_delete_record('phylotree', $values);
+}

+ 331 - 0
legacy/tripal_phylogeny/includes/parsers/tripal_phylogeny.newick_parser.inc

@@ -0,0 +1,331 @@
+<?php
+
+/**
+ * This code parses the grammer for the Newick format as per the following
+ * grammar:
+ * 
+ *  Tree --> Subtree ";" | Branch ";"
+ *  Subtree --> Leaf | Internal
+ *  Leaf --> Name
+ *  Internal --> "(" BranchSet ")" Name
+ *  BranchSet --> Branch | BranchSet "," Branch
+ *  Branch --> Subtree Length
+ *  Name --> empty | string
+ *  Length --> empty | ":" number
+ * 
+ */
+
+/**
+ * 
+ * @param unknown $file_name
+ */
+function tripal_phylogeny_parse_newick_file($file_name) {
+  
+  // Initialize the bootstrap value and index
+  global $tripal_phylogeny_bootstrap;
+  
+  $tripal_phylogeny_bootstrap = 1;
+  $tripal_phylogeny_index = 1;
+  
+  $tree = array();
+  
+  $fp = fopen($file_name, 'r');
+  if ($fp) {
+    $tree = tripal_phylogeny_parse_newick_tree($fp);
+  }
+  else {
+    // ERROR
+  }
+  return $tree;
+}
+/**
+ * 
+ * @param unknown $fp
+ * @param number $depth
+ * @return boolean
+ */
+function tripal_phylogeny_parse_newick_tree($fp, $depth = 0) {
+  
+  $subtree = tripal_phylogeny_parse_newick_subtree($fp, $depth);
+  $subtree['is_root'] = 1;
+
+  // this subtree may also be a branch. A branch is a subtree with a length,
+  // so see if there is a length
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+  if ($token == ";") {
+    // we're done!
+    return $subtree;
+  }
+  tripal_phylogeny_parse_newick_replace_token($fp);
+  
+  // Get the length.
+  $length = tripal_phylogeny_parse_newick_length($fp, $depth);
+  $subtree['length'] = $length;
+  
+  // Now if we're missing the semicolon we have a syntax error.
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+  if ($token != ';') {
+    print "Syntax Error: missing trailing semicolon.\n";
+    exit;
+  }
+ 
+  return $subtree;
+}
+
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $depth
+ * @return Ambigous|unknown
+ */
+function tripal_phylogeny_parse_newick_subtree($fp, $depth) {
+  
+  $internal = tripal_phylogeny_parse_newick_internal($fp, $depth + 1);
+  if (!is_array($internal)) {
+    $leaf_node = tripal_phylogeny_parse_newick_leaf($fp, $depth);
+    return array(
+      'name' => $leaf_node,
+      'depth' => $depth,
+      'is_leaf' => TRUE,
+      'descendents' => 0,
+    );
+  }
+  else {
+    $internal['depth'] = $depth;
+  }
+  return $internal;
+}
+
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $depth
+ * @return boolean|multitype:unknown Ambigous <Ambigous, unknown>
+ */
+function tripal_phylogeny_parse_newick_branch($fp, $depth) {
+
+  $subtree = tripal_phylogeny_parse_newick_subtree($fp, $depth);
+  $length = tripal_phylogeny_parse_newick_length($fp, $depth);
+
+  $subtree['length'] = $length;
+  return $subtree;
+}
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $parent
+ * @param unknown $depth
+ */
+function tripal_phylogeny_parse_newick_internal($fp, $depth) {
+
+  // If the next character is not an open paren then this is an internal node
+  if (tripal_phylogeny_parse_newick_get_token($fp) != '(') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    return FALSE;
+  }
+  
+  $branches = tripal_phylogeny_parse_newick_branchset($fp, $depth);
+  if (!is_array($branches)) {
+    return FALSE;
+  }
+  // If we don't have a closing parent then this is a syntax error.
+  if (tripal_phylogeny_parse_newick_get_token($fp) != ')') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    return FALSE;
+  }
+  $internal_node = tripal_phylogeny_parse_newick_name($fp, $depth);
+  $descendent_count = 0;
+  for ($i = 0; $i < count($branches); $i++) {
+    $branches[$i]['parent'] = $internal_node;
+    $descendent_count += 1 + $branches[$i]['descendents'];
+  }
+
+  return array(
+    'name' => $internal_node,
+    'depth' => $depth,
+    'branch_set' => $branches,
+    'is_internal' => TRUE,
+    'descendents' => $descendent_count,
+  );
+}
+
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $parent
+ * @param unknown $depth
+ */
+function tripal_phylogeny_parse_newick_branchset($fp, $depth) {
+  $branches = array();
+  
+  $num_read = 0;
+  $branch = tripal_phylogeny_parse_newick_branch($fp, $depth);
+  $branches[] = $branch;
+
+  // If it's not a branch then return false, a branchset will
+  // always appear as a branch.
+  if (!is_array($branch)) {
+    return FALSE;
+  }
+
+  // If we have a comma as the next token then this is
+  // a branchset and we should recurse. 
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+  if ($token == ',') {
+    $rbranches = tripal_phylogeny_parse_newick_branchset($fp, $depth);
+    foreach ($rbranches as $branch) {
+      $branches[] = $branch;
+    }
+  }
+  else {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+  }
+
+  return $branches;
+}
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $depth
+ * @return Ambigous <string, boolean, unknown>
+ */
+function tripal_phylogeny_parse_newick_leaf($fp, $depth) {
+  return tripal_phylogeny_parse_newick_name($fp, $depth);
+}
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $depth
+ * @return string|boolean|Ambigous <string, unknown>
+ */
+function tripal_phylogeny_parse_newick_name($fp, $depth) {
+  global $tripal_phylogeny_bootstrap;
+
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+  
+  // If the next token is a colon, semicolon, close paren, or comma 
+  // then the name is empty.
+  if ($token == ':' or $token == ',' or $token == ';' or $token == ')') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    // create a bootstrap value
+    return $tripal_phylogeny_bootstrap++;
+  }
+  
+  // If the next token is an open paren then this is a syntax error:
+  if ($token == '(') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    return FALSE;
+  }
+  return $token;
+}
+/**
+ * 
+ * @param unknown $fp
+ * @param unknown $depth
+ * @return string|boolean|unknown
+ */
+function tripal_phylogeny_parse_newick_length($fp, $depth) {
+  $length  = '';
+
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+  
+  // If the next token is a semicolon, close paren, or comma
+  // then the length is empty.
+  if ($token == ',' or $token == ';' or $token == ')') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    return '';
+  }
+
+  // If the next token is a colon then we are parsing the length. 
+  // Otherwise we are not.
+  if ($token != ':') {
+    tripal_phylogeny_parse_newick_replace_token($fp);
+    return FALSE;
+  }
+  
+  // Now get the length.
+  $token = tripal_phylogeny_parse_newick_get_token($fp);
+
+  // If the next token is an open paren then this is a syntax error:
+  if ($token == '(') {
+    exit();
+  }
+
+  return $token;
+}
+
+/**
+ * 
+ * @param unknown $fp
+ * @return string
+ */
+function tripal_phylogeny_parse_newick_get_token($fp) {
+  
+  // Keep track of the file position that we start with
+  global $tripal_phylogeny_fp_pos;
+  $tripal_phylogeny_fp_pos = ftell($fp);
+  
+  $token = '';
+  $in_quote = FALSE;
+  $num_read = 0;
+  
+  $c = fgetc($fp);
+  while (!feof($fp)) {
+    $num_read++;
+
+    switch ($c) {
+      // If the first character is a reserved character and we
+      // we have not encountered any other charcters then return
+      // it as the token. Otherwise, return the collected token.
+      case ';':
+      case '(':
+      case ')':
+      case ',':
+      case ':':
+        if (!$token) {
+          return $c;
+        }
+        else {
+          // put the character back and return the token
+          fseek($fp, $tripal_phylogeny_fp_pos + $num_read - 1);
+          return $token;
+        }
+        
+        break;
+      // Quotes are allowed around names and if a name is in
+      // quotes then allow spaces. Otherwise, spaces are ignored.
+      case '\'':
+      case '"':
+        if (!$in_quote) {
+          $in_quote = TRUE;
+        }
+        else {
+          $in_quote = FALSE;
+        }
+        break;
+      case " ":
+      case "\t":
+      case "\r":
+      case "\n":
+        if ($in_quote) {
+          $token .= $c;
+        }
+        break;
+      // All other characters get saved as the token
+      default:
+        $token .= $c;
+    }
+    $c = fgetc($fp);
+  }
+  return $token;
+}
+/**
+ * 
+ * @param unknown $fp
+ */
+function tripal_phylogeny_parse_newick_replace_token($fp) {
+  global $tripal_phylogeny_fp_pos;
+  
+  fseek($fp, $tripal_phylogeny_fp_pos);
+  $tripal_phylogeny_fp_pos = ftell($fp);
+}

+ 320 - 0
legacy/tripal_phylogeny/includes/tripal_phylogeny.admin.inc

@@ -0,0 +1,320 @@
+<?php
+/**
+ * @file
+ * This file contains the functions used for administration of the module
+ *
+ */
+
+function tripal_phylogeny_admin_phylotrees_listing() {
+  $output = '';
+
+  // set the breadcrumb
+  $breadcrumb = array();
+  $breadcrumb[] = l('Home', '<front>');
+  $breadcrumb[] = l('Administration', 'admin');
+  $breadcrumb[] = l('Tripal', 'admin/tripal');
+  $breadcrumb[] = l('Chado', 'admin/tripal/chado');
+  $breadcrumb[] = l('Phylotrees', 'admin/tripal/extension/tripal_phylogeny');
+  drupal_set_breadcrumb($breadcrumb);
+
+  // Add the view
+  $view = views_embed_view('tripal_phylogeny_admin_phylotree','default');
+  if (isset($view)) {
+    $output .= $view;
+  }
+  else {
+    $output .= '<p>The Phylotree module uses primarily views to provide an '
+      . 'administrative interface. Currently one or more views needed for this '
+      . 'administrative interface are disabled. <strong>Click each of the following links to '
+      . 'enable the pertinent views</strong>:</p>';
+    $output .= '<ul>';
+      $output .= '<li>'.l('Phylotree View', 'admin/tripal/extension/tripal_phylogeny/views/phylotree/enable').'</li>';
+    $output .= '</ul>';
+  }
+  return $output;
+}
+
+/**
+ * Administrative settings form
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_admin() {
+  $form = array();
+
+
+  // PHYLOTREE NODE TITLES
+  // If your module is using the Chado Node: Title & Path API to allow
+  // custom titles for your node type then you need to add the
+  // configuration form for this functionality.
+  $details = array(
+    'module' => 'tripal_phylogeny',
+    'content_type' => 'chado_phylotree',
+    // An array of options to use under "Page Titles"
+    // the key should be the token and the value should be the human-readable option
+    'options' => array(
+      '[phylotree.name]' => 'Tree Name Only',
+      // there should always be one options matching the unique constraint.
+      '[phylotree.phylotree_id]' => 'The Chado ID for Phylotrees'
+    ),
+    // the token indicating the unique constraint in the options array
+    'unique_option' => '[phylotree.phylotree_id]'
+  );
+
+  // This call adds the configuration form to your current form
+  // This sub-form handles it's own validation & submit
+  chado_add_admin_form_set_title($form, $form_state, $details);
+
+
+  // PHYLOTREE NODE URL
+  // Using the Chado Node: Title & Path API
+  $details = array(
+    'module' => 'tripal_phylogeny',
+    'content_type' => 'chado_phylotree',
+    // An array of options to use under "Page URL"
+    // the key should be the token and the value should be the human-readable option
+    'options' => array(
+      '/tree/[phylotree.name]' => 'Tree Name Only',
+      // there should always be one options matching the unique constraint.
+      '/tree/[phylotree.phylotree_id]' => 'The Chado ID for Phylotrees',
+    )
+  );
+  // This call adds the configuration form to your current form
+  // This sub-form handles it's own validation & submit
+  chado_add_admin_form_set_url($form, $form_state, $details);
+
+  return system_settings_form($form);
+
+}
+
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_default_plots_form($form, &$form_state) {
+  $form = array();
+
+  $form['plot_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Plot Settings'),
+    '#description' => t('You can customize settings for each plot'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+
+  $form['plot_settings']['phylogram_width'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Tree Width',
+    '#description' => 'Please specify the width in pixels for the phylogram',
+    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+    '#element_validate' => array(
+      'element_validate_integer_positive'
+    ),
+    '#size' => 5,
+  );
+
+  $form['node_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Node Settings'),
+    '#description' => t('You can customize settings for the nodes on the trees.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+  $form['node_settings']['root_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Root Node Size',
+    '#description' => 'Please specify a size for the root node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['interior_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Interor Node Size',
+    '#description' => 'Please specify a size for the interior node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['leaf_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Leaf Node Size',
+    '#description' => 'Please specify a size for the leaf node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+
+  // Get the number of organism colors that already exist. If the site admin
+  // has set colors then those settings will be in a Drupal variable which we
+  // will retrieve.  Otherwise the num_orgs defaults to 1 and a single
+  // set of fields is provided.
+  $num_orgs = variable_get("tripal_phylogeny_num_orgs", 1);
+  if (array_key_exists('values', $form_state) and array_key_exists('num_orgs', $form_state['values'])) {
+    $num_orgs = $form_state['values']['num_orgs'];
+  }
+  // The default values for each organism color are provided in a d
+  // Drupal variable that gets set when the form is set.
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+
+  $form['node_settings']['desc'] = array(
+    '#type' => 'item',
+    '#title' => t('Node Colors by Organism'),
+    '#markup' => t('If the trees are associated with features (e.g. proteins)
+      then the nodes can be color-coded by their organism.  This helps the user
+      visualize which nodes belong to each organism.  Please enter the
+      name of the organism and it\'s corresponding color in HEX code (e.g. #FF0000 == red).
+      Organisms that are not given a color will be gray.'),
+  );
+  $form['node_settings']['org_table']['num_orgs'] = array(
+    '#type' => 'value',
+    '#value' => $num_orgs,
+  );
+
+  // Iterate through the number of organism colors and add a field for each one.
+  for ($i = 0; $i < $num_orgs; $i++) {
+    $form['node_settings']['org_table']['organism_' . $i] = array(
+      '#type' => 'textfield',
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['organism'] : '',
+      '#autocomplete_path' => "admin/tripal/chado/tripal_organism/organism/auto_name",
+      '#description' => t('Please enter the name of the organism.'),
+      '#size' => 30,
+    );
+    $form['node_settings']['org_table']['color_' . $i] = array(
+      '#type' => 'textfield',
+      '#description' => t('Please provide a color in Hex format (e.g. #FF0000).'),
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['color'] : '',
+      '#suffix' => "<div id=\"color-box-$i\" style=\"width: 30px;\"></div>",
+      '#size' => 10,
+    );
+  }
+  $form['node_settings']['org_table']['add'] = array(
+    '#type' => 'submit',
+    '#name' => 'add',
+    '#value' => 'Add',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['remove'] = array(
+    '#type' => 'submit',
+    '#name' => 'remove',
+    '#value' => 'Remove',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['#theme'] = 'tripal_phylogeny_admin_org_color_tables';
+  $form['node_settings']['org_table']['#prefix'] = '<div id="tripal_phylogeny_default_plots_form">';
+  $form['node_settings']['org_table']['#suffix'] = '</div>';
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#name' => 'submit',
+    '#value' => 'Save Configuration',
+  );
+
+  $form['#submit'][] = 'tripal_phylogeny_default_plots_form_submit';
+
+  return $form;
+}
+
+/**
+ * Validate the phylotree settings forms
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_default_plots_form_validate($form, &$form_state) {
+
+}
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
+  // Rebuild this form after submission so that any changes are reflected in
+  // the flat tables.
+  $form_state['rebuild'] = TRUE;
+
+  if ($form_state['clicked_button']['#name'] == 'submit') {
+    variable_set('tripal_phylogeny_default_phylogram_width', $form_state['values']['phylogram_width']);
+
+    variable_set('tripal_phylogeny_default_root_node_size', $form_state['values']['root_node_size']);
+    variable_set('tripal_phylogeny_default_interior_node_size', $form_state['values']['interior_node_size']);
+    variable_set('tripal_phylogeny_default_leaf_node_size', $form_state['values']['leaf_node_size']);
+
+    $num_orgs = $form_state['values']['num_orgs'];
+    variable_set("tripal_phylogeny_num_orgs", $num_orgs);
+    $colors = array();
+    for ($i = 0; $i < $num_orgs ;$i++) {
+      $colors[$i] = array(
+        'organism' => $form_state['values']['organism_' . $i],
+        'color' => $form_state['values']['color_' . $i]
+      );
+    }
+    variable_set("tripal_phylogeny_org_colors", $colors);
+  }
+  if ($form_state['clicked_button']['#name'] == 'add') {
+    $form_state['values']['num_orgs']++;
+  }
+  if ($form_state['clicked_button']['#name'] == 'remove') {
+    $form_state['values']['num_orgs']--;
+  }
+}
+
+/**
+ *
+ * @param unknown $variables
+ */
+function theme_tripal_phylogeny_admin_org_color_tables($variables){
+   $fields = $variables['element'];
+   $num_orgs = $fields['num_orgs']['#value'];
+   $headers = array('Organism', 'Color', '');
+   $rows = array();
+   for ($i = 0; $i < $num_orgs; $i++) {
+     $add_button = ($i == $num_orgs - 1) ? drupal_render($fields['add']) : '';
+     $del_button = ($i == $num_orgs - 1 and $i != 0) ? drupal_render($fields['remove']) : '';
+     $rows[] = array(
+       drupal_render($fields['organism_' . $i]),
+       drupal_render($fields['color_' . $i]),
+       $add_button . $del_button,
+     );
+   }
+   $table_vars = array(
+     'header' => $headers,
+     'rows' => $rows,
+     'attributes' => array(),
+     'sticky' => FALSE,
+     'colgroups' => array(),
+     'empty' => '',
+   );
+   $form['orgs']['num_orgs'] = $fields['num_orgs'];
+   return theme('table', $table_vars);
+}
+
+
+/**
+ * Ajax callback function for the gensas_job_view_panel_form.
+ *
+ * @param $form
+ * @param $form_state
+ */
+function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
+ // drupal_debug($form['tree_settings']['org_table']['num_orgs']);
+
+  return $form['node_settings']['org_table'];
+}

+ 782 - 0
legacy/tripal_phylogeny/includes/tripal_phylogeny.chado_node.inc

@@ -0,0 +1,782 @@
+<?php
+
+/**
+ * @file
+ * Implements the phylotree node content type
+ */
+
+/**
+ * Implements hook_node_info().
+ *
+ * Provide information to drupal about the node types that we're creating
+ * in this module.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_node_info() {
+  $nodes = array();
+  $nodes['chado_phylotree'] = array(
+    'name'        => t('Phylotree'),
+    'base'        => 'chado_phylotree',
+    'description' => t('A phylotree from the chado database'),
+    'has_title'   => TRUE,
+    'locked'      => TRUE,
+    'chado_node_api' => array(
+      'base_table' => 'phylotree',
+      'hook_prefix' => 'chado_phylotree',
+      'record_type_title' => array(
+        'singular' => t('Phylotree'),
+        'plural' => t('Phylotrees')
+      ),
+
+      /* sync_filters: tripal is hardcoded to look for this
+       sync_filter settings: type_id and organism_id. (phylotree does
+       not have organism_id but need to set it false anyways. */
+      'sync_filters' => array(
+        'type_id' => FALSE,
+        'organism_id' => FALSE
+      ),
+    )
+  );
+  return $nodes;
+}
+
+
+/**
+ * Implements hook_node_view(). Acts on all content types
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_node_view($node, $view_mode, $langcode) {
+
+  if($node->type != 'chado_phylotree') { return; }
+
+  switch($view_mode) {
+  case 'full':
+    $node->content['tripal_phylogeny_base'] = array(
+      '#theme' => 'tripal_phylogeny_base',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'base',
+      '#tripal_toc_title' => 'Overview',
+      '#weight' => -100,
+    );
+    $node->content['tripal_phylogeny_phylogram'] = array(
+      '#theme' => 'tripal_phylogeny_phylogram',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'phylotree_phylogram',
+      '#tripal_toc_title' => 'Phylogram',
+      '#weight' => -90,
+    );
+    $node->content['tripal_phylogeny_taxonomic_tree'] = array(
+      '#theme' => 'tripal_phylogeny_taxonomic_tree',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'tripal_phylogeny_taxonomic_tree',
+      '#tripal_toc_title' => 'Taxonomic Tree',
+      '#weight' => -80,
+    );
+    $node->content['tripal_phylogeny_organisms'] = array(
+      '#theme' => 'tripal_phylogeny_organisms',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'phylotree_organisms',
+      '#tripal_toc_title' => 'Organisms',
+      '#weight' => -70,
+    );
+    $node->content['tripal_phylogeny_references'] = array(
+      '#theme' => 'tripal_phylogeny_references',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'phylotree_references',
+      '#tripal_toc_title' => 'Cross References',
+    );
+     $node->content['tripal_phylogeny_analysis'] = array(
+      '#theme' => 'tripal_phylogeny_analysis',
+      '#node' => $node,
+      '#tripal_toc_id'    => 'phylotree_analysis',
+      '#tripal_toc_title' => 'Analysis',
+    );
+    break;
+
+  case 'teaser':
+    $node->content['tripal_phylogeny_teaser'] = array(
+      '#theme' => 'tripal_phylogeny_teaser',
+      '#node' => $node,
+    );
+    break;
+    }
+}
+
+/**
+ * Implementation of hook_form().
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_form($node, &$form_state) {
+
+  $form = array();
+
+  // Default values can come in the following ways:
+  //
+  // 1) as elements of the $node object.  This occurs when editing an existing phylotree
+  // 2) in the $form_state['values'] array which occurs on a failed validation or
+  //    ajax callbacks from non submit form elements
+  // 3) in the $form_state['input'[ array which occurs on ajax callbacks from submit
+  //    form elements and the form is being rebuilt
+  //
+  // set form field defaults
+  $phylotree     = null;
+  $phylotree_id  = null;
+  $tree_name     = '';
+  $leaf_type     = '';
+  $analysis_id   = '';
+  $dbxref        = '';
+  $comment       = '';
+  $tree_required = TRUE;
+  $tree_file     = '';
+  $name_re      = '';
+  $match = '';
+
+  // If we are editing an existing node then the phylotree is already part of the node.
+  if (property_exists($node, 'phylotree')) {
+    $phylotree      = $node->phylotree;
+    $phylotree      = chado_expand_var($phylotree, 'field', 'phylotree.comment');
+    $phylotree_id   = $phylotree->phylotree_id;
+    $tree_name      = $phylotree->name;
+    $leaf_type      = $phylotree->type_id ? $phylotree->type_id->name : '';
+    $comment        = $phylotree->comment;
+    $analysis_id    = $phylotree->analysis_id ? $phylotree->analysis_id->analysis_id : '';
+    $dbxref         = $phylotree->dbxref_id->db_id->name . ":" . $phylotree->dbxref_id->accession;
+    $name_re        = $phylotree->tripal_variables->phylotree_name_re;
+    $match          = $phylotree->tripal_variables->phylotree_use_uniquename;
+
+    // If the dbxref is the null db then hide it.
+    if ($phylotree->dbxref_id->db_id->name == 'null') {
+      $dbxref = '';
+    }
+
+    // Get the tree file name. If the file was added via the Drupal interface
+    // then a numeric file_id will be present in the phylotree_tree_file
+    // variable. If not then the tree was loaded on the command-line and
+    // the actual filename is in this variable.
+    $file_id = $phylotree->tripal_variables->phylotree_tree_file;
+    if (is_numeric($file_id)) {
+      $file = file_load($file_id);
+      if ($file) {
+        $tree_file = $file->filename;
+      }
+    }
+    else {
+      $tree_file = $file_id;
+    }
+
+    // The tree file is not a required input field when editing the node.
+    $tree_required  = FALSE;
+
+    // Keep track of the phylotree id.
+    $form['phylotree_id'] = array(
+      '#type' => 'value',
+      '#value' => $phylotree_id,
+    );
+  }
+  // If we are re constructing the form from a failed validation or ajax callback
+  // then use the $form_state['values'] values.
+  if (array_key_exists('values', $form_state) and isset($form_state['values']['tree_name'])) {
+    $tree_name    = $form_state['values']['tree_name'];
+    $leaf_type    = $form_state['values']['leaf_type'];
+    $analysis_id  = $form_state['values']['analysis_id'];
+    $dbxref       = $form_state['values']['dbxref'];
+    $comment      = $form_state['values']['description'];
+  }
+  // If we are re building the form from after submission (from ajax call) then
+  // the values are in the $form_state['input'] array.
+  if (array_key_exists('input', $form_state) and !empty($form_state['input'])) {
+    $tree_name    = $form_state['input']['tree_name'];
+    $leaf_type    = $form_state['input']['leaf_type'];
+    $analysis_id  = $form_state['input']['analysis_id'];
+    $comment      = $form_state['input']['description'];
+    $dbxref       = $form_state['input']['dbxref'];
+  }
+
+  $form['tree_name']= array(
+    '#type' => 'textfield',
+    '#title' => t('Tree Name'),
+    '#required' => TRUE,
+    '#default_value' => $tree_name,
+    '#description' => t('Enter the name used to refer to this phylogenetic tree.'),
+    '#maxlength' => 255
+  );
+
+  $type_cv = tripal_get_default_cv('phylotree', 'type_id');
+  $so_cv  = tripal_get_cv(array('name' => 'sequence'));
+  $cv_id = $so_cv->cv_id;
+  if (!$so_cv) {
+    drupal_set_message('The Sequence Ontolgoy does not appear to be imported.
+        Please import the Sequence Ontology before adding a tree.', 'error');
+  }
+
+  $form['leaf_type'] = array(
+    '#title'       => t('Tree Type'),
+    '#type'        => 'textfield',
+    '#description' => t("Choose the tree type. The type is
+        a valid Sequence Ontology (SO) term. For example, trees derived
+        from protein sequences should use the SO term 'polypeptide'.
+        Alternatively, a phylotree can be used for representing a taxonomic
+        tree. In this case, the word 'taxonomy' should be used."),
+    '#required'    => TRUE,
+    '#default_value' => $leaf_type,
+    '#autocomplete_path' => "admin/tripal/chado/tripal_cv/cvterm/auto_name/$cv_id",
+  );
+
+  // Get the list of analyses.
+  $sql = "SELECT * FROM {analysis} ORDER BY name";
+  $arset = chado_query($sql);
+  $analyses = array();
+  $analyses[''] = '';
+  while ($analysis = $arset->fetchObject()) {
+    $analyses[$analysis->analysis_id] = $analysis->name;
+  }
+  $form['analysis_id'] = array(
+    '#title'         => t('Analysis'),
+    '#type'          => 'select',
+    '#description'   => t("Choose the analysis from which this phylogenetic tree was derived"),
+    '#required'      => TRUE,
+    '#default_value' => $analysis_id,
+    '#options'       => $analyses,
+  );
+
+  $form['dbxref'] = array(
+    '#title'       => t('Database Cross-Reference'),
+    '#type'        => 'textfield',
+    '#description' => t("Enter a database cross-reference of the form
+        [DB name]:[accession]. The database name must already exist in the
+        database. If the accession does not exist it is automatically added."),
+    '#required'    => FALSE,
+    '#default_value' => $dbxref,
+  );
+
+  $form['description']= array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#required' => TRUE,
+    '#default_value' => $comment,
+    '#description' => t('Enter a description for this tree.'),
+  );
+
+  $upload_location = tripal_get_files_stream('tripal_phylogeny');
+  $form['tree_file'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Tree File Import'),
+    '#collapsible' => FALSE,
+  );
+
+  $description = t('Please provide a file in the Newick format that contains
+      the nodes of this tree.');
+  if ($tree_file) {
+    $form['tree_file']['curr_file'] = array(
+      '#type' => 'item',
+      '#title' => 'Current Tree File',
+      '#markup' => $tree_file,
+    );
+    $description = t('Please provide a file in the Newick format that
+        contains the nodes of this tree.  Please note that uploading a new
+        file will overwrite the current tree.');
+  }
+  $form['tree_file']['tree_file'] = array(
+    '#type' => 'managed_file',
+    '#title' => t('New Tree File'),
+    '#description' => $description,
+    '#upload_location' => $upload_location,
+    '#upload_validators' => array(
+        // We don't want to require a specific file extension so leave the array empty.
+       'file_validate_extensions' => array(),
+        // The following is for checking the Newick file format.
+       'chado_phylotree_validate_newick_format' => array(),
+    ),
+    '#required' => $tree_required,
+  );
+
+  $form['tree_file']['name_re'] = array(
+      '#title' => t('Feature Name Regular Expression'),
+      '#type' => 'textfield',
+      '#description' => t('If this is a phylogenetic (non taxonomic) tree, then
+          the tree nodes will be automatically associated with features. However,
+          if the nodes in the tree file are not exactly as the names of features
+          but have enough information to uniquely identify the feature then you
+          may provide a regular expression that the importer will use to extract
+          the feature names from the node names.'),
+      '#default_value' => $name_re,
+  );
+  $form['tree_file']['match'] = array(
+      '#title' => t('Use Unique Feature Name'),
+      '#type' => 'checkbox',
+      '#description' => t('If this is a phylogenetic (non taonomic tree) and the nodes ' .
+          'should match the unique name of the feature rather than the name of the feautre ' .
+          'then select this box. If unselected the loader will try to match the feature ' .
+          'using the feature name.'),
+      '#default_value' => $match,
+  );
+
+  return $form;
+}
+
+/**
+ * A validation function for checking the newick file format.
+ *
+ * @param stdClass $file
+ *   A Drupal file object.
+ */
+function chado_phylotree_validate_newick_format(stdClass $file) {
+  // An array of strings where each string represents a unique error
+  // when examining the file.
+  $errors = array();
+
+  // TODO: check the newick file format for errors.
+
+  return $errors;
+}
+
+/**
+ * Implementation of hook_validate().
+ *
+ * This validation is being used for three activities:
+ *   CASE A: Update a node that exists in both drupal and chado
+ *   CASE B: Synchronizing a node from chado to drupal
+ *   CASE C: Inserting a new node that exists in niether drupal nor chado
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_validate($node, $form, &$form_state) {
+
+  // We are syncing if we do not have a node ID but we do have a phylotree_id. We don't
+  // need to validate during syncing so just skip it.
+  if (is_null($node->nid) and property_exists($node, 'phylotree_id') and $node->phylotree_id != 0) {
+    return;
+  }
+
+  // Remove surrounding white-space on submitted values.
+  $node->tree_name  = trim($node->tree_name);
+  $node->description   = trim($node->description);
+  $node->dbxref = trim($node->dbxref);
+
+  // if this is a delete then don't validate
+  if ($node->op == 'Delete') {
+    return;
+  }
+
+  $errors = array();
+  $warnings = array();
+  $options = array(
+    'name'         => $node->tree_name,
+    'description'  => $node->description,
+    'analysis_id'  => $node->analysis_id,
+    'leaf_type'    => $node->leaf_type,
+    'tree_file'    => $node->tree_file,
+    'format'       => 'newick',
+    'dbxref'       => $node->dbxref,
+    'match'        => $node->match,
+    'name_re'      => $node->name_re,
+  );
+  // If we have a node id already then this is an update:
+  if ($node->nid) {
+    $options['phylotree_id'] = $node->phylotree_id;
+    tripal_validate_phylotree('update', $options, $errors, $warnings);
+  }
+  else {
+    tripal_validate_phylotree('insert', $options, $errors, $warnings);
+  }
+
+  // Now set form errors if any errors were detected.
+  if (count($errors) > 0) {
+    foreach($errors as $field => $message) {
+      if ($field == 'name') {
+        $field = 'tree_name';
+      }
+      form_set_error($field, $message);
+    }
+  }
+  // Add any warnings if any were detected
+  if (count($warnings) > 0) {
+    foreach($warnings as $field => $message) {
+      drupal_set_message($message, 'warning');
+    }
+  }
+}
+/**
+ * Implements hook_node_presave(). Acts on all node content types.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_node_presave($node) {
+
+  switch ($node->type) {
+    // This step is for setting the title for the Drupal node.  This title
+    // is permanent and thus is created to be unique.  Title changes provided
+    // by tokens are generated on the fly dynamically, but the node title
+    // seen in the content listing needs to be set here. Do not call
+    // the chado_get_node_title() function here to set the title as the node
+    // object isn't properly filled out and the function will fail.
+    case 'chado_phylotree':
+      // for a form submission the 'phylotreename' field will be set,
+      // for a sync, we must pull from the phylotree object
+      if (property_exists($node, 'phylotreename')) {
+        // set the title
+        $node->title = $node->tree_name;
+      }
+      else if (property_exists($node, 'phylotree')) {
+        $node->title = $node->phylotree->name;
+      }
+      break;
+  }
+}
+
+/**
+ * Implements hook_node_insert().
+ * Acts on all content types.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_node_insert($node) {
+
+  switch ($node->type) {
+    case 'chado_phylotree':
+
+      $phylotree_id = chado_get_id_from_nid('phylotree', $node->nid);
+      $values = array('phylotree_id' => $phylotree_id);
+      $phylotree = chado_generate_var('phylotree', $values);
+      $phylotree = chado_expand_var($phylotree, 'field', 'phylotree.comment');
+      $node->phylotree = $phylotree;
+
+      // Now use the API to set the path.
+      chado_set_node_url($node);
+
+      // Now get the title.
+      $node->title = chado_get_node_title($node);
+
+      break;
+  }
+}
+
+/**
+ * Implements hook_node_update().
+ * Acts on all content types.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_node_update($node) {
+
+  switch ($node->type) {
+    case 'chado_phylotree':
+
+      $phylotree_id = chado_get_id_from_nid('phylotree', $node->nid);
+      $values = array('phylotree_id' => $phylotree_id);
+      $phylotree = chado_generate_var('phylotree', $values);
+      $phylotree = chado_expand_var($phylotree, 'field', 'phylotree.comment');
+      $node->phylotree = $phylotree;
+
+      // Now get the title
+      $node->title = chado_get_node_title($node);
+
+      break;
+  }
+}
+
+/**
+ * Implements [content_type]_chado_node_default_title_format().
+ *
+ * Defines a default title format for the Chado Node API to set the titles on
+ * Chado phylotree nodes based on chado fields.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_chado_node_default_title_format() {
+  return '[phylotree.name]';
+}
+
+/**
+ * Implements hook_chado_node_default_url_format().
+ *
+ * Designates a default URL format for phylotree nodes.
+ */
+function chado_phylotree_chado_node_default_url_format() {
+  return '/phylotree/[phylotree.name]';
+}
+
+/**
+ *  Implements hook_insert().
+ *
+ *  When a new chado_phylotree node is created we also need to add
+ *  information to our chado_phylotree table.  This function is called
+ *  on insert of a new node of type 'chado_phylotree' and inserts the
+ *  necessary information.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_insert($node) {
+  global $user;
+
+  $node->tree_name   = trim($node->tree_name);
+  $node->description = trim($node->description);
+  $node->dbxref      = trim($node->dbxref);
+
+  // if there is a phylotree_id in the $node object then this must
+  // be a sync (not an insert) so we can skip adding the phylotree as it is
+  // already there, although we do need to proceed with the rest of the
+  // insert.
+  $phylotree_id = NULL;
+  if (!property_exists($node, 'phylotree_id')) {
+    $options = array(
+      'name'         => $node->tree_name,
+      'description'  => $node->description,
+      'analysis_id'  => $node->analysis_id,
+      'leaf_type'    => $node->leaf_type,
+      'tree_file'    => $node->tree_file,
+      'format'       => 'newick',
+      'dbxref'       => $node->dbxref,
+      'match'        => $node->match,
+      'name_re'      => $node->name_re,
+    );
+    $errors = array();
+    $warnings = array();
+    if (tripal_insert_phylotree($options, $errors, $warnings)) {
+      $phylotree_id = $options['phylotree_id'];
+
+      // Add the Tripal variables to this node.
+      tripal_add_node_variable($node->nid, 'phylotree_name_re', $node->name_re);
+      tripal_add_node_variable($node->nid, 'phylotree_use_uniquename', $node->match);
+      tripal_add_node_variable($node->nid, 'phylotree_tree_file', $node->tree_file);
+    }
+    else {
+      drupal_set_message(t('Unable to insert phylotree.'), 'error');
+      tripal_report_error('tripal_phylogeny', TRIPAL_WARNING,
+          'Insert phylotree: Unable to insert phylotree where values: %values',
+          array('%values' => print_r($options, TRUE))
+      );
+    }
+  }
+  else {
+    $phylotree_id = $node->phylotree_id;
+  }
+
+  // Make sure the entry for this phylotree doesn't already exist in the
+  // chado_phylotree table if it doesn't exist then we want to add it.
+  $check_org_id = chado_get_id_from_nid('phylotree', $node->nid);
+  if (!$check_org_id) {
+    $record = new stdClass();
+    $record->nid = $node->nid;
+    $record->vid = $node->vid;
+    $record->phylotree_id = $phylotree_id;
+    drupal_write_record('chado_phylotree', $record);
+  }
+}
+
+/**
+ * Implements hook_update().
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_update($node) {
+
+  global $user;
+
+  $node->tree_name  = trim($node->tree_name);
+  $node->description   = trim($node->description);
+  $node->dbxref = trim($node->dbxref);
+
+  // Get the phylotree_id for this node.
+  $phylotree_id = chado_get_id_from_nid('phylotree', $node->nid) ;
+
+
+  $options = array(
+    'phylotree_id' => $node->phylotree_id,
+    'name'         => $node->tree_name,
+    'description'  => $node->description,
+    'analysis_id'  => $node->analysis_id,
+    'leaf_type'    => $node->leaf_type,
+    'tree_file'    => $node->tree_file,
+    'format'       => 'newick',
+    'dbxref'       => $node->dbxref,
+    'match'        => $node->match,
+    'name_re'      => $node->name_re,
+  );
+
+  $success = tripal_update_phylotree($phylotree_id, $options);
+
+  if (!$success) {
+    drupal_set_message("Unable to update phylotree.", "error");
+    tripal_report_error('tripal_phylogeny', TRIPAL_WARNING,
+      'Update phylotree: Unable to update phylotree where values: %values',
+      array('%values' => print_r($options, TRUE))
+    );
+    return;
+  }
+
+  // Remove any variables and then add back the variables from the form.
+  tripal_delete_node_variables($node->nid);
+  tripal_add_node_variable($node->nid, 'phylotree_name_re', $node->name_re);
+  tripal_add_node_variable($node->nid, 'phylotree_use_uniquename', $node->match);
+  tripal_add_node_variable($node->nid, 'phylotree_tree_file', $node->tree_file);
+}
+/**
+ *  Implements hook_load().
+ *
+ *  When a node is requested by the user this function is called to allow us
+ *  to add auxiliary data to the node object.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_load($nodes) {
+
+  foreach ($nodes as $nid => $node) {
+
+    $phylotree_id = chado_get_id_from_nid('phylotree', $nid);
+
+    // If the nid does not have a matching record then skip this node.
+    // this can happen with orphaned nodes.
+    if (!$phylotree_id) {
+      continue;
+    }
+
+    // Build the Chado variable for the phylotree.
+    $values = array('phylotree_id' => $phylotree_id);
+    $phylotree = chado_generate_var('phylotree', $values);
+    $nodes[$nid]->phylotree = $phylotree;
+
+    // Expand the comment field, chado_generate_var() omits it by default
+    // because it is a large text field.
+    $phylotree = chado_expand_var($phylotree, 'field', 'phylotree.comment');
+
+    // Add non Chado information to the object. These variables are needed
+    // for the edit/update forms.
+    $phylotree->tripal_variables = new stdClass;
+    $variables = tripal_get_node_variables($nid, 'phylotree_name_re');
+    $phylotree->tripal_variables->phylotree_name_re = count($variables) > 0 ? $variables[0]->value : '';
+
+    $variables = tripal_get_node_variables($nid, 'phylotree_use_uniquename');
+    $phylotree->tripal_variables->phylotree_use_uniquename = count($variables) > 0 ? $variables[0]->value : '';
+
+    $variables = tripal_get_node_variables($nid, 'phylotree_tree_file');
+    $phylotree->tripal_variables->phylotree_tree_file = count($variables) > 0 ? $variables[0]->value : '';
+
+    // Set the title for this node.
+    $node->title = chado_get_node_title($node);
+  }
+}
+
+/**
+ * Implements hook_delete().
+ *
+ * Delete data from drupal and chado databases when a node is deleted
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_delete(&$node) {
+
+  $phylotree_id = chado_get_id_from_nid('phylotree', $node->nid);
+
+  // if we don't have a phylotree id for this node then this isn't a node of
+  // type chado_phylotree or the entry in the chado_phylotree table was lost.
+  if (!$phylotree_id) {
+    return;
+  }
+
+  // Remove data from {chado_phylotree}, {node} and {node_revisions} tables of
+  // drupal database
+  $sql_del = "DELETE FROM {chado_phylotree} WHERE nid = :nid AND vid = :vid";
+  db_query($sql_del, array(':nid' => $node->nid, ':vid' => $node->vid));
+  $sql_del = "DELETE FROM {node_revision} WHERE nid = :nid AND vid = :vid";
+  db_query($sql_del, array(':nid' => $node->nid, ':vid' => $node->vid));
+  $sql_del = "DELETE FROM {node} WHERE nid = :nid AND vid = :vid";
+  db_query($sql_del, array(':nid' => $node->nid, ':vid' => $node->vid));
+
+  // Remove data from phylotree and phylotreeprop tables of chado
+  // database as well
+
+  chado_query("DELETE FROM {phylotree} WHERE phylotree_id = :phylotree_id", array(':phylotree_id' => $phylotree_id));
+
+}
+
+/**
+ * Implement hook_node_access().
+ *
+ * This hook allows node modules to limit access to the node types they define.
+ *
+ *  @param $node
+ *  The node on which the operation is to be performed, or, if it does not yet exist, the
+ *  type of node to be created
+ *
+ *  @param $op
+ *  The operation to be performed
+ *
+ *  @param $account
+ *  A user object representing the user for whom the operation is to be performed
+ *
+ *  @return
+ *  If the permission for the specified operation is not set then return FALSE. If the
+ *  permission is set then return NULL as this allows other modules to disable
+ *  access.  The only exception is when the $op == 'create'.  We will always
+ *  return TRUE if the permission is set.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function chado_phylotree_node_access($node, $op, $account) {
+
+  $node_type = $node;
+  if (is_object($node)) {
+    $node_type = $node->type;
+  }
+
+  if($node_type == 'chado_phylotree') {
+    if ($op == 'create') {
+      if (!user_access('create chado_phylotree content', $account)) {
+        return NODE_ACCESS_DENY;
+      }
+      return NODE_ACCESS_ALLOW;
+    }
+    if ($op == 'update') {
+      if (!user_access('edit chado_phylotree content', $account)) {
+        return NODE_ACCESS_DENY;
+      }
+    }
+    if ($op == 'delete') {
+      if (!user_access('delete chado_phylotree content', $account)) {
+        return NODE_ACCESS_DENY;
+      }
+    }
+    if ($op == 'view') {
+      if (!user_access('access chado_phylotree content', $account)) {
+        return NODE_ACCESS_DENY;
+      }
+    }
+    return NODE_ACCESS_IGNORE;
+  }
+}
+
+/**
+ *  Phylotree feature summary.
+ *
+ *  Get an array of feature counts by organism. key = organism
+ *  abbreviation. value = number of features for this phylotree having
+ *  this organism.
+ *
+ * @param int phylotree_id
+ * @return array
+ * @ingroup tripal_phylogeny
+ */
+function phylotree_feature_summary($phylotree_id) {
+
+  $sql = "
+    SELECT o.abbreviation, COUNT(o.organism_id) AS count
+    FROM {phylonode} n
+      LEFT OUTER JOIN {feature} f  ON n.feature_id = f.feature_id
+      LEFT OUTER JOIN {organism} o ON f.organism_id = o.organism_id
+    WHERE n.phylotree_id = :phylotree_id
+      AND n.feature_id IS NOT NULL
+    GROUP BY o.organism_id
+  ";
+
+  $args = array(':phylotree_id' => $phylotree_id);
+  $result = chado_query($sql, $args);
+  $summary = array();
+  foreach($result as $r) {
+    $summary[$r->abbreviation] = $r->count;
+  }
+  return $summary;
+}

+ 326 - 0
legacy/tripal_phylogeny/includes/tripal_phylogeny.import_tree.inc

@@ -0,0 +1,326 @@
+<?php
+
+
+/**
+ * Imports a tree file.
+ *
+ * This function is used as a wrapper for loading a phylogenetic tree using
+ * any number of file loaders.
+ *
+ * @param $file_name
+ *   The name of the file containing the phylogenetic tree to import.
+ * @param $format
+ *   The format of the file. Currently only the 'newick' file format is
+ *   supported.
+ * @param $options
+ *   Options if the phylotree record already exists:
+ *     'phylotree_id': The imported nodes will be associated with this tree.
+ *     'leaf_type':  A sequence ontology term or the word 'organism'. If the
+ *                   type is 'organism' then this tree represents a
+ *                   taxonomic tree.  The default, if not specified, is the
+ *                   term 'polypeptide'.
+ *     'name_re':    If the leaf type is NOT 'taxonomy', then the value of
+ *                   this field can be a regular expression to pull out
+ *                   the name of the feature from the node label in the
+ *                   intput tree. If no value is provided the entire label is
+ *                   used.
+ *     'match':      Set to 'uniquename' if the leaf nodes should be matched
+ *                   with the feature uniquename.
+ *
+ */
+function tripal_phylogeny_import_tree_file($file_name, $format, $options = array(), $job_id = NULL) {
+
+    // Set some option details.
+  if (!array_key_exists('leaf_type', $options)) {
+    $options['leaf_type'] = 'polypeptide';
+  }
+  if (!array_key_exists('match', $options)) {
+    $options['match'] = 'name';
+  }
+  if (!array_key_exists('name_re', $options)) {
+    $options['name_re'] = '^(.*)$';
+  }
+  $options['name_re'] = trim($options['name_re']);
+
+  // If a phylotree ID is not passed in then make sure we have the other
+  // required fields for creating a tree.
+  if (!array_key_exists('phylotree_id', $options)) {
+    if (!array_key_exists('name', $options)) {
+       tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+         'The phylotree_id is required for importing the tree.');
+       return FALSE;
+    }
+  }
+
+  // get the phylotree record.
+  $values = array('phylotree_id' => $options['phylotree_id']);
+  $phylotree = chado_generate_var('phylotree', $values);
+
+  if (!$phylotree) {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+        'Could not find the phylotree using the ID provided: %phylotree_id.',
+        array('%phylotree_id' => $options['phylotree_id']));
+    return FALSE;
+  }
+
+  $transaction = db_transaction();
+  print "\nNOTE: Loading of this tree file is performed using a database transaction. \n" .
+      "If the load fails or is terminated prematurely then the entire set of \n" .
+      "insertions/updates is rolled back and will not be found in the database\n\n";
+  try {
+
+    // Parse the file according to the format indicated.
+    if ($format == 'newick') {
+      // Parse the tree into the expected nested node format.
+      module_load_include('inc', 'tripal_phylogeny', 'includes/parsers/tripal_phylogeny.newick_parser');
+      $tree = tripal_phylogeny_parse_newick_file($file_name);
+
+      // Assign the right and left indecies to the tree ndoes
+      tripal_phylogeny_assign_tree_indices($tree);
+    }
+    // Iterate through the tree nodes and add them to Chado in accordance
+    // with the details in the $options array.
+    tripal_phylogeny_import_tree($tree, $phylotree, $options);
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    watchdog_exception('tripal_phylogeny', $e);
+    print "\nFAILED: Rolling back database changes...\n";
+  }
+  print "\nDone Importing Tree.\n";
+}
+
+/**
+ *
+ * @return boolean|multitype:Either
+ */
+function tripal_phylogeny_get_node_types_vocab() {
+  // Get the vocabulary terms used to describe nodes in the tree
+  $values = array(
+    'name' => 'phylo_leaf',
+    'cv_id' => array(
+      'name' => 'tripal_phylogeny',
+    ),
+  );
+  $leaf = chado_generate_var('cvterm', $values);
+  if (!$leaf) {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+      "Could not find the leaf vocabulary term: 'phylo_leaf'. It should " .
+      "already be present as part of the tripal_phylogeny vocabulary.");
+    return FALSE;
+  }
+  $values['name'] = 'phylo_interior';
+  $internal = chado_generate_var('cvterm', $values);
+  if (!$internal) {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+      "Could not find the leaf vocabulary term: 'phylo_interior'. It should " .
+      "already be present as part of the tripal_phylogeny vocabulary.");
+    return FALSE;
+  }
+  $values['name'] = 'phylo_root';
+  $root = chado_generate_var('cvterm', $values);
+  if (!$root) {
+    tripal_report_error('tripal_phylogeny', TRIPAL_ERROR,
+      "Could not find the leaf vocabulary term: 'phylo_root'. It should " .
+      "already be present as part of the tripal_phylogeny vocabulary.");
+    return FALSE;
+  }
+  $vocab = array(
+    'leaf' => $leaf,
+    'internal' => $internal,
+    'root' => $root,
+  );
+  return $vocab;
+}
+
+/**
+ * Iterates through the tree and sets the left and right indicies .
+ *
+ * @param $tree
+ *   The tree array.
+ * @param $index
+ *   This parameters is not used when the function is first called. It
+ *   is used for recursive calls.
+ */
+function tripal_phylogeny_assign_tree_indices(&$tree, &$index = 1) {
+  // Assign a left and right index to each node.  The child node must
+  // have a right and left index less than that of it's parents.  We
+  // increment the index by 100 to give space for new nodes that might
+  // be added later.
+  if (array_key_exists('name', $tree)) {
+    $tree['left_index'] = $index += 100;
+    if (array_key_exists('is_leaf', $tree)) {
+      $tree['right_index'] = $index += 100;
+    }
+  }
+  if (array_key_exists('branch_set', $tree)) {
+    foreach ($tree['branch_set'] as $key => $node) {
+      tripal_phylogeny_assign_tree_indices($tree['branch_set'][$key], $index);
+      $tree['right_index'] = $index += 100;
+    }
+  }
+}
+
+/**
+ * Iterates through the tree array and creates phylonodes in Chado.
+ *
+ * The function iterates through the tree in a top-down approach adding
+ * parent internal nodes prior to leaf nodes.  Each node of the tree should have
+ * the following fields:
+ *
+ *   -name:         The name (or label) for this node.
+ *   -depth:        The depth of the node in the tree.
+ *   -is_root:      Set to 1 if this node is a root node.
+ *   -is_leaf:      Set to 1 if this node is a leaf node.
+ *   -is_internal:  Set to 1 if this node is an internal node.
+ *   -left_index:   The index of the node to the left in the tree.
+ *   -right_index:  The index of the node to the right in the tree.
+ *   -branch_set:   An array containing a list of nodes of that are children
+ *                  of the node.
+ *   -parent:       The name of the parent node.
+ *   -organism_id:  The organism_id for associtating the node with an organism.
+ *   -properties:   An array of key/value pairs where the key is the cvterm_id
+ *                  and the value is the property value.  These properties
+ *                  will be assocaited with the phylonode.
+ *
+ * Prior to importing the tree the indicies can be set by using the
+ * tripal_phylogeny_assign_tree_indices() function.
+ *
+ * @param $tree
+ *   The tree array.
+ * @param $options
+ *   The options provide some direction for how the tree is imported.  The
+ *   following keys can be used:
+ *   -taxonomy:  Set to 1 if this tree is a taxonomic tree. Set to 0
+ *               otherwise.
+ *   -leaf_type: Set to the leaf type name. If this is a non-taxonomic tree
+ *               that is associated with features, then this should be the
+ *               Sequence Ontology term for the feature (e.g. polypeptide).
+ *               If this is a taxonomic tree then this option is not needed.
+ *   -match:     Set to either 'name' or 'uniquename'.  This is used for
+ *               matching the feature name or uniquename with the node name.
+ *               This is not needed for taxonomic trees.
+ *   -match_re:  Set to a regular that can be used for matching the node
+ *               name with the feature name if the node name is not
+ *               identical to the feature name.
+ * @param $vocab
+ *   Optional. An array containing a set of key/value pairs that maps node
+ *   types to CV terms.  The keys must be 'root', 'internal' or 'leaf'.  If
+ *   no vocab is provded then the terms provided by the tripal_phylogeny
+ *   CV will be used.
+ * @param $parent
+ *   This argument is not needed when the funtion is first called. This
+ *   function is recursive and this argument is used on recursive calls.
+ */
+function tripal_phylogeny_import_tree(&$tree, $phylotree, $options, $vocab = array(), $parent = NULL) {
+
+  // Get the vocabulary terms used to describe nodes in the tree if one
+  // wasn't provided.
+  if (count($vocab) == 0) {
+    $vocab = tripal_phylogeny_get_node_types_vocab();
+  }
+
+  if (is_array($tree) and array_key_exists('name', $tree)) {
+    $values = array(
+      'phylotree_id' => $phylotree->phylotree_id,
+      'left_idx'  => $tree['left_index'],
+      'right_idx' => $tree['right_index'],
+    );
+    // Add in any optional values to the $values array if they are present
+    if (!empty($tree['name']) and $tree['name'] != '') {
+      $values['label'] = $tree['name'];
+    }
+    if (!empty($tree['length']) and $tree['length'] != '') {
+      $values['distance'] = $tree['length'];
+    }
+    // Set the type of node
+    if ($tree['is_root']) {
+      $values['type_id'] = $vocab['root']->cvterm_id;
+    }
+    else if ($tree['is_internal']) {
+      $values['type_id'] = $vocab['internal']->cvterm_id;
+      $values['parent_phylonode_id'] = $parent['phylonode_id'];
+      // TOOD: a feature may be associated here but it is recommended that it
+      // be a feature of type SO:match and should represent the alignment of
+      // all features beneath it.
+    }
+    else if ($tree['is_leaf']) {
+      $values['type_id'] = $vocab['leaf']->cvterm_id;
+      $values['parent_phylonode_id'] = $parent['phylonode_id'];
+
+      // Match this leaf node with an organism or feature depending on the
+      // type of tree. But we can't do that if we don't have a name.
+      if (!empty($tree['name']) and $tree['name'] != '') {
+        if (!$options['taxonomy']) {
+
+          // This is a sequence-based tree. Try to match leaf nodes with features.
+          // First, Get the Name and uniquename for the feature
+          $matches = array();
+          $sel_values = array();
+          if ($options['match'] == "name") {
+            $sel_values['name'] = $tree['name'];
+            $re = $options['name_re'];
+            if (preg_match("/$re/", $tree['name'], $matches)) {
+              $sel_values['name'] = $matches[1];
+            }
+          }
+          else {
+            $sel_values['uniquename'] = $tree['name'];
+            $re = $options['name_re'];
+            if (preg_match("/$re/", $tree['name'], $matches)) {
+              $sel_values['uniquename'] = $matches[1];
+            }
+          }
+          $sel_values['type_id'] = array(
+            'name' => $options['leaf_type'],
+            'cv_id' => array(
+              'name' => 'sequence'
+            ),
+          );
+          $sel_columns = array('feature_id');
+          $feature = chado_select_record('feature', $sel_columns, $sel_values);
+          if (count($feature) > 1) {
+            // Found multiple features, cannot make an association.
+          }
+          else if (count($feature) == 1) {
+            $values['feature_id'] = $feature[0]->feature_id;
+          }
+          else {
+            // Could not find a feature that matches the name or uniquename
+          }
+        }
+      }
+    }
+
+    // Insert the new node and then add it's assigned phylonode_id to the node
+    $phylonode = chado_insert_record('phylonode', $values);
+    $tree['phylonode_id'] = $phylonode['phylonode_id'];
+
+    // This is a taxonomic tree, so assocaite this node with an
+    // organism if one is provided.
+    if (array_key_exists('organism_id', $tree)) {
+      $values = array(
+        'phylonode_id' => $tree['phylonode_id'],
+        'organism_id' => $tree['organism_id']
+      );
+      $pylonode_organism = chado_insert_record('phylonode_organism', $values);
+    }
+
+    // Associate any properties
+    if (array_key_exists('properties', $tree)) {
+      foreach ($tree['properties'] as $type_id => $value) {
+        $values = array(
+          'phylonode_id' => $tree['phylonode_id'],
+          'type_id' => $type_id,
+          'value' => $value,
+        );
+        $pylonode_organism = chado_insert_record('phylonodeprop', $values);
+      }
+    }
+  }
+  if (is_array($tree) and array_key_exists('branch_set', $tree)) {
+    foreach ($tree['branch_set'] as $key => $node) {
+      tripal_phylogeny_import_tree($tree['branch_set'][$key], $phylotree, $options, $vocab, $tree);
+    }
+  }
+}

+ 411 - 0
legacy/tripal_phylogeny/includes/tripal_phylogeny.taxonomy.inc

@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * Generates a page that contains the taxonomy view.
+ */
+function tripal_phylogeny_taxonomy_view() {
+  $values = array(
+    'type_id' => array(
+      'name' => 'taxonomy',
+    ),
+  );
+
+  $message = t('Site administrators:  This page is meant to provide
+      a heirarchical taxonomic tree for all of the organism present
+      in this site.  This may not be useful if you only have a few
+      species. If so, you can turn off this page by disabling this page on
+      the site\'s <a href="@menu">Navigation Menu</a>.  Otherwise, to generate the taxonomy go to this site\'s
+      <a href="@taxloader">NCBI taxonomy loader</a> to import the taxonomy information from NCBI.
+      <br><br>Note: If you add new species to this site, you should rerun the
+        NCBI taxonomy loader to update the view</p>',
+      array(
+        '@menu' => url('admin/structure/menu/manage/navigation'),
+        '@taxloader' => url('admin/tripal/loaders/ncbi_taxonomy_loader'
+      ))
+  );
+  $admin_message = tripal_set_message($message, TRIPAL_INFO, array('return_html' => TRUE));
+
+  $phylotree = chado_generate_var('phylotree', $values);
+  if ($phylotree) {
+    $node = new stdClass();
+    $node->phylotree = $phylotree;
+
+    $html =  theme('tripal_phylogeny_taxonomic_tree', array('node' => $node)) .
+      $admin_message;
+    return $html;
+  }
+
+  return array(
+    '#type' => 'markup',
+    '#markup' => t('This site has not yet prepared the taxonomy for viewing.') . $admin_message,
+  );
+}
+/**
+ *
+ */
+function tripal_phylogeny_taxonomy_load_form($form, &$form_state) {
+  $form['instructions'] = array(
+    '#type' => 'item',
+    '#markup' => '',
+  );
+
+  $form['import_existing'] = array(
+    '#type' => 'checkbox',
+    '#title' => 'Import taxonomy for existing species.',
+    '#description' =>  t('The NCBI Taxonmic Importer examines the organisms
+      currently present in the database and queries NCBI for the
+      taxonomic details.  If the importer is able to match the
+      genus and species with NCBI the species details will be imported,
+      and a page containing the taxonomic tree will be created.'),
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#name' => 'import',
+    '#value' => 'Submit',
+  );
+  return $form;
+}
+
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_taxonomy_load_form_validate($form, &$form_state) {
+  global $user;
+
+  if (!$form_state['values']['import_existing']) {
+    form_set_error('import_exists', 'Please confirm the import by clicking the checkbox.');
+  }
+}
+
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_taxonomy_load_form_submit($form, &$form_state) {
+  global $user;
+
+  if ($form_state['values']['import_existing']) {
+    $args = array();
+    tripal_add_job("Import NCBI Taxonomy", 'tripal_phylogeny',
+      'tripal_phylogeny_ncbi_taxonomy_import', $args, $user->uid);
+  }
+}
+
+/**
+ *
+ * @param unknown $job_id
+ */
+function tripal_phylogeny_ncbi_taxonomy_import($job_id) {
+
+  print "\nNOTE: Importing of NCBI taxonomy data is performed using a database transaction. \n" .
+    "If the load fails or is terminated prematurely then the entire set of \n" .
+    "insertions/updates is rolled back and will not be found in the database\n\n";
+
+  $transaction = db_transaction();
+  try {
+    // TDDO: there should be an API function named tripal_insert_analysis().
+    // But until then we have to insert the analysis manually.
+    // Get the version of this module for the analysis record:
+    $info = system_get_info('module', 'tripal_phylogeny');
+    $version = $info['version'];
+    $analysis_name = 'NCBI Taxonomy Tree Import';
+
+    // If the analysis record already exists then don't add it again.
+    $analysis = chado_select_record('analysis', array('*'), array('name' => $analysis_name));
+    if (count($analysis) == 0) {
+      $values = array(
+        'name' => 'NCBI Taxonomy Tree Import',
+        'description' => 'Used to import NCBI taxonomy details for organisms in this database.',
+        'program' => 'Tripal Phylogeny Module NCBI Taxonomy Importer',
+        'programversion' => $version,
+        'sourcename' => 'NCBI Taxonomy',
+        'sourceuri' => 'http://www.ncbi.nlm.nih.gov/taxonomy',
+      );
+      $analysis = chado_insert_record('analysis', $values);
+      if (!$analysis) {
+        throw new Exception("Cannot add NCBI Taxonomy Tree Import Analysis.");
+      }
+    }
+    else {
+      $analysis = $analysis[0];
+    }
+
+    // If the tree already exists then don't insert it again.
+    global $site_name;
+    $tree_name = $site_name . 'Taxonomy Tree';
+    $phylotree = chado_select_record('phylotree', array('*'), array('name' => $tree_name));
+    if (count($phylotree) == 0) {
+      // Add the taxonomic tree.
+      $options = array(
+        'name' =>  $site_name . 'Taxonomy Tree',
+        'description' => 'The taxonomic tree of species present on this site. Click a species name for more details.',
+        'leaf_type' => 'taxonomy',
+        'analysis_id' => $analysis->analysis_id,
+        'tree_file' => '/dev/null',
+        'format' => 'taxonomy',
+        'no_load' => TRUE,
+      );
+      $errors = array();
+      $warnings = array();
+      $success = tripal_insert_phylotree($options, $errors, $warnings);
+      if (!$success) {
+        throw new Exception("Cannot add the Taxonomy Tree record.");
+      }
+      $phylotree = (object) $options;
+    }
+    else {
+      $phylotree = $phylotree[0];
+    }
+
+    // Clean out the phylotree in the event this is a reload
+    chado_delete_record('phylonode', array('phylotree_id' => $phylotree->phylotree_id));
+
+    // The taxonomic tree must have a root, so create that first.
+    $tree = array(
+      'name' => 'root',
+      'depth' => 0,
+      'is_root' => 1,
+      'is_leaf' => 0,
+      'is_internal' => 0,
+      'left_index' => 0,
+      'right_index' => 0,
+      'branch_set' => array(),
+    );
+
+    // Get the "rank" cvterm. It requires that the TAXRANK vocabulary is loaded.
+    $rank_cvterm = tripal_get_cvterm(array(
+      'name' => 'rank',
+      'cv_id' => array('name' => 'tripal_phylogeny')
+    ));
+
+    // Get the list of organisms
+    $sql = "SELECT O.* FROM {organism} O";
+    $organisms = chado_query($sql);
+    while ($organism = $organisms->fetchObject()) {
+      // Build the query string to get the information about this species.
+      $term = $organism->genus . ' ' . $organism->species;
+      $term = urlencode($term);
+      $search_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?".
+        "db=taxonomy" .
+        "&term=$term";
+
+      // Get the search response from NCBI.
+      $rfh = fopen($search_url, "r");
+      $xml_text = '';
+      while (!feof($rfh)) {
+        $xml_text .= fread($rfh, 255);
+      }
+      fclose($rfh);
+
+      // Parse the XML to get the taxonomy ID
+      $xml = new SimpleXMLElement($xml_text);
+      if ($xml) {
+        $taxid = (string) $xml->IdList->Id;
+        if ($taxid) {
+          print "$taxid\t$organism->genus $organism->species\n";
+          // If we have a taxonomy ID we can now get the details.
+          $fetch_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?".
+            "db=taxonomy" .
+            "&id=$taxid";
+          // Get the search response from NCBI.
+          $rfh = fopen($fetch_url, "r");
+          $xml_text = '';
+          while (!feof($rfh)) {
+            $xml_text .= fread($rfh, 255);
+          }
+          fclose($rfh);
+
+          $xml = new SimpleXMLElement($xml_text);
+          if ($xml) {
+            $taxon = $xml->Taxon;
+
+            // Add in the organism properties
+            $lineage = (string) $taxon->Lineage;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'lineage', $lineage);
+
+            $genetic_code = (string) $taxon->GeneticCode->GCId;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'genetic_code', $genetic_code);
+
+            $genetic_code_name = (string) $taxon->GeneticCode->GCName;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'genetic_code_name', $genetic_code_name);
+
+            $mito_genetic_code = (string) $taxon->MitoGeneticCode->MGCId;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'mitochondrial_genetic_code', $mito_genetic_code);
+
+            $mito_genetic_code_name = (string) $taxon->MitoGeneticCode->MGCName;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'mitochondrial_genetic_code_name', $mito_genetic_code_name);
+
+            $division = (string) $taxon->Division;
+            tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'division', $division);
+
+            $name_ranks = array();
+            foreach ($taxon->OtherNames->children() as $child) {
+              $type = $child->getName();
+              $name = (string) $child;
+              if (!array_key_exists($type, $name_ranks)) {
+                $name_ranks[$type] = 0;
+              }
+              switch ($type) {
+                case 'GenbankCommonName':
+                  tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'genbank_common_name', $name, $name_ranks[$type]);
+                  break;
+                case 'Synonym':
+                  tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'synonym', $name, $name_ranks[$type]);
+                  break;
+                case 'CommonName':
+                case 'Includes':
+                  tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'other_name', $name, $name_ranks[$type]);
+                  break;
+                case 'EquivalentName':
+                  tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'equivalent_name', $name, $name_ranks[$type]);
+                  break;
+                case 'Anamorph':
+                  tripal_phylogeny_taxonomy_add_organism_property($organism->organism_id, 'anamorph', $name, $name_ranks[$type]);
+                  break;
+                case 'Name':
+                  // skip the Name stanza
+                  break;
+                default:
+                  print "NOTICE: Skipping unrecognzed name type: $type\n";
+                  // do nothing for unrecognized types
+              }
+              $name_ranks[$type]++;
+            }
+
+            // Generate a nested array structure that can be used for importing the tree.
+            $parent = (string) $taxon->ParentTaxId;
+            $rank = (string) $taxon->Rank;
+            $sci_name = (string) $taxon->ScientificName;
+            $lineage_depth = preg_split('/;\s*/', $lineage);
+            $parent = $tree;
+            $i = 1;
+            foreach ($taxon->LineageEx->children() as $child) {
+              $tid = (string) $child->TaxID;
+              $name = (string) $child->ScientificName;
+              $node_rank = (string) $child->Rank;
+              $node = array(
+                'name' => $name,
+                'depth' => $i,
+                'is_root' => 0,
+                'is_leaf' => 0,
+                'is_internal' => 1,
+                'left_index' => 0,
+                'right_index' => 0,
+                'parent' => $parent,
+                'branch_set' => array(),
+                'parent' => $parent['name'],
+                'properties' => array(
+                  $rank_cvterm->cvterm_id => $node_rank,
+                ),
+              );
+              $parent = $node;
+              tripal_phylogeny_taxonomy_import_add_node($tree, $node, $lineage_depth);
+              $i++;
+            }
+            // Now add in the leaf node
+            $node = array(
+              'name' => $sci_name,
+              'depth' => $i,
+              'is_root' => 0,
+              'is_leaf' => 1,
+              'is_internal' => 0,
+              'left_index' => 0,
+              'right_index' => 0,
+              'parent' => $parent['name'],
+              'organism_id' => $organism->organism_id,
+              'properties' => array(
+                $rank_cvterm->cvterm_id => $rank,
+              ),
+            );
+            tripal_phylogeny_taxonomy_import_add_node($tree, $node, $lineage_depth);
+
+            // Set the indecies for the tree.
+            tripal_phylogeny_assign_tree_indices($tree);
+          } // end: if ($xml) { ...
+        } // end: if ($taxid) { ...
+      } // end: if ($xml) { ...
+    } // end: while ($organism = $organisms->fetchObject()) { ...
+    // print json_encode(($tree));
+
+    // Now add the tree
+    $options = array('taxonomy' => 1);
+    tripal_phylogeny_import_tree($tree, $phylotree, $options);
+
+    // If ther user requested to sync the tree then do it.
+    //if ($sync) {
+      chado_node_sync_records('phylotree', FALSE, FALSE,
+        array(), $ids = array($phylotree->phylotree_id));
+    //}
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    print "\n"; // make sure we start errors on new line
+    watchdog_exception('tripal_phylogeny', $e);
+    print "FAILED: Rolling back database changes...\n";
+  }
+}
+
+/**
+ *
+ * @param unknown $node
+ */
+function tripal_phylogeny_taxonomy_import_add_node(&$tree, $node, $lineage_depth) {
+
+   // Get the branch set for the tree root.
+   $branch_set = &$tree['branch_set'];
+
+   // Iterate through the tree up until the depth where this node will
+   // be placed.
+   $node_depth = $node['depth'];
+   for ($i = 1; $i <= $node_depth; $i++) {
+     // Iterate through any existing nodes in the branch set to see if
+     // the node name matches the correct name for the lineage at this
+     // depth. If it matches then it is inside of this branch set that
+     // we will place the node.
+     for ($j = 0; $j < count($branch_set); $j++) {
+       // If this node already exists in the tree then return.
+       if ($branch_set[$j]['name'] == $node['name'] and
+           $branch_set[$j]['depth'] = $node['depth']) {
+         return;
+       }
+       // Otherwise, set the branch to be the current branch and continue.
+       if ($branch_set[$j]['name'] == $lineage_depth[$i-1]) {
+         $branch_set = &$branch_set[$j]['branch_set'];
+         break;
+       }
+     }
+   }
+   // Add the node to the last branch set.  This should be where this node goes.
+   $branch_set[] = $node;
+}
+
+/**
+ *
+ * @param unknown $organism_id
+ * @param unknown $term_name
+ * @param unknown $value
+ */
+function tripal_phylogeny_taxonomy_add_organism_property($organism_id, $term_name, $value, $rank = 0) {
+  if (!$value) {
+    return;
+  }
+
+  $record = array(
+    'table' => 'organism',
+    'id' => $organism_id
+  );
+  $property = array(
+    'type_name' => $term_name,
+    'cv_name' => organism_property,
+    'value' => $value
+  );
+  // Delete all properties of this type if the rank is zero.
+  if ($rank == 0) {
+    chado_delete_property($record, $property);
+  }
+  chado_insert_property($record, $property);
+}

+ 0 - 0
legacy/tripal_phylogeny/theme/css/tripal_phylogeny.css


BIN
legacy/tripal_phylogeny/theme/images/ajax-loader.gif


+ 402 - 0
legacy/tripal_phylogeny/theme/js/d3.phylogram.js

@@ -0,0 +1,402 @@
+/*
+  (agr@ncgr.org : this is a significantly modified version of
+  d3.phylogram.js....  retaining attribution/copyright per below)
+
+  d3.phylogram.js http://bl.ocks.org/kueda/1036776
+
+  Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
+  Also includes a radial dendrogram visualization (branch lengths not scaled)
+  along with some helper methods for building angled-branch trees.
+
+  Copyright (c) 2013, Ken-ichi Ueda
+
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer. Redistributions in binary
+  form must reproduce the above copyright notice, this list of conditions and
+  the following disclaimer in the documentation and/or other materials
+  provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+  POSSIBILITY OF SUCH DAMAGE.
+
+  DOCUEMENTATION
+
+  d3.phylogram.build(selector, nodes, options)
+    Creates a phylogram.
+    Arguments:
+      selector: selector of an element that will contain the SVG
+      nodes: JS object of nodes
+    Options:
+      width       
+        Width of the vis, will attempt to set a default based on the width of
+        the container.
+      height
+        Height of the vis, will attempt to set a default based on the height
+        of the container.
+      fill
+        Function for generating fill color for leaf nodes.
+      vis
+        Pre-constructed d3 vis.
+      tree
+        Pre-constructed d3 tree layout.
+      children
+        Function for retrieving an array of children given a node. Default is
+        to assume each node has an attribute called "children"
+      diagonal
+        Function that creates the d attribute for an svg:path. Defaults to a
+        right-angle diagonal.
+      skipTicks
+        Skip the tick rule.
+      skipBranchLengthScaling
+        Make a dendrogram instead of a phylogram.
+  
+  d3.phylogram.buildRadial(selector, nodes, options)
+    Creates a radial dendrogram.
+    Options: same as build, but without diagonal, skipTicks, and 
+      skipBranchLengthScaling
+  
+  d3.phylogram.rightAngleDiagonal()
+    Similar to d3.diagonal except it create an orthogonal crook instead of a
+    smooth Bezier curve.
+    
+  d3.phylogram.radialRightAngleDiagonal()
+    d3.phylogram.rightAngleDiagonal for radial layouts.
+*/
+
+if (!d3) { throw "d3 wasn't included!"};
+(function() {
+  d3.phylogram = {}
+  d3.phylogram.rightAngleDiagonal = function() {
+    var projection = function(d) { return [d.y, d.x]; }
+    
+    var path = function(pathData) {
+      return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
+    }
+    
+    function diagonal(diagonalPath, i) {
+      var source = diagonalPath.source,
+          target = diagonalPath.target,
+          midpointX = (source.x + target.x) / 2,
+          midpointY = (source.y + target.y) / 2,
+          pathData = [source, {x: target.x, y: source.y}, target];
+      pathData = pathData.map(projection);
+      return path(pathData)
+    }
+    
+    diagonal.projection = function(x) {
+      if (!arguments.length) return projection;
+      projection = x;
+      return diagonal;
+    };
+    
+    diagonal.path = function(x) {
+      if (!arguments.length) return path;
+      path = x;
+      return diagonal;
+    };
+    
+    return diagonal;
+  }
+  
+  d3.phylogram.radialRightAngleDiagonal = function() {
+    return d3.phylogram.rightAngleDiagonal()
+      .path(function(pathData) {
+        var src = pathData[0],
+            mid = pathData[1],
+            dst = pathData[2],
+            radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
+            srcAngle = d3.phylogram.coordinateToAngle(src, radius),
+            midAngle = d3.phylogram.coordinateToAngle(mid, radius),
+            clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
+            rotation = 0,
+            largeArc = 0,
+            sweep = clockwise ? 0 : 1;
+        return 'M' + src + ' ' +
+          "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
+          'L' + dst;
+      })
+      .projection(function(d) {
+        var r = d.y, a = (d.x - 90) / 180 * Math.PI;
+        return [r * Math.cos(a), r * Math.sin(a)];
+      })
+  }
+  
+  // Convert XY and radius to angle of a circle centered at 0,0
+  d3.phylogram.coordinateToAngle = function(coord, radius) {
+    var wholeAngle = 2 * Math.PI,
+        quarterAngle = wholeAngle / 4
+    
+    var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
+        coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
+    
+    // Since this is just based on the angle of the right triangle formed
+    // by the coordinate and the origin, each quad will have different 
+    // offsets
+    switch (coordQuad) {
+      case 1:
+        coordAngle = quarterAngle - coordBaseAngle
+        break
+      case 2:
+        coordAngle = quarterAngle + coordBaseAngle
+        break
+      case 3:
+        coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
+        break
+      case 4:
+        coordAngle = 3*quarterAngle + coordBaseAngle
+    }
+    return coordAngle
+  }
+
+  function scaleBranchLengths(nodes, w) {
+    // Visit all nodes and adjust y pos width distance metric
+    var visitPreOrder = function(root, callback) {
+      callback(root)
+      if (root.children) {
+        for (var i = root.children.length - 1; i >= 0; i--){
+          visitPreOrder(root.children[i], callback);
+        };
+      }
+    }
+    visitPreOrder(nodes[0], function(node) {
+      node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.length || 0)
+    })
+    var rootDists = nodes.map(function(n) { return n.rootDist; });
+    var yscale = d3.scale.linear()
+      .domain([0, d3.max(rootDists)])
+      .range([0, w]);
+    visitPreOrder(nodes[0], function(node) {
+      node.y = yscale(node.rootDist)
+    })
+    return yscale
+  }
+  
+  d3.phylogram.build = function(selector, nodes, options) {
+    options = options || {}
+    var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
+        h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
+        w = parseInt(w),
+        h = parseInt(h);
+    var fill = options.fill || function(d) {
+      return 'cyan';
+    };
+    var size = options.size || function(d) {
+      return 6;
+    }
+    var nodeMouseOver = options.nodeMouseOver || function(d) {};
+    var nodeMouseOut  = options.nodeMouseOut  || function(d) {};
+    var nodeMouseDown = options.nodeMouseDown || function(d) {};
+    
+    var tree = options.tree || d3.layout.cluster()
+      .size([h, w])
+      .sort(function(node) { return node.children ? node.children.length : -1; })
+      .children(options.children || function(node) {
+        return node.children;
+      });
+    var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
+    var vis = options.vis || d3.select(selector).append("svg:svg")
+        .attr("width", w + 300)
+        .attr("height", h + 30)
+        .append("svg:g")
+        .attr("transform", "translate(20, 20)");
+    var nodes = tree(nodes);
+    
+    if (options.skipBranchLengthScaling) {
+      var yscale = d3.scale.linear()
+        .domain([0, w])
+        .range([0, w]);
+    } 
+    else {
+      var yscale = scaleBranchLengths(nodes, w)
+    }
+    
+    if (!options.skipTicks) {
+      vis.selectAll('line')
+          .data(yscale.ticks(10))
+          .enter().append('svg:line')
+          .attr('y1', 0)
+          .attr('y2', h)
+          .attr('x1', yscale)
+          .attr('x2', yscale)
+          .attr("stroke", "#ddd");
+
+      vis.selectAll("text.rule")
+          .data(yscale.ticks(10))
+          .enter().append("svg:text")
+          .attr("class", "rule")
+          .attr("x", yscale)
+          .attr("y", 0)
+          .attr("dy", -3)
+          .attr("text-anchor", "middle")
+          .attr('font-size', '9px')
+          .attr('fill', 'grey')
+          .text(function(d) { return Math.round(d*100) / 100; });
+    }
+        
+    var link = vis.selectAll("path.link")
+        .data(tree.links(nodes))
+        .enter().append("svg:path")
+        .attr("class", "link")
+        .attr("d", diagonal)
+        .attr("fill", "none")
+        .attr("stroke", "#aaa")
+        .attr("stroke-width", "4px");
+        
+    var node = vis.selectAll("g.node")
+        .data(nodes)
+        .enter().append("svg:g")
+        .attr("class", function(n) {
+          if (n.children) {
+            if (n.depth == 0) {
+              return "root node"
+            } 
+            else {
+              return "inner node"
+            }
+          } 
+          else {
+            return "leaf node"
+          }
+        })
+        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
+
+     // style the root node
+     vis.selectAll('g.root.node')
+      .append('svg:circle')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .attr("r", size)
+      .attr('fill', 'dimgrey')
+      .attr('stroke', 'black')
+      .attr('stroke-width', '2px');
+
+    // style the leaf nodes and add js event handlers
+    vis.selectAll('g.leaf.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .attr('stroke', 'dimgrey')
+      .attr('fill', fill)
+      .attr('stroke-width', '2px');
+
+    vis.selectAll('g.inner.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .attr('stroke', 'dimgrey')
+      .attr('stroke-width', '2px')
+      .attr('fill', 'white');
+    
+    if (!options.skipLabels) {
+      vis.selectAll('g.inner.node')
+        .append("svg:text")
+          .attr("dx", -6)
+          .attr("dy", -6)
+          .attr("text-anchor", 'end')
+          .attr('font-size', '9px')
+          .attr('fill', 'black')
+        //.text(function(d) { return d.length.toFixed(4); }); // hide length
+
+      vis.selectAll('g.leaf.node').append("svg:text")
+        .attr("dx", 8)
+        .attr("dy", 3)
+        .attr("text-anchor", "start")
+        .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .text(function(d) {
+          // return d.name + ' (' + d.length.toFixed(4) + ')'; // hide length
+          return d.name;
+         });
+    }
+    return {tree: tree, vis: vis}
+  }
+  
+  d3.phylogram.buildRadial = function(selector, nodes, options) {
+    options = options || {};
+    
+    var fill = options.fill || function(d) {
+      return 'cyan';
+    };
+    var size = options.size || function(d) {
+      return 6;
+    }
+    var nodeMouseOver = options.nodeMouseOver || function(d) {};
+    var nodeMouseOut = options.nodeMouseOut || function(d) {};
+    var nodeMouseDown = options.nodeMouseDown || function(d) {};
+    
+    var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
+        r = w / 2,
+        labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
+    
+    var vis = d3.select(selector).append("svg:svg")
+        .attr("width", r * 2)
+        .attr("height", r * 2)
+        .append("svg:g")
+        .attr("transform", "translate(" + r + "," + r + ")");
+        
+    var tree = d3.layout.tree()
+      .size([360, r - labelWidth])
+      .sort(function(node) { return node.children ? node.children.length : -1; })
+      .children(options.children || function(node) {
+        return node.children;
+      })
+      .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
+
+    var phylogram = d3.phylogram.build(selector, nodes, {
+      vis: vis,
+      tree: tree,
+      fill : fill,
+      size: size,
+      nodeMouseOver : nodeMouseOver,
+      nodeMouseOut : nodeMouseOut,
+      nodeMouseDown : nodeMouseDown,
+      skipBranchLengthScaling: true,
+      skipTicks: true,
+      skipLabels: options.skipLabels,
+      diagonal: d3.phylogram.radialRightAngleDiagonal()
+    })
+    vis.selectAll('g.node')
+      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
+    
+    if (!options.skipLabels) {
+      vis.selectAll('g.leaf.node text')
+        .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
+        .attr("dy", ".31em")
+        .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
+        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
+        .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .text(function(d) {
+          return d.name;
+        });
+
+      vis.selectAll('g.inner.node text')
+        .attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
+        .attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
+        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
+    }
+    
+    return {tree: tree, vis: vis}
+  }
+}());

+ 130 - 0
legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js

@@ -0,0 +1,130 @@
+/* phylotree d3js graphs */
+
+(function ($) {
+
+  var height = 0; // will be dynamically sized
+
+  $(document).ready( function () {
+
+    // Callback function to determine node size.
+    var nodeSize = function(d) {
+      var size;
+      if (d.cvterm_name == "phylo_root") {
+        size = treeOptions['root_node_size']; 
+      }
+      if (d.cvterm_name == "phylo_interior") {
+        size = treeOptions['interior_node_size']; 
+      }
+      if (d.cvterm_name == "phylo_leaf") {
+        size = treeOptions['leaf_node_size']; 
+      }
+      return size;
+    }
+
+    // Callback function to determine the node color.
+    var organismColor = function(d) {
+      var color = null;
+      if (d.genus) {
+        color = organismColors[d.genus + ' ' + d.species];
+      }
+      if (color) { 
+        return color; 
+      }
+      else { 
+        return 'grey'; 
+      }
+    };
+
+    // Callback for mouseover event on graph node d.
+    var nodeMouseOver = function(d) {
+      var el = $(this);
+      el.attr('cursor', 'pointer');
+      var circle = el.find('circle');
+      // highlight in yellow no matter if leaf or interior node
+      circle.attr('fill', 'yellow');
+      if(!d.children) {
+        // only leaf nodes have descriptive text
+        var txt = el.find('text');
+        txt.attr('font-weight', 'bold');
+      }
+    };
+    
+    // Callback for mouseout event on graph node d.
+    var nodeMouseOut = function(d) {
+      var el = $(this);
+      el.attr('cursor', 'default');
+      var circle = el.find('circle');
+      if(!d.children) {
+        // restore the color based on organism id for leaf nodes
+        circle.attr('fill', organismColor(d));
+        var txt = el.find('text');
+        txt.attr('font-weight', 'normal');
+      }
+      else {
+        // restore interior nodes to white
+        circle.attr('fill', 'white');
+      }
+    };
+    
+    // Callback for mousedown/click event on graph node d.
+    var nodeMouseDown = function(d) {
+      var el = $(this);
+      var title = (! d.children ) ? d.name : 'interior node ' + d.phylonode_id;
+
+      if(d.children) {
+        // interior node
+        if(d.phylonode_id) {
+        }
+        else {
+          // this shouldn't happen but ok
+        }
+      }
+      else {
+        // If this node is not associated with a feature but it has an 
+        // organism node then this is a taxonomic node and we want to
+        // link it to the organism page.
+        if (!d.feature_id && d.organism_node_id) {
+          window.location.replace(baseurl + '/node/' + d.organism_node_id);
+        }
+        // leaf node
+      }
+    };
+
+    // AJAX function for retrieving the tree data.
+    $.getJSON(phylotreeDataURL, function(treeData) {
+      displayData(treeData);
+      $('.phylogram-ajax-loader').hide();
+    });
+
+    // Creates the tree using the d3.phylogram.js library.
+    function displayData(treeData) {
+      height = graphHeight(treeData);
+      d3.phylogram.build('#phylogram', treeData, {
+        'width' : treeOptions['phylogram_width'],
+        'height' : height,
+        'fill' : organismColor,
+        'size' : nodeSize,
+        'nodeMouseOver' : nodeMouseOver,
+        'nodeMouseOut' : nodeMouseOut,
+        'nodeMouseDown' : nodeMouseDown,
+        'skipTicks' : treeOptions['skipTicks']
+      });
+    }
+
+    /* graphHeight() generate graph height based on leaf nodes */
+    function graphHeight(data) {
+      function countLeafNodes(node) {
+        if(! node.children) {
+          return 1;
+        }
+        var ct = 0;
+        node.children.forEach( function(child) {
+          ct+= countLeafNodes(child);
+        });
+        return ct;
+      }
+      var leafNodeCt = countLeafNodes(data);
+      return 22 * leafNodeCt;
+    }
+  });
+})(jQuery);

+ 80 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_analysis.tpl.php

@@ -0,0 +1,80 @@
+<?php
+$node = $variables['node'];
+$phylotree = $node->phylotree;
+
+if ($phylotree->analysis_id) {
+
+  // the $headers array is an array of fields to use as the colum headers.
+  // additional documentation can be found here
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $header = array(
+    'Name',
+    'Description',
+    array(
+      'data' => 'Metadata',
+      'width' => '50%',
+    ),
+  );
+
+  // the $rows array contains an array of rows where each row is an array
+  // of values for each column of the table in that row.  Additional documentation
+  // can be found here:
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $rows = array();
+
+  $analysis = $phylotree->analysis_id;
+  if ($analysis) {
+    $analysis = chado_expand_var($analysis, 'field', 'analysis.description');
+    // Source row
+    $source = '';
+    if($analysis->sourceuri){
+      $source = "<a href=\"$analysis->sourceuri\">$analysis->sourcename</a>";
+    }
+    else {
+      $source = $analysis->sourcename;
+    }
+    if($analysis->sourceversion){
+      $source = " (" . $analysis->sourceversion . ")";
+    }
+
+    $software = $analysis->program;
+    if($analysis->programversion != 'n/a'){
+      $software .=  " (" . $analysis->programversion . ")";
+    }
+    if($analysis->algorithm){
+      $software .= ". " . $analysis->algorithm;
+    }
+    $date = preg_replace("/^(\d+-\d+-\d+) .*/","$1", $analysis->timeexecuted);
+    $metadata = "
+      <dl class=\"tripal-dl\">
+        <dt>Method</dt> <dd>: $software</dd>
+        <dt>Source</dt> <dd>: $source</dd>
+        <dt>Date</dt>   <dd>: $date</dd>
+      </dl>
+    ";
+
+    $analysis_name = $analysis->name;
+    if (property_exists($analysis, 'nid')) {
+      $analysis_name = l($analysis_name, "node/" . $analysis->nid);
+    }
+    $rows[] = array($analysis_name, $analysis->description, $metadata);
+  }
+
+  // the $table array contains the headers and rows array as well as other
+  // options for controlling the display of the table.  Additional
+  // documentation can be found here:
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $table = array(
+    'header' => $header,
+    'rows' => $rows,
+    'attributes' => array(
+      'id' => 'tripal_phylogeny-table-analysis',
+      'class' => 'tripal-data-table'
+    ),
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => t('This tree is not associated with an analysis'),
+  );
+  print theme_table($table);
+}

+ 72 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_base.tpl.php

@@ -0,0 +1,72 @@
+<?php
+$phylotree = $variables['node']->phylotree;
+$phylotree = chado_expand_var($phylotree,'field','phylotree.comment'); ?>
+
+<div class="tripal_phylogeny-data-block-desc tripal-data-block-desc"> <?php
+
+// the $headers array is an array of fields to use as the colum headers.
+// additional documentation can be found here
+// https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+// This table for the analysis has a vertical header (down the first column)
+// so we do not provide headers here, but specify them in the $rows array below.
+$headers = array();
+
+// the $rows array contains an array of rows where each row is an array
+// of values for each column of the table in that row.  Additional documentation
+// can be found here:
+// https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+$rows = array();
+
+// Name row
+$rows[] = array(
+  array(
+    'data' => 'Tree Name',
+    'header' => TRUE,
+  ),
+  $phylotree->name
+);
+
+$leaf_type = 'N/A';
+if ($phylotree->type_id) {
+  $leaf_type = $phylotree->type_id->name;
+}
+$rows[] = array(
+  array(
+    'data' => 'Leaf type',
+    'header' => TRUE,
+  ),
+  $leaf_type
+);
+
+$description = 'N/A';
+if ($phylotree->comment) {
+  $description = $phylotree->comment;
+}
+$rows[] = array(
+  array(
+    'data' => 'Description',
+    'header' => TRUE,
+  ),
+  $description
+);
+
+// the $table array contains the headers and rows array as well as other
+// options for controlling the display of the table.  Additional
+// documentation can be found here:
+// https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+$table = array(
+  'header' => $headers,
+  'rows' => $rows,
+  'attributes' => array(
+    'id' => 'tripal_phylogeny-table-base',
+    'class' => 'tripal-data-table'
+  ),
+  'sticky' => FALSE,
+  'caption' => '',
+  'colgroups' => array(),
+  'empty' => '',
+);
+
+// once we have our table array structure defined, we call Drupal's theme_table()
+// function to generate the table.
+print theme_table($table);

+ 11 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_help.tpl.php

@@ -0,0 +1,11 @@
+<h3>Module Description:</h3>
+
+<p>
+  The Tripal Phylotree module is an interface for the Chado Phylotree
+module for visualizing and browsing phylogenetic gene trees.
+</p>
+
+<h3>Setup Instructions:</h3>
+TODO
+<h3>Features of this Module:</h3>
+TODO

+ 8 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_organisms.tpl.php

@@ -0,0 +1,8 @@
+<?php
+$phylotree = $variables['node']->phylotree;
+
+if ($phylotree->has_features) { ?>
+  <div id="phylotree-organisms">
+    <!-- d3 will add svg to this div -->
+  </div> <?php
+}

+ 19 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_phylogram.tpl.php

@@ -0,0 +1,19 @@
+<?php
+$phylotree = $variables['node']->phylotree;
+
+if ($phylotree->type_id->name != "taxonomy" and $phylotree->has_nodes) {
+
+  if ($phylotree->type_id and $phylotree->type_id->name == 'polypeptide') { ?>
+    <p>Phylogenies are essential to any analysis of evolutionary gene
+      sequences collected across a group of organisms. A <b>phylogram</b>
+      is a phylogenetic tree that has branch spans proportional to the
+      amount of character change.
+    </p> <?php
+  } ?>
+
+  <div id="phylogram">
+    <!-- d3js will add svg to this div, and remove the loader gif prefix with / for absolute url --><?php
+    $ajax_loader = url(drupal_get_path('module', 'tripal_phylogeny') . '/theme/images/ajax-loader.gif'); ?>
+    <img src="<?php print $ajax_loader ?>" class="phylogram-ajax-loader"/>
+  </div> <?php
+}

+ 56 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_references.tpl.php

@@ -0,0 +1,56 @@
+<?php
+$phylotree = $variables['node']->phylotree;
+$dbxref = $phylotree->dbxref_id;
+
+// Make sure the dbxref isn't the null database. If not, then show this pane.
+if ($dbxref and $dbxref->db_id->name != 'null') { ?>
+
+  <div class="tripal_phylogeny-data-block-desc tripal-data-block-desc">This tree is also available in the following databases:</div><?php 
+  
+  // the $headers array is an array of fields to use as the colum headers.
+  // additional documentation can be found here
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $headers = array('Database', 'Accession');
+  
+  // the $rows array contains an array of rows where each row is an array
+  // of values for each column of the table in that row.  Additional documentation
+  // can be found here:
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $rows = array();
+
+  $database = $dbxref->db_id->name . ': ' . $dbxref->db_id->description;
+  if ($dbxref->db_id->url) {
+    $database = l($dbxref->db_id->name, $dbxref->db_id->url, array('attributes' => array('target' => '_blank'))) . ': ' . $dbxref->db_id->description; 
+  }
+  $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
+  if ($dbxref->db_id->urlprefix) {
+    $accession = l($accession, $dbxref->db_id->urlprefix . $dbxref->accession, array('attributes' => array('target' => '_blank')));
+  }
+  
+  $rows[] = array(
+    $database,
+    $accession
+  );
+
+  // the $table array contains the headers and rows array as well as other
+  // options for controlling the display of the table.  Additional
+  // documentation can be found here:
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  $table = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(
+      'id' => 'tripal_phylogeny-table-references',
+      'class' => 'tripal-data-table'
+    ),
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => t('There are no database cross-references for this tree'),
+  );
+  
+  // once we have our table array structure defined, we call Drupal's theme_table()
+  // function to generate the table.
+  print theme_table($table);
+}
+

+ 12 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_taxonomic_tree.tpl.php

@@ -0,0 +1,12 @@
+<?php
+$phylotree = $variables['node']->phylotree;
+$phylotree = chado_expand_var($phylotree,'field','phylotree.comment');
+
+if ($phylotree->type_id->name == "taxonomy" and $phylotree->has_nodes) {
+  print $phylotree->comment ?>
+  <div id="phylogram">
+    <!-- d3js will add svg to this div, and remove the loader gif prefix with / for absolute url --><?php
+    $ajax_loader = url(drupal_get_path('module', 'tripal_phylogeny') . '/theme/images/ajax-loader.gif'); ?>
+    <img src="<?php print $ajax_loader ?>" class="phylogram-ajax-loader"/>
+  </div> <?php
+}

+ 18 - 0
legacy/tripal_phylogeny/theme/templates/tripal_phylogeny_teaser.tpl.php

@@ -0,0 +1,18 @@
+<?php
+$node = $variables['node'];
+$phylotree = $variables['node']->phylotree;
+$phylotree = chado_expand_var($phylotree,'field','phylotree.comment'); ?>
+
+<div class="tripal_phylogeny_blast-teaser tripal-teaser">
+  <div class="tripal-phylotree-blast-teaser-title tripal-teaser-title"><?php
+    print l($node->title, "node/$node->nid", array('html' => TRUE));?>
+  </div>
+  <div class="tripal-phylotree-blast-teaser-text tripal-teaser-text"><?php
+    print substr($phylotree->comment, 0, 650);
+    if (strlen($phylotree->comment) > 650) {
+      print "... " . l("[more]", "node/$node->nid");
+    } ?>
+  </div>
+</div>
+
+

+ 102 - 0
legacy/tripal_phylogeny/theme/tripal_phylogeny.theme.inc

@@ -0,0 +1,102 @@
+<?php
+
+function tripal_phylogeny_prepare_tree_viewer($phylotree) {
+
+  // Don't prepare for viewing more than once.
+  if (property_exists($phylotree, 'prepared_to_view') and
+      $phylotree->prepared_to_view == TRUE) {
+    return;
+  }
+
+  $module_path = drupal_get_path('module', 'tripal_phylogeny');
+
+  drupal_add_js('https://d3js.org/d3.v3.min.js', 'external');
+
+  drupal_add_js("$module_path/theme/js/d3.phylogram.js");
+  drupal_add_js("$module_path/theme/js/tripal_phylogeny.js");
+  drupal_add_css("$module_path/theme/css/tripal_phylogeny.css");
+
+  drupal_add_library('system', 'ui.dialog');
+
+  // Don't show tick marks for the taxonomy tree.
+  $skip_ticks = 0;
+  if ($phylotree->type_id->name == 'taxonomy') {
+    $skip_ticks = 1;
+  }
+
+  // Get the tree options as set by the administrator.
+  $options = json_encode(array(
+    'phylogram_width' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+    'root_node_size' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+    'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
+    'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+    'skipTicks' => $skip_ticks,
+  ));
+
+  // Get the node colors as set by the administrator.
+  $colors = array();
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+  foreach ($color_defaults as $i => $details) {
+    if ($details['organism']) {
+      $colors[$details['organism']] =  $details['color'];
+    }
+  }
+  $colors = json_encode($colors);
+
+  // Add javascript data needed for this tree.
+  drupal_add_js(
+    ' // var having URL of json data source for charting
+      var phylotreeDataURL =  baseurl + "/ajax/chado_phylotree/' . $phylotree->phylotree_id . '/json";
+
+      // var with path to our theme, for use by javascript functions.
+      var pathToTheme = baseurl + "/' . $module_path . '/theme";
+
+      // var with custom options
+      var treeOptions = ' . $options . ';
+
+      // var with the organism colors
+      var organismColors = ' . $colors . ';',
+    'inline'
+  );
+
+  if (!property_exists($phylotree, 'has_nodes')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the phylogram.
+    $values  = array('phylotree_id' => $phylotree->phylotree_id);
+    $options = array('limit' => 1, 'offset' => 0, 'has_record' => 1);
+    $phylotree->has_nodes = chado_select_record('phylonode', array('phylonode_id'), $values, $options);
+  }
+  if (!property_exists($phylotree, 'has_features')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the circular dendrogram. The chado_select_record()
+    // API call can't do this query so we have to do it manually.
+    $sql = "
+      SELECT count(*) as num_features
+      FROM {phylonode}
+      WHERE NOT feature_id IS NULL and phylotree_id = :phylotree_id
+      LIMIT 1 OFFSET 0
+    ";
+    $phylotree->has_features = chado_query($sql, array(':phylotree_id' => $phylotree->phylotree_id))->fetchField();
+  }
+
+  $phylotree->prepared_to_view = TRUE;
+}
+/**
+ * Implements hook_preprocess_hook()
+ *
+ * @param $variables
+ */
+function tripal_phylogeny_preprocess_tripal_phylogeny_phylogram(&$variables) {
+  $phylotree = $variables['node']->phylotree;
+  tripal_phylogeny_prepare_tree_viewer($phylotree);
+}
+
+/**
+ * Implements hook_preprocess_hook()
+ *
+ * @param $variables
+ */
+function tripal_phylogeny_preprocess_tripal_phylogeny_taxonomic_tree(&$variables) {
+  $phylotree = $variables['node']->phylotree;
+  tripal_phylogeny_prepare_tree_viewer($phylotree);
+}

+ 243 - 0
legacy/tripal_phylogeny/tripal_phylogeny.drush.inc

@@ -0,0 +1,243 @@
+<?php
+/**
+ * @file
+ * Contains function relating to drush-integration of this module.
+ */
+
+/**
+ * Describes each drush command implemented by the module
+ *
+ * @return
+ *   The first line of description when executing the help for a given command
+ *
+ * @ingroup tripal_drush
+ */
+function tripal_phylogeny_drush_help($command) {
+  switch ($command) {
+    case 'trp-insert-phylotree':
+      return dt('Adds a new phylotree record.');
+      break;
+    case 'trp-update-phylotree':
+      return dt('Updates an existing phylotree record.');
+      break;
+    case 'trp-delete-phylotree':
+      return dt('Deletes an existing phylotree record.');
+      break;
+  }
+}
+
+/**
+ * Registers a drush command and constructs the full help for that command
+ *
+ * @return
+ *   And array of command descriptions
+ *
+ * @ingroup tripal_drush
+ */
+function tripal_phylogeny_drush_command() {
+  $items = array();
+  $items['trp-insert-phylotree'] = array(
+    'description' => dt('Adds a new phylotree record.'),
+    'arguments' => array(),
+    'examples' => array(),
+    'options' => array(
+      'username' => array(
+        'description' => dt('The Drupal user name for which the job should be run.  The permissions for this user will be used.'),
+        'required' => TRUE,
+      ),
+      'name' => array(
+        'description' => dt('The name of the tree. The name of the tree must be unique.'),
+        'required' => TRUE,
+      ),
+      'description' => array(
+        'description' => dt('A description for the tree. Use quotes.'),
+        'required' => TRUE,
+      ),
+      'analysis'  => array(
+        'description' => dt('The name of the analysis used to generate this tree. This should be the name of an analysis record already present in Chado. Use quotes.'),
+        'required' => TRUE,
+      ),
+      'leaf_type'   => array(
+        'description' => dt('The Sequence Ontology term for the leaf node type of the tree (e.g. polypeptide). If this is a taxonomic the use the word "taxonomy".'),
+        'required' => TRUE,
+      ),
+      'tree_file' => array(
+        'description' => dt('The full path to the file containing the tree.'),
+        'required' => TRUE,
+      ),
+      'format'      => array(
+        'description' => dt('The format of the input file. Currently, only the "newick" file format is supported.'),
+        'required' => TRUE,
+      ),
+      'dbxref'      => dt('A database cross-reference of the form DB:ACCESSION. Where DB is the database name, which is already present in Chado, and ACCESSION is the unique identifier for this tree in the remote database.'),
+      'sync'        => dt('Set to 1 if this tree should be synced with Drupal.'),
+      'match'       => dt('Set to "uniquename" if the leaf nodes should be matched with the feature uniquename'),
+      'name_re'     => dt('If the leaf type is NOT "taxonomy", then this option can be a regular expression used pull out the name of the feature from the node label in theintput tree.'),
+    ),
+  );
+
+  $items['trp-update-phylotree'] = array(
+    'description' => dt('Adds a new phylotree record. If a new file is provided then the entire existing tree will be rebuilt using the file provided.'),
+    'arguments' => array(),
+    'examples' => array(),
+    'options' => array(
+      'username' => array(
+        'description' => dt('The Drupal user name for which the job should be run.  The permissions for this user will be used.'),
+        'required' => TRUE,
+      ),
+      'phylotree_id' => dt('The unique phylotree ID assigned within Chado for the tree that should be updated.'),
+      'name'         => dt('A unique name for the tree. If the phylotree_id is not provided then the phylotree matching this name will be updated.'),
+      'description'  => dt('A description for the tree. Use quotes.'),
+      'analysis'     => dt('The name of the analysis used to generate this tree. This should be the name of an analysis record already present in Chado. Use quotes.'),
+      'leaf_type'    => dt('The Sequence Ontology term for the leaf node type of the tree (e.g. polypeptide). If this is a taxonomic the use the word "taxonomy".'),
+      'tree_file'    => dt('The full path to the file containing the tree.'),
+      'format'       => dt('The format of the input file. Currently, only the "newick" file format is supported.'),
+      'dbxref'       => dt('A database cross-reference of the form DB:ACCESSION. Where DB is the database name, which is already present in Chado, and ACCESSION is the unique identifier for this tree in the remote database.'),
+      'sync'         => dt('Set to 1 if this tree should be synced with Drupal.'),
+      'match'        => dt('Set to "uniquename" if the leaf nodes should be matched with the feature uniquename'),
+      'name_re'      => dt('If the leaf type is NOT "taxonomy", then this option can be a regular expression used pull out the name of the feature from the node label in theintput tree.'),
+    ),
+  );
+
+  $items['trp-delete-phylotree'] = array(
+    'username' => array(
+      'description' => dt('The Drupal user name for which the job should be run.  The permissions for this user will be used.'),
+      'required' => TRUE,
+    ),
+    'description' => dt('Deletes a phylotree record and it\'s corresponding tree nodes.'),
+    'arguments' => array(),
+    'examples' => array(),
+    'options' => array(
+      'phylotree_id' => dt('The unique phylotree ID assigned within Chado for the tree that should be deleted.'),
+      'name'         => dt('If the phylotree_id is not provided then the phylotree matching this name will be deleted.'),
+    ),
+  );
+  return $items;
+}
+
+/**
+ * Deletes a phylotree record.
+ *
+ * Executed when 'drush trp-delete-phylotree' is called.
+ *
+ * @ingroup tripal_drush
+ */
+function drush_tripal_phylogeny_trp_insert_phylotree() {
+  $username = drush_get_option('username');
+  drush_tripal_core_set_user($username);
+
+  $options = array(
+    'name'         => drush_get_option('name'),
+    'description'  => drush_get_option('description'),
+    'analysis'     => drush_get_option('analysis'),
+    'leaf_type'    => drush_get_option('leaf_type'),
+    'tree_file'    => drush_get_option('tree_file'),
+    'format'       => drush_get_option('format'),
+    'dbxref'       => drush_get_option('dbxref'),
+    'sync'         => drush_get_option('sync'),
+    'match'        => drush_get_option('match'),
+    'name_re'      => drush_get_option('name_re'),
+    'load_now'     => TRUE,
+  );
+  $errors = array();
+  $warnings = array();
+  if (tripal_insert_phylotree($options, $errors, $warnings)) {
+
+    if ($options['sync']) {
+      chado_node_sync_records('phylotree', FALSE, FALSE,
+        array(), $ids = array($options['phylotree_id']));
+
+      $nid = chado_get_nid_from_id('phylotree', $options['phylotree_id']);
+      tripal_add_node_variable($nid, 'phylotree_name_re', $options['name_re']);
+      tripal_add_node_variable($nid, 'phylotree_use_uniquename', $options['match']);
+      tripal_add_node_variable($nid, 'phylotree_tree_file', basename($options['tree_file']));
+    }
+  }
+  drush_print("Done.");
+}
+/**
+ *
+ */
+function drush_tripal_phylogeny_trp_update_phylotree() {
+  $username = drush_get_option('username');
+  drush_tripal_core_set_user($username);
+
+  $options = array(
+    'phylotree_id' => drush_get_option('phylotree_id'),
+    'name'         => drush_get_option('name'),
+    'description'  => drush_get_option('description'),
+    'analysis'     => drush_get_option('analysis'),
+    'leaf_type'    => drush_get_option('leaf_type'),
+    'tree_file'    => drush_get_option('tree_file'),
+    'format'       => drush_get_option('format'),
+    'dbxref'       => drush_get_option('dbxref'),
+    'sync'         => drush_get_option('sync'),
+    'match'        => drush_get_option('match'),
+    'name_re'      => drush_get_option('name_re'),
+    'load_now'     => TRUE,
+  );
+
+  if (!$options['phylotree_id'] and $options['name']) {
+    $phylotree = chado_select_record('phylotree', array('phylotree_id'), array('name' => $options['name']));
+    if (count($phylotree) > 0) {
+      $options['phylotree_id'] = $phylotree[0]->phylotree_id;
+    }
+    else {
+      drush_print('A phylotree record with this name does not exists.');
+    }
+  }
+
+  if($options['phylotree_id'] and tripal_update_phylotree($options['phylotree_id'], $options)) {
+
+    if ($options['sync']) {
+      chado_node_sync_records('phylotree', FALSE, FALSE,
+          array(), $ids = array($options['phylotree_id']));
+    }
+    $nid = chado_get_nid_from_id('phylotree', $options['phylotree_id']);
+    if ($nid) {
+      if ($options['name_re']) {
+        tripal_delete_node_variables($nid, 'phylotree_name_re');
+        tripal_add_node_variable($nid, 'phylotree_name_re', $options['name_re']);
+      }
+      if ($options['match']) {
+        tripal_delete_node_variables($nid, 'phylotree_use_uniquename');
+        tripal_add_node_variable($nid, 'phylotree_use_uniquename', $options['match']);
+      }
+      if ($options['tree_file']) {
+        tripal_delete_node_variables($nid, 'phylotree_tree_file');
+        tripal_add_node_variable($nid, 'phylotree_tree_file', basename($options['tree_file']));
+      }
+    }
+  }
+  drush_print("Done.");
+}
+/**
+ * Deletes a phylotree record.
+ *
+ * Executed when 'drush trp-delete-phylotree' is called.
+ *
+ * @ingroup tripal_drush
+ */
+function drush_tripal_phylogeny_trp_delete_phylotree() {
+  $username = drush_get_option('username');
+  drush_tripal_core_set_user($username);
+
+  $options = array(
+    'phylotree_id' => drush_get_option('phylotree_id'),
+    'name'         => trim(drush_get_option('name')),
+  );
+  if (!$options['phylotree_id'] and $options['name']) {
+    $phylotree = chado_select_record('phylotree', array('phylotree_id'), array('name' => $options['name']));
+    if (count($phylotree) > 0) {
+      $options['phylotree_id'] = $phylotree[0]->phylotree_id;
+    }
+    else {
+      drush_print('A phylotree record with this name does not exists.');
+    }
+  }
+
+  if ($options['phylotree_id'] and tripal_delete_phylotree($options['phylotree_id'])) {
+    chado_cleanup_orphaned_nodes('phylotree');
+  }
+  drush_print("Done.");
+}

+ 12 - 0
legacy/tripal_phylogeny/tripal_phylogeny.info

@@ -0,0 +1,12 @@
+name = Tripal Phylotree
+description = Supports the phylogeny tables of Chado by providing pages for viewing and editing of phylotrees. Table list at: http://gmod.org/wiki/chado_phylotree_Module. This module was added to Tripal through collaboration with the LegumeInformation group (http://http://legumeinfo.org/)
+core = 7.x
+project = tripal_phylogeny
+package = Tripal Extensions
+version = 7.x-2.x
+dependencies[] = tripal_core
+dependencies[] = tripal_cv
+dependencies[] = tripal_db
+dependencies[] = tripal_organism
+dependencies[] = tripal_analysis
+dependencies[] = tripal_feature

+ 514 - 0
legacy/tripal_phylogeny/tripal_phylogeny.install

@@ -0,0 +1,514 @@
+<?php
+/**
+ * @file
+ * Installation of the phylotree module
+ */
+
+/**
+ * Implements hook_install().
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_install() {
+
+  // Add the vocabularies used by the feature module.
+  tripal_phylogeny_add_cvterms();
+
+  // Set the default vocabularies.
+  tripal_set_default_cv('phylonode', 'type_id', 'tripal_phylogeny');
+  tripal_set_default_cv('phylotree', 'type_id', 'sequence');
+
+  // Add the materializedviews.
+  tripal_phylogeny_add_mview();
+
+  // We want to integrate the materialized views so that they
+  // are available for Drupal Views, upon which our search forms are built.
+  tripal_phylogeny_integrate_view();
+  $mview_id =  tripal_get_mview_id('phylotree_count');
+
+  // SPF: commented out automatic populate of MView.  If the query fails
+  // for some reason the install will not properly complete.
+  // tripal_populate_mview($mview_id);
+
+  // Add the custom tables.
+  tripal_phylogeny_add_custom_tables();
+
+  // Add an index on the phylonode table.
+  $exists = chado_index_exists('phylonode', 'parent_phylonode_id');
+  if (!$exists) {
+    chado_add_index('phylonode', 'parent_phylonode_id', array('parent_phylonode_id'));
+  }
+
+  // Add in the variables that this module will use to store properties for
+  // loading of the tree files.
+  tripal_insert_variable('phylotree_name_re', 'The regular expression for matching a name in a string.');
+  tripal_insert_variable('phylotree_use_uniquename', 'Set to 1 if features should be matched using the unqiuename rather than the name.');
+  tripal_insert_variable('phylotree_tree_file', 'Holds the Drupal file ID for the uploaded tree file.');
+}
+
+/**
+ * Implements hook_disable().
+ *
+ * Disable default views when module is disabled
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_disable() {
+  // Disable all default views provided by this module
+  require_once("tripal_phylogeny.views_default.inc");
+  $views = tripal_phylogeny_views_default_views();
+  foreach (array_keys($views) as $view_name) {
+    tripal_disable_view($view_name,FALSE,array('suppress_error' => TRUE));
+  }
+}
+
+/**
+ * Implementation of hook_requirements().
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_requirements($phase) {
+  $requirements = array();
+  if ($phase == 'install') {
+    // Make sure chado is installed.
+    if (!$GLOBALS["chado_is_installed"]) {
+      $requirements ['tripal_phylogeny'] = array(
+        'title' => "tripal_phylogeny",
+        'value' => "ERROR: Chado must be installed before this module can be enabled",
+        'severity' => REQUIREMENT_ERROR,
+      );
+    }
+  }
+  return $requirements;
+}
+
+/**
+ * Implementation of hook_schema().
+ * Standard tripal linker table between a node and a phylotree record.
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_schema() {
+  $schema['chado_phylotree'] = array(
+    'fields' => array(
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0
+      ),
+      'phylotree_id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0
+      )
+    ),
+    'indexes' => array(
+      'chado_phylotree_idx1' => array('phylotree_id')
+    ),
+    'unique keys' => array(
+      'chado_phylotree_uq1' => array('nid', 'vid'),
+      'chado_phylotree_uq2' => array('vid')
+    ),
+    'primary key' => array('nid'),
+  );
+
+  return $schema;
+}
+
+
+/**
+ * Adds controlled vocabulary terms needed by this module.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_add_cvterms() {
+
+  tripal_insert_cv(
+    'tripal_phylogeny',
+    'Terms used by the Tripal phylotree module for phylogenetic and taxonomic trees.'
+  );
+
+  // Add the terms used to identify nodes in the tree.
+  tripal_insert_cvterm(
+    array(
+      'name' => 'phylo_leaf',
+      'definition' => 'A leaf node in a phylogenetic tree.',
+      'cv_name' => 'tripal_phylogeny',
+      'is_relationship' => 0,
+      'db_name' => 'tripal'
+    ),
+    array('update_existing' => TRUE)
+  );
+  // Add the terms used to identify nodes in the tree.
+  tripal_insert_cvterm(
+    array(
+      'name' => 'phylo_root',
+      'definition' => 'The root node of a phylogenetic tree.',
+      'cv_name' => 'tripal_phylogeny',
+      'is_relationship' => 0,
+      'db_name' => 'tripal'
+    ),
+    array('update_existing' => TRUE)
+  );
+  // Add the terms used to identify nodes in the tree.
+  tripal_insert_cvterm(
+    array(
+      'name' => 'phylo_interior',
+      'definition' => 'An interior node in a phylogenetic tree.',
+      'cv_name' => 'tripal_phylogeny',
+      'is_relationship' => 0,
+      'db_name' => 'tripal'
+    ),
+    array('update_existing' => TRUE)
+  );
+  // Add the terms used to identify nodes in the tree.
+  tripal_insert_cvterm(
+    array(
+      'name' => 'taxonomy',
+      'definition' => 'A term used to indicate if a phylotree is a taxonomic tree',
+      'cv_name' => 'tripal_phylogeny',
+      'is_relationship' => 0,
+      'db_name' => 'tripal'
+    ),
+    array('update_existing' => TRUE)
+  );
+
+  tripal_insert_cvterm(
+      array(
+        'name' => 'rank',
+        'definition' => 'A taxonmic rank',
+        'cv_name' => 'tripal_phylogeny',
+        'is_relationship' => 0,
+        'db_name' => 'tripal'
+      ),
+      array('update_existing' => TRUE)
+  );
+
+//   // ----------------------------------------------
+//   // Add the terms for importing NCBI taxonomy data
+//   $values = array(
+//     'name' => 'taxonomy',
+//     'description' => 'The NCBI Taxonomy Database is a curated classification and nomenclature for all of the organisms in the public sequence databases. This currently represents about 10% of the described species of life on the planet.',
+//     'url' => 'http://www.ncbi.nlm.nih.gov/taxonomy',
+//     'urlprefix' => 'http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=',
+//   );
+//   tripal_insert_db($values);
+//   tripal_insert_cv('taxonomy', 'A taxonomy for NCBI taxonomy ranks');
+
+//   $terms = array('rank', 'taxid', 'class', 'cohort', 'family', 'forma', 'genus', 'infraclass',
+//     'infraorder', 'kingdom', 'no rank', 'order', 'parvorder', 'phylum',
+//     'species', 'species group', 'species subgroup', 'subclass', 'subcohort',
+//     'subfamily', 'subgenus', 'subkingdom', 'suborder', 'subphylum',
+//     'subspecies', 'subtribe', 'superclass', 'superfamily', 'superkingdom',
+//     'superorder', 'superphylum', 'tribe', 'varietas');
+//   $options = array('update_existing' => TRUE);
+//   foreach ($terms as $term) {
+//     $value = array(
+//       'name' => $term,
+//       'definition' => '',
+//       'cv_name' => 'taxonomy',
+//       'is_relationship' => 0,
+//       'db_name' => 'taxonomy'
+//     );
+//     tripal_insert_cvterm($value, $options);
+//   }
+
+  $terms = array(
+    'lineage',
+    'genetic_code',
+    'genetic_code_name',
+    'mitochondrial_genetic_code',
+    'mitochondrial_genetic_code_name',
+    'division',
+    'genbank_common_name',
+    'synonym',
+    'other_name',
+    'equivalent_name',
+    'anamorph'
+  );
+  $options = array('update_existing' => TRUE);
+  foreach ($terms as $term) {
+    $value = array(
+      'name' => $term,
+      'definition' => '',
+      'cv_name' => 'organism_property',
+      'is_relationship' => 0,
+      'db_name' => 'tripal'
+    );
+    tripal_insert_cvterm($value, $options);
+  }
+}
+
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function tripal_phylogeny_uninstall() {
+
+    // Drop the MView table if it exists
+    $mview_id =  tripal_get_mview_id('phylotree_count');
+    if ($mview_id) {
+        tripal_delete_mview($mview_id);
+    }
+    // Remove views integration.
+    // Note: tripal_remove_views_intergration accepts table_name and priority in a key value form.
+    $delete_view=array(
+        'table_name' => 'phylotree_count',
+        'priority' => '-1',
+    );
+    tripal_remove_views_integration($delete_view);
+
+}
+
+function tripal_phylogeny_add_mview(){
+  // Materialized view addition.
+  $sql_query="
+    WITH count_genes as
+      (SELECT count(*) count, t.phylotree_id
+       FROM phylotree t, phylonode n
+       WHERE n.phylotree_id = t.phylotree_id AND n.label is NOT NULL
+       GROUP BY t.phylotree_id)
+    SELECT
+      phylotree.phylotree_id AS phylotree_phylotree_id,
+      phylotree.name         AS phylotree_name,
+      phylotree.comment      AS phylotree_comment,
+      count_genes.count      AS total_count
+    FROM chado.phylotree phylotree
+      LEFT JOIN chado_phylotree chado_phylotree ON phylotree.phylotree_id = chado_phylotree.phylotree_id
+      LEFT JOIN count_genes count_genes         ON phylotree.phylotree_id = count_genes.phylotree_id
+  ";
+
+  // Table Phylotree User Search description.
+  $schema = array (
+    'table' => 'phylotree_count',
+    'fields' => array(
+      'phylotree_phylotree_id' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+      ),
+      'phylotree_name' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+      'phylotree_comment' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+      'total_count' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('phylotree_phylotree_id'),
+  );
+
+  // Add a comment to make sure this view makes sense to the site administator.
+  $comment = t('This view is used to provide a table for Phylotree User Search with total count.');
+  tripal_add_mview('phylotree_count', 'tripal_phylogeny',  $schema, $sql_query, $comment);
+}
+
+
+/**
+ * Integrate the qtl_search materialized view for use by Drupal Views and
+ * our search form
+ */
+
+function tripal_phylogeny_integrate_view(){
+  $integration = array (
+    'table' => 'phylotree_count',
+    'name' => 'phylotree_count',
+    'type' => 'chado',
+    'description' => '',
+    'priority' => '-1',
+    'base_table' => '1',
+    'fields' => array (
+      'phylotree_phylotree_id' => array (
+        'name' => 'phylotree_phylotree_id',
+        'title' => 'Phylotree ID',
+        'description' => 'Phylotree ID',
+        'type' => 'int',
+        'handlers' => array (
+          'filter' => array (
+            'name' => 'views_handler_filter_numeric'
+          ),
+          'field' => array (
+            'name' => 'views_handler_field_numeric'
+          ),
+          'sort' => array (
+            'name' => 'views_handler_sort'
+          ),
+          'argument' => array (
+            'name' => 'views_handler_argument_numeric'
+          ),
+          'relationship' => array (
+            'name' => 'views_handler_relationship'
+          )
+        ),
+        'joins' => array ()
+      ),
+      'phylotree_name' => array (
+        'name' => 'phylotree_name',
+        'title' => 'Family ID',
+        'description' => 'Family ID',
+        'type' => 'text',
+        'handlers' => array (
+          'filter' => array (
+            'name' => 'tripal_views_handler_filter_select_string'
+          ),
+          'field' => array (
+            'name' => 'views_handler_field'
+          ),
+          'sort' => array (
+            'name' => 'views_handler_sort'
+          ),
+          'argument' => array (
+            'name' => 'views_handler_argument_string'
+          ),
+          'relationship' => array (
+            'name' => 'views_handler_relationship'
+          )
+        ),
+        'joins' => array ()
+      ),
+      'phylotree_comment' => array (
+        'name' => 'phylotree_comment',
+        'title' => 'Description',
+        'description' => 'Description',
+        'type' => 'text',
+        'handlers' => array (
+          'filter' => array (
+            'name' => 'tripal_views_handler_filter_select_string'
+          ),
+          'field' => array (
+            'name' => 'views_handler_field'
+          ),
+          'sort' => array (
+            'name' => 'views_handler_sort'
+          ),
+          'argument' => array (
+            'name' => 'views_handler_argument_string'
+          ),
+          'relationship' => array (
+            'name' => 'views_handler_relationship'
+          )
+        ),
+        'joins' => array ()
+      ),
+      'total_count' => array (
+        'name' => 'total_count',
+        'title' => 'Total count',
+        'description' => 'Total count',
+        'type' => 'int',
+        'handlers' => array (
+          'filter' => array (
+            'name' => 'views_handler_filter_numeric'
+          ),
+          'field' => array (
+            'name' => 'views_handler_field'
+          ),
+          'sort' => array (
+            'name' => 'views_handler_sort'
+          ),
+          'argument' => array (
+            'name' => 'views_handler_argument_numeric'
+          ),
+          'relationship' => array (
+            'name' => 'views_handler_relationship'
+          )
+        ),
+        'joins' => array ()
+      )
+    )
+  );
+
+  // Add the array above that will integrate our qtl_search materialized view
+  // for use with Drupal Views.
+  tripal_add_views_integration($integration);
+}
+
+/**
+ * Add any custom tables needed by this module.
+ * - phylotreeprop: keep track of properties of phylotree
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_add_custom_tables() {
+  $schema = array (
+    'table' => 'phylotreeprop',
+    'fields' => array (
+      'phylotreeprop_id' => array (
+        'type' => 'serial',
+        'not null' => true,
+      ),
+      'phylotree_id' => array (
+        'type' => 'int',
+        'not null' => true,
+      ),
+      'type_id' => array (
+        'type' => 'int',
+        'not null' => true,
+      ),
+      'value' => array (
+        'type' => 'text',
+        'not null' => false,
+      ),
+      'rank' => array (
+        'type' => 'int',
+        'not null' => true,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array (
+      0 => 'phylotreeprop_id',
+    ),
+    'unique keys' => array (
+      'phylotreeprop_c1' => array (
+        0 => 'phylotree_id',
+        1 => 'type_id',
+        2 => 'rank',
+      ),
+    ),
+    'indexes' => array (
+      'phylotreeprop_idx1' => array (
+        0 => 'phylotree_id',
+      ),
+      'phylotreeprop_idx2' => array (
+        0 => 'type_id',
+      ),
+    ),
+    'foreign keys' => array (
+      'cvterm' => array (
+        'table' => 'cvterm',
+        'columns' => array (
+          'type_id' => 'cvterm_id',
+        ),
+      ),
+      'phylotree' => array (
+        'table' => 'phylotree',
+        'columns' => array (
+          'phylotree_id' => 'phylotree_id',
+        ),
+      ),
+    ),
+  );
+  chado_create_custom_table('phylotreeprop', $schema, TRUE);
+}
+
+/**
+ * Adds the vocabulary needed for storing taxonmies.
+ */
+function tripal_phylogeny_update_7200() {
+  try {
+    tripal_phylogeny_add_cvterms();
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Failed to complete update' . $error);
+  }
+}

+ 398 - 0
legacy/tripal_phylogeny/tripal_phylogeny.module

@@ -0,0 +1,398 @@
+<?php
+
+/**
+ * @file
+ * Integrates the Chado Phylotree module with Drupal Nodes & Views
+ */
+
+/**
+ * @defgroup tripal_phylogeny Phylotree Module
+ * @ingroup tripal_modules
+ * @{
+ * Integrates the Chado Phylotree module with Drupal Nodes
+ * @}
+ */
+require_once 'api/tripal_phylogeny.api.inc';
+require_once 'theme/tripal_phylogeny.theme.inc';
+require_once 'includes/tripal_phylogeny.admin.inc';
+require_once 'includes/tripal_phylogeny.chado_node.inc';
+require_once 'includes/tripal_phylogeny.import_tree.inc';
+require_once 'includes/tripal_phylogeny.taxonomy.inc';
+
+/**
+ * Implements hook_permission().
+ *
+ * Set the permission types that the chado module uses.  Essentially we
+ * want permissionis that protect creation, editing and deleting of chado
+ * data objects
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_permission() {
+  return array(
+    'access chado_phylotree content' => array(
+      'title' => t('View Phylotrees'),
+      'description' => t('Allow users to view phylotree pages.'),
+    ),
+    'administer tripal phylotree' => array(
+      'title' => t('Administer Phylotrees'),
+      'description' => t('Allow users to administer all phylotrees.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ *
+ * Menu items are automatically added for the new node types created
+ * by this module to the 'Create Content' Navigation menu item.  This function
+ * adds more menu items needed for this module.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_menu() {
+  $items = array();
+
+  // administration landing page. currently has no content but is
+  // apparently required for the Sync and Help links to work.
+  $items['admin/tripal/chado/tripal_phylogeny'] = array(
+    'title' => 'Phylogeny and Taxonomy',
+    'description' => 'Phylogenetic and taxonomic trees.',
+    'page callback' => 'tripal_phylogeny_admin_phylotrees_listing',
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+
+  // help menu
+  $items['admin/tripal/chado/tripal_phylogeny/help'] = array(
+    'title' => 'Help',
+    'description' => 'Basic Description of Tripal Phylotree Module Functionality',
+    'page callback' => 'theme',
+    'page arguments' => array('tripal_phylogeny_help'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10
+  );
+
+  // configuration menu item
+  $items['admin/tripal/chado/tripal_phylogeny/configuration'] = array(
+    'title' => 'Settings',
+    'description' => 'Configure the Tripal Phylotree module',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_phylogeny_admin'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1
+  );
+
+  $items['admin/tripal/chado/tripal_phylogeny/plots'] = array(
+    'title' => 'Plot Defaults',
+    'description' => 'Set defaults for the trees',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_phylogeny_default_plots_form'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2
+  );
+
+  // sync menu item (will be rendered as a tab by tripal)
+  $items['admin/tripal/chado/tripal_phylogeny/sync'] = array(
+    'title' => ' Sync',
+    'description' => 'Create pages on this site for phylotrees stored in Chado',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('chado_node_sync_form', 'tripal_phylogeny', 'chado_phylotree'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 3
+  );
+
+  // Enable admin view
+  $items['admin/tripal/chado/tripal_phylogeny/views/phylotree/enable'] = array(
+    'title' => 'Enable Phylotree Administrative View',
+    'page callback' => 'tripal_enable_view',
+    'page arguments' => array('tripal_phylogeny_admin_phylotree', 'admin/tripal/chado/tripal_phylogeny'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_CALLBACK,
+  );
+  // Data Loaders
+   $items['admin/tripal/loaders/newic_phylotree_loader'] = array(
+     'title' => 'Phylogenetic Trees (Newic format)',
+     'description' => 'Loads phylogenetic trees in Newic format. (Redirects to create a phylogenetic tree content type)',
+     'page callback' => 'drupal_goto',
+     'page arguments' => array('node/add/chado-phylotree'),
+     'access arguments' => array('administer tripal phylotree'),
+     'type' => MENU_NORMAL_ITEM,
+   );
+
+   $items['admin/tripal/loaders/ncbi_taxonomy_loader'] = array(
+     'title' => 'NCBI Taxonomy Loader',
+     'description' => 'Loads taxonomic details about installed organisms.',
+     'page callback' => 'drupal_get_form',
+     'page arguments' => array('tripal_phylogeny_taxonomy_load_form'),
+     'access arguments' => array('administer tripal phylotree'),
+     'file' => '/includes/tripal_phylogeny.taxonomy.inc',
+     'type' => MENU_NORMAL_ITEM,
+   );
+
+   $items['taxonomy_view'] = array(
+     'title' => 'Taxonomy',
+     'description' => 'Taxonomic view of the species available on this site.',
+     'page callback' => 'tripal_phylogeny_taxonomy_view',
+     'access arguments' => array('access taxonomy content'),
+     'file' => '/includes/tripal_phylogeny.taxonomy.inc',
+     'type' => MENU_NORMAL_ITEM,
+   );
+
+   // create a route for viewing json of all phylonodes having this phylotree_id
+   $items['ajax/chado_phylotree/%/json'] = array(
+     'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
+     'page arguments' => array(2),
+     // allow all anonymous http clients
+     'access callback' => TRUE
+   );
+
+  return $items;
+}
+
+/**
+ * Implements hook_search_biological_data_views().
+ *
+ * Adds the described views to the "Search Data" Page created by Tripal Views
+ */
+function tripal_phylogeny_search_biological_data_views() {
+  return array(
+      'tripal_phylogeny_user_phylotree' => array(
+          'machine_name' => 'tripal_phylogeny_user_phylotree',
+          'human_name' => 'Phylogenetic Trees',
+          'description' => 'Gene trees, species trees, etc.',
+          'link' => 'chado/phylotree'
+      ),
+  );
+}
+
+/**
+ * Implements hook_views_api().
+ *
+ * Essentially this hook tells drupal that there is views support for
+ *  for this module which then includes tripal_db.views.inc where all the
+ *  views integration code is
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_views_api() {
+  return array(
+    'api' => 3.0,
+  );
+}
+
+/**
+ *  Implements hook_theme().
+ *
+ * We need to let drupal know about our theme functions and their arguments.
+ *  We create theme functions to allow users of the module to customize the
+ *  look and feel of the output generated in this module
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_theme($existing, $type, $theme, $path) {
+  $core_path = drupal_get_path('module', 'tripal_core');
+  $items = array(
+    // built-in theme
+    'node__chado_phylotree' => array(
+      'template' => 'node--chado-generic',
+      'render element' => 'node',
+      'base hook' => 'node',
+      'path' => "$core_path/theme/templates",
+    ),
+    // base template for this page (default tab) includes the phylogram
+    'tripal_phylogeny_base' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_base',
+      'path' => "$path/theme/templates",
+    ),
+    // Template for the phylogram.
+    'tripal_phylogeny_phylogram' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_phylogram',
+      'path' => "$path/theme/templates",
+    ),
+    // Template for the taxonomic tree.
+    'tripal_phylogeny_taxonomic_tree' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_taxonomic_tree',
+      'path' => "$path/theme/templates",
+    ),
+    // partial for organisms block
+    'tripal_phylogeny_organisms' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_organisms',
+      'path' => "$path/theme/templates",
+    ),
+    // partial for cross references block
+    'tripal_phylogeny_references' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_references',
+      'path' => "$path/theme/templates",
+    ),
+    // partial for cross references block
+    'tripal_phylogeny_analysis' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_analysis',
+      'path' => "$path/theme/templates",
+    ),
+    // partial for teaser view
+    'tripal_phylogeny_teaser' => array(
+      'variables' => array('node' => NULL),
+      'template' => 'tripal_phylogeny_teaser',
+      'path' => "$path/theme/templates",
+    ),
+
+    // FORM THEMES
+    // Theme function for the project table in admin projects form
+    'tripal_phylogeny_admin_org_color_tables' => array(
+      'render element' => 'element',
+    )
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_help().
+ * Adds a help page to the module list
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_help ($path, $arg) {
+  if ($path == 'admin/help#tripal_phylogeny') {
+    return theme('tripal_phylogeny_help', array());
+  }
+}
+
+/**
+ * Get json representation of a phylotree id.
+ *
+ * This function is meant to be called via AJAX.
+ *
+ * @param int $phylotree_id
+ *   the ID of the phylotree node.
+ *
+ * @return string json
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
+
+  $phylotree = chado_generate_var('phylotree', array('phylotree_id' => $phylotree_id));
+
+  // This SQL gets all of the phylonodes for a given tree as well as the
+  // features and organisms with which it is assocaited.  Each phylonode
+  // can be associated with an orgnaism in one of two ways: 1) via a
+  // feature linked by the phylonode.feature_id field or 2) via a
+  // a record in the phylonde_organsim table.  Therefore both types of
+  // organism records are returned in the query below, but those
+  // retrieved via a FK link on features are prefixed with 'fo_'.
+  $sql = "
+    SELECT
+      n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
+      f.feature_id, f.name AS feature_name,
+      cvt.name AS cvterm_name,
+      o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
+      fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
+      fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species,
+      cf.nid AS feature_node_id,
+      fco.nid AS fo_organism_node_id,
+      co.nid AS organism_node_id
+    FROM {phylonode} n
+      LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
+      LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
+      LEFT OUTER JOIN [chado_feature] cf        ON cf.feature_id = f.feature_id
+      LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
+      LEFT OUTER JOIN [chado_organism] fco      ON fco.organism_id = fo.organism_id
+      LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
+      LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
+      LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
+    WHERE n.phylotree_id = :phylotree_id
+  ";
+  $args = array(':phylotree_id' => $phylotree_id);
+  $result = chado_query($sql, $args);
+
+  // Fetch all the phylonodes into an assoc array indexed by phylonode_id.
+  // Convert from resultset record to array, fixing datatypes. chado_query
+  // returns numeric as string and fun stuff like that.
+  $phylonodes = array();
+  $root_phylonode_ref = null;
+
+  foreach ($result as $r) {
+    $phylonode_id = (int) $r->phylonode_id;
+
+    // expect all nodes to have these properties
+    $node = array(
+      'phylonode_id' => $phylonode_id,
+      'parent_phylonode_id' => (int) $r->parent_phylonode_id,
+      'length' => (double) $r->length,
+      'cvterm_name' => $r->cvterm_name
+    );
+
+    // If the nodes are taxonomic then set an equal distance
+    if ($phylotree->type_id->name == 'taxonomy') {
+      $node['length'] = 0.001;
+    }
+
+    // Other props may exist only for leaf nodes
+    if ($r->name) {
+      $node['name'] = $r->name;
+    }
+    // If this node is associated with a feature then add in the details
+    if ($r->feature_id) {
+      $node['feature_id'] = (int) $r->feature_id;
+      $node['feature_name'] = $r->feature_name;
+      $node['feature_node_id'] = (int) $r->feature_node_id;
+    }
+    // Add in the organism fields when they are available via the
+    // phylonode_organism table.
+    if ($r->organism_id) {
+      $node['organism_id'] = (int) $r->organism_id;
+      $node['common_name'] = $r->common_name;
+      $node['abbreviation'] = $r->abbreviation;
+      $node['genus'] = $r->genus;
+      $node['species'] = $r->species;
+      $node['organism_node_id'] = (int) $r->organism_node_id;
+      // If the node does not have a name but is linked to an organism
+      // then set the name to be that of the genus and species.
+      if (!$r->name) {
+        $node['name'] = $r->genus . ' ' . $r->species;
+      }
+    }
+    // Add in the organism fields when they are available via the
+    // the phylonode.feature_id FK relationship.
+    if ($r->fo_organism_id) {
+      $node['fo_organism_id'] = (int) $r->fo_organism_id;
+      $node['fo_common_name'] = $r->fo_common_name;
+      $node['fo_abbreviation'] = $r->fo_abbreviation;
+      $node['fo_genus'] = $r->fo_genus;
+      $node['fo_species'] = $r->fo_species;
+      $node['fo_organism_node_id'] = (int) $r->fo_organism_node_id;
+    }
+
+    // Add this node to the list, organized by ID.
+    $phylonodes[$phylonode_id] = $node;
+  }
+
+  // Populate the children[] arrays for each node.
+  foreach ($phylonodes as $key => &$node) {
+    if ($node['parent_phylonode_id'] !== 0) {
+      $parent_ref = &$phylonodes[ $node['parent_phylonode_id']];
+      // Append node refernce to children.
+      $parent_ref['children'][] = &$node;
+    }
+    else {
+      $root_phylonode_ref = &$node;
+    }
+  }
+
+  // dump datastructure as json to browser. drupal sets the mime-type correctly.
+  drupal_json_output($root_phylonode_ref);
+}
+
+
+

+ 23 - 0
legacy/tripal_phylogeny/tripal_phylogeny.views.inc

@@ -0,0 +1,23 @@
+<?php
+/**
+ *  @file
+ *  This file contains the basic functions for views integration of
+ *  chado/tripal phylogeny tables
+ */
+
+/**
+ * Implements hook_views_handlers().
+ *
+ * Purpose: Register all custom handlers with views
+ *   where a handler describes either "the type of field",
+ *   "how a field should be filtered", "how a field should be sorted"
+ *
+ * @return: An array of handler definitions
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_views_handlers() {
+  return array(
+
+  );
+}

+ 276 - 0
legacy/tripal_phylogeny/tripal_phylogeny.views_default.inc

@@ -0,0 +1,276 @@
+<?php
+/**
+ * @file
+ * Describe default phylotree views
+ */
+
+/**
+ * Implements hook_views_default_views().
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_views_default_views() {
+  $views = array();
+
+  // User View ("Search Biological Content")
+  // Remember, if you change the name/path of this view, you also want to
+  // change it's description in tripal_phylogeny_search_biological_data_views()
+  $view = tripal_phylogeny_defaultvalue_user_phylotrees();
+  $view = tripal_make_view_compatible_with_external($view);
+  $views[$view->name] = $view;
+
+  /// Admin view
+  $view = tripal_phylogeny_defaultview_admin_phylotree();
+  $view = tripal_make_view_compatible_with_external($view);
+  $views[$view->name] = $view;
+
+  return $views;
+}
+
+/**
+ * The default phylotree administration view.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_defaultview_admin_phylotree() {
+  $view = new view();
+  $view->name = 'tripal_phylogeny_admin_phylotree';
+  $view->description = 'DO NOT DISABLE';
+  $view->tag = 'tripal admin';
+  $view->base_table = 'phylotree';
+  $view->human_name = 'Phylogeny Admin';
+  $view->core = 0;
+  $view->api_version = '3.0';
+  $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+  /* Display: phylotree_all */
+  $handler = $view->new_display('default', 'phylotree_all', 'default');
+  $handler->display->display_options['title'] = 'Phylogeny';
+  $handler->display->display_options['use_more_always'] = FALSE;
+  $handler->display->display_options['access']['type'] = 'perm';
+  $handler->display->display_options['access']['perm'] = 'access chado_phylotree content';
+  $handler->display->display_options['cache']['type'] = 'none';
+  $handler->display->display_options['query']['type'] = 'views_query';
+  $handler->display->display_options['exposed_form']['type'] = 'basic';
+  $handler->display->display_options['pager']['type'] = 'full';
+  $handler->display->display_options['pager']['options']['items_per_page'] = '25';
+  $handler->display->display_options['style_plugin'] = 'table';
+  /* Header: Global: Action Links */
+  $handler->display->display_options['header']['action_links_area']['id'] = 'action_links_area';
+  $handler->display->display_options['header']['action_links_area']['table'] = 'views';
+  $handler->display->display_options['header']['action_links_area']['field'] = 'action_links_area';
+  $handler->display->display_options['header']['action_links_area']['label'] = 'Action Links';
+  $handler->display->display_options['header']['action_links_area']['link-1'] = array(
+    'label-1' => 'Add Phylogenetic Tree',
+    'path-1' => 'node/add/chado-phylotree',
+  );
+  $handler->display->display_options['header']['action_links_area']['link-2'] = array(
+    'label-2' => '',
+    'path-2' => '',
+  );
+  $handler->display->display_options['header']['action_links_area']['link-3'] = array(
+    'label-3' => '',
+    'path-3' => '',
+  );
+  $handler->display->display_options['header']['action_links_area']['link-4'] = array(
+    'label-4' => '',
+    'path-4' => '',
+  );
+  /* Relationship: Phylotree: Phylotree => Node */
+  $handler->display->display_options['relationships']['phylotree_id']['id'] = 'phylotree_id';
+  $handler->display->display_options['relationships']['phylotree_id']['table'] = 'chado_phylotree';
+  $handler->display->display_options['relationships']['phylotree_id']['field'] = 'phylotree_id';
+  /* Field: Content: Nid */
+  $handler->display->display_options['fields']['nid']['id'] = 'nid';
+  $handler->display->display_options['fields']['nid']['table'] = 'node';
+  $handler->display->display_options['fields']['nid']['field'] = 'nid';
+  $handler->display->display_options['fields']['nid']['relationship'] = 'phylotree_id';
+  $handler->display->display_options['fields']['nid']['exclude'] = TRUE;
+  /* Field: Chado Phylotree: Phylotree Id */
+  $handler->display->display_options['fields']['phylotree_id']['id'] = 'phylotree_id';
+  $handler->display->display_options['fields']['phylotree_id']['table'] = 'phylotree';
+  $handler->display->display_options['fields']['phylotree_id']['field'] = 'phylotree_id';
+  $handler->display->display_options['fields']['phylotree_id']['exclude'] = TRUE;
+  /* Field: Chado Phylotree: Name */
+  $handler->display->display_options['fields']['name']['id'] = 'name';
+  $handler->display->display_options['fields']['name']['table'] = 'phylotree';
+  $handler->display->display_options['fields']['name']['field'] = 'name';
+  $handler->display->display_options['fields']['name']['label'] = 'Tree Name';
+  $handler->display->display_options['fields']['name']['alter']['make_link'] = TRUE;
+  $handler->display->display_options['fields']['name']['alter']['path'] = 'node/[nid]';
+  /* Field: Chado Phylotree: Phylotree Id */
+  $handler->display->display_options['fields']['phylotree_id_1']['id'] = 'phylotree_id_1';
+  $handler->display->display_options['fields']['phylotree_id_1']['table'] = 'phylotree';
+  $handler->display->display_options['fields']['phylotree_id_1']['field'] = 'phylotree_id';
+  $handler->display->display_options['fields']['phylotree_id_1']['exclude'] = TRUE;
+  /* Field: Chado Analysis: Name */
+  $handler->display->display_options['fields']['name_1']['id'] = 'name_1';
+  $handler->display->display_options['fields']['name_1']['table'] = 'analysis';
+  $handler->display->display_options['fields']['name_1']['field'] = 'name';
+  $handler->display->display_options['fields']['name_1']['label'] = 'Analysis';
+  /* Field: Chado Cvterm: Name */
+  $handler->display->display_options['fields']['name_2']['id'] = 'name_2';
+  $handler->display->display_options['fields']['name_2']['table'] = 'cvterm';
+  $handler->display->display_options['fields']['name_2']['field'] = 'name';
+  $handler->display->display_options['fields']['name_2']['label'] = 'Leaf Node Type';
+  /* Field: Chado Phylotree: Comment */
+  $handler->display->display_options['fields']['comment']['id'] = 'comment';
+  $handler->display->display_options['fields']['comment']['table'] = 'phylotree';
+  $handler->display->display_options['fields']['comment']['field'] = 'comment';
+  $handler->display->display_options['fields']['comment']['label'] = 'Description';
+  /* Sort criterion: Chado Phylotree: Name */
+  $handler->display->display_options['sorts']['name']['id'] = 'name';
+  $handler->display->display_options['sorts']['name']['table'] = 'phylotree';
+  $handler->display->display_options['sorts']['name']['field'] = 'name';
+  /* Filter criterion: Chado Phylotree: Name */
+  $handler->display->display_options['filters']['name_1']['id'] = 'name_1';
+  $handler->display->display_options['filters']['name_1']['table'] = 'phylotree';
+  $handler->display->display_options['filters']['name_1']['field'] = 'name';
+  $handler->display->display_options['filters']['name_1']['group'] = 1;
+  $handler->display->display_options['filters']['name_1']['exposed'] = TRUE;
+  $handler->display->display_options['filters']['name_1']['expose']['operator_id'] = 'name_1_op';
+  $handler->display->display_options['filters']['name_1']['expose']['label'] = 'Tree Name';
+  $handler->display->display_options['filters']['name_1']['expose']['description'] = 'Provide the name of a phylogenetic tree';
+  $handler->display->display_options['filters']['name_1']['expose']['use_operator'] = TRUE;
+  $handler->display->display_options['filters']['name_1']['expose']['operator'] = 'name_1_op';
+  $handler->display->display_options['filters']['name_1']['expose']['identifier'] = 'name_1';
+  $handler->display->display_options['filters']['name_1']['expose']['remember_roles'] = array(
+    2 => '2',
+    1 => 0,
+    3 => 0,
+  );
+  $handler->display->display_options['filters']['name_1']['max_length'] = '40';
+
+  /* Display: Page */
+  $handler = $view->new_display('page', 'Page', 'page');
+  $handler->display->display_options['path'] = 'admin/tripal/chado/tripal_phylogeny/phylotree';
+  $handler->display->display_options['menu']['type'] = 'default tab';
+  $handler->display->display_options['menu']['title'] = 'Trees';
+  $handler->display->display_options['menu']['weight'] = '0';
+  $handler->display->display_options['menu']['context'] = 0;
+  $handler->display->display_options['menu']['context_only_inline'] = 0;
+  $handler->display->display_options['tab_options']['weight'] = '0';
+
+
+  return $view;
+}
+/**
+ * Defines the phylotree user search default view.
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_defaultvalue_user_phylotrees() {
+
+    $view = new view();
+    $view->name = 'tripal_phylogeny_user_phylotree';
+    $view->description = 'Allows users to search phylotree data';
+    $view->tag = 'tripal search';
+    $view->base_table = 'phylotree_count';
+    $view->human_name = 'Phylogeny User Search';
+    $view->core = 0;
+    $view->api_version = '3.0';
+    $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+    /* Display: phylotrees_all */
+    $handler = $view->new_display('default', 'phylotrees_all', 'default');
+    $handler->display->display_options['title'] = 'Phylogeny User Search';
+    $handler->display->display_options['use_more_always'] = FALSE;
+    $handler->display->display_options['access']['type'] = 'perm';
+    $handler->display->display_options['access']['perm'] = 'access chado_phylotree content';
+    $handler->display->display_options['cache']['type'] = 'none';
+    $handler->display->display_options['query']['type'] = 'views_query';
+    $handler->display->display_options['exposed_form']['type'] = 'basic';
+    $handler->display->display_options['pager']['type'] = 'full';
+    $handler->display->display_options['pager']['options']['items_per_page'] = '15';
+    $handler->display->display_options['pager']['options']['offset'] = '0';
+    $handler->display->display_options['pager']['options']['id'] = '0';
+    $handler->display->display_options['pager']['options']['quantity'] = '9';
+    $handler->display->display_options['style_plugin'] = 'table';
+    /* Header: Global: Text area */
+    $handler->display->display_options['header']['area']['id'] = 'area';
+    $handler->display->display_options['header']['area']['table'] = 'views';
+    $handler->display->display_options['header']['area']['field'] = 'area';
+    $handler->display->display_options['header']['area']['empty'] = TRUE;
+    $handler->display->display_options['header']['area']['content'] = 'Search for a gene family by submitting annotation terms, PFAM IDs, or GO terms in the "Tree Description" field (e.g.cytochrome, IPR008914, homeobox leucine zipper, or GO:0003677) ... or by "Family ID" (e.g. 54689426; these IDs correspond to the Phytozome v10 gene families).
+';
+    $handler->display->display_options['header']['area']['format'] = 'filtered_html';
+    /* Header: Global: Result summary */
+    $handler->display->display_options['header']['result']['id'] = 'result';
+    $handler->display->display_options['header']['result']['table'] = 'views';
+    $handler->display->display_options['header']['result']['field'] = 'result';
+    /* Footer: Global: Result summary */
+    $handler->display->display_options['footer']['result']['id'] = 'result';
+    $handler->display->display_options['footer']['result']['table'] = 'views';
+    $handler->display->display_options['footer']['result']['field'] = 'result';
+    $handler->display->display_options['footer']['result']['content'] = 'Displaying @start - @end of @total records found.';
+    /* Field: phylotree_count: Family ID */
+    $handler->display->display_options['fields']['phylotree_name']['id'] = 'phylotree_name';
+    $handler->display->display_options['fields']['phylotree_name']['table'] = 'phylotree_count';
+    $handler->display->display_options['fields']['phylotree_name']['field'] = 'phylotree_name';
+    /* Field: phylotree_count: Description */
+    $handler->display->display_options['fields']['phylotree_comment']['id'] = 'phylotree_comment';
+    $handler->display->display_options['fields']['phylotree_comment']['table'] = 'phylotree_count';
+    $handler->display->display_options['fields']['phylotree_comment']['field'] = 'phylotree_comment';
+    /* Field: phylotree_count: Total count */
+    $handler->display->display_options['fields']['total_count']['id'] = 'total_count';
+    $handler->display->display_options['fields']['total_count']['table'] = 'phylotree_count';
+    $handler->display->display_options['fields']['total_count']['field'] = 'total_count';
+    /* Filter criterion: phylotree_count: Family ID */
+    $handler->display->display_options['filters']['phylotree_name']['id'] = 'phylotree_name';
+    $handler->display->display_options['filters']['phylotree_name']['table'] = 'phylotree_count';
+    $handler->display->display_options['filters']['phylotree_name']['field'] = 'phylotree_name';
+    $handler->display->display_options['filters']['phylotree_name']['exposed'] = TRUE;
+    $handler->display->display_options['filters']['phylotree_name']['expose']['operator_id'] = 'phylotree_name_op';
+    $handler->display->display_options['filters']['phylotree_name']['expose']['label'] = 'Family ID';
+    $handler->display->display_options['filters']['phylotree_name']['expose']['use_operator'] = TRUE;
+    $handler->display->display_options['filters']['phylotree_name']['expose']['operator'] = 'phylotree_name_op';
+    $handler->display->display_options['filters']['phylotree_name']['expose']['identifier'] = 'phylotree_name';
+    $handler->display->display_options['filters']['phylotree_name']['expose']['remember_roles'] = array(
+        2 => '2',
+        1 => 0,
+        4 => 0,
+    );
+    $handler->display->display_options['filters']['phylotree_name']['max_length'] = '40';
+    /* Filter criterion: phylotree_count: Description */
+    $handler->display->display_options['filters']['phylotree_comment']['id'] = 'phylotree_comment';
+    $handler->display->display_options['filters']['phylotree_comment']['table'] = 'phylotree_count';
+    $handler->display->display_options['filters']['phylotree_comment']['field'] = 'phylotree_comment';
+    $handler->display->display_options['filters']['phylotree_comment']['operator'] = 'allwords';
+    $handler->display->display_options['filters']['phylotree_comment']['exposed'] = TRUE;
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['operator_id'] = 'phylotree_comment_op';
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['label'] = 'Tree Description';
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['use_operator'] = TRUE;
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['operator'] = 'phylotree_comment_op';
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['identifier'] = 'phylotree_comment';
+    $handler->display->display_options['filters']['phylotree_comment']['expose']['remember_roles'] = array(
+        2 => '2',
+        1 => 0,
+        4 => 0,
+    );
+    $handler->display->display_options['filters']['phylotree_comment']['max_length'] = '40';
+    /* Filter criterion: phylotree_count: Total count */
+    $handler->display->display_options['filters']['total_count']['id'] = 'total_count';
+    $handler->display->display_options['filters']['total_count']['table'] = 'phylotree_count';
+    $handler->display->display_options['filters']['total_count']['field'] = 'total_count';
+    $handler->display->display_options['filters']['total_count']['exposed'] = TRUE;
+    $handler->display->display_options['filters']['total_count']['expose']['operator_id'] = 'total_count_op';
+    $handler->display->display_options['filters']['total_count']['expose']['label'] = 'Total count';
+    $handler->display->display_options['filters']['total_count']['expose']['use_operator'] = TRUE;
+    $handler->display->display_options['filters']['total_count']['expose']['operator'] = 'total_count_op';
+    $handler->display->display_options['filters']['total_count']['expose']['identifier'] = 'total_count';
+    $handler->display->display_options['filters']['total_count']['expose']['remember_roles'] = array(
+        2 => '2',
+        1 => 0,
+        4 => 0,
+    );
+
+    /* Display: Phylogeny */
+    $handler = $view->new_display('page', 'Phylogeny', 'page');
+    $handler->display->display_options['path'] = 'chado/phylotree';
+    $handler->display->display_options['menu']['type'] = 'normal';
+    $handler->display->display_options['menu']['title'] = 'Phylogeny';
+    $handler->display->display_options['menu']['description'] = 'Gene trees, species trees, etc.';
+    $handler->display->display_options['menu']['weight'] = '-10';
+
+    return $view;
+}