Browse Source

Fixed issues with web services that affected collections

Stephen Ficklin 7 years ago
parent
commit
0549cf5735

+ 1 - 0
tripal/api/tripal.entities.api.inc

@@ -185,6 +185,7 @@ function tripal_load_entity($entity_type, $ids = FALSE, $reset = FALSE,
   if ($reset) {
     $ec->resetCache();
   }
+
   return $ec->load($ids, $conditions, $field_ids);
 }
 /**

+ 44 - 164
tripal/includes/TripalEntityCollection.inc

@@ -224,13 +224,13 @@ class TripalEntityCollection {
    */
   public function addFields($details, $collection_id) {
     if (!$details['bundle_name']) {
-      throw new Exception("Must provide a 'bundle_name' key to TripalEntityCollection::add().");
+      throw new Exception("Must provide a 'bundle_name' to TripalEntityCollection::addFields().");
     }
     if (!$details['ids']) {
-      throw new Exception("Must provide a 'ids' key to TripalEntityCollection::add().");
+      throw new Exception("Must provide a 'ids' to TripalEntityCollection::addFields().");
     }
     if (!$details['fields']) {
-      throw new Exception("Must provide a 'fields' key to TripalEntityCollection::add().");
+      throw new Exception("Must provide a 'fields' to TripalEntityCollection::addFields().");
     }
 
     try {
@@ -287,14 +287,39 @@ class TripalEntityCollection {
     return $result;
   }
 
+  /**
+   * Retrieves the vocabulary for a remote Tripal web service.
+   *
+   * @return
+   *   The vocabulary of a remote Tripal web service.
+   */
+  protected function retrieveRemoteAPIDoc($site_id) {
+    $cache_name = 'tripal_web_services_doc_' . $site_id;
+    if ($cache = cache_get($cache_name)) {
+      $site_doc = $cache->data;
+    }
+    else {
+      $site_doc = tripal_get_remote_site_doc($site_id);
+      if (!$site_doc) {
+        cache_set('tripal_web_services_doc_'  . $site_id, $site_doc);
+      }
+    }
+    return $site_doc;
+  }
+
   /**
    * Retrieves the list of appropriate download formatters for the basket.
    *
+   * @param $fields
+   *   An array of field_ids, organized by bundles. They key is a bundle
+   *   name and the value is the list of fields for that bundle.
+   *
    * @return
    *   An associative array where the key is the TripalFieldDownloader class
    *   name and the value is the human-readable lable for the formatter.
    */
   public function getDownloadFormattersList($fields) {
+
     $downloaders = array();
     // Iterate through the fields and find out what download formats are
     // supported for this basket.
@@ -303,54 +328,40 @@ class TripalEntityCollection {
         // Check is $field_groups is an array because if it is that means we
         // nested arrays we need to deal with.
         if (is_array($field_id)) {
-          foreach ($field_id as $field) {
+          foreach ($field_id as $fid) {
             // If the $field is numeric it's a field id from the local site but
             // if it is not then it's a remote field that needs to be handled differently.
-            if (!is_numeric($field)) {
+            if (!is_numeric($fid)) {
               // Need the site ID from the tripal_collection_bundle table.
               $site_id = $this->getSiteId($bundle_name);
-              // Use the webservices call to pull the available downloaders list.
-              // Need to pull the vocab doc of the web services to know the download formats.
-              // Use the api call to get the vocab for the site which will need to be parsed
-              // to find the accession and the download types available.
-              $site_vocab = tripal_web_services_vocab_request($site_id);
-              if (!empty($site_vocab)) {
-                // Because there are multiple sites available we need to differentiate the
-                // different cached vocab structures.
-                $cache_name = 'tripal_web_services_vocab_' . $site_id['site_id'];
-                // Put it in the cache so we don't need to make repeated calls to it.
-                cache_set($cache_name, $site_vocab);
-
-                // Now find the tripal formatters in the json data returned.
-                $this->getRemoteFieldDownloadFormats($site_vocab, $bundle_name, $field, $site_id['site_id']);
-              }
+              $site_doc = $this->retrieveRemoteAPIDoc($site_id);
+              // Now find the tripal formatters in the json data returned.
+              $this->getRemoteFieldDownloadFormats($site_doc, $bundle_name, $fid, $site_id['site_id']);
             }
             else {
-              foreach ($field_id as $field) {
-                $field_info = field_info_field_by_id($field);
-                if (!$field_info) {
-                  continue;
-                }
+              $field_info = field_info_field_by_id($fid);
+              if (!$field_info) {
+                continue;
               }
               $field_name = $field_info['field_name'];
               $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
               // API function
               // All fields should support the Tab and CSV downloaders.
               $downloaders = array();
-              $this->downloaders += tripal_get_field_field_formatters($field, $instance);
+              $this->downloaders += tripal_get_field_field_formatters($field_info, $instance);
             }
           }
         }
         else {
-          $field = field_info_field_by_id($field_id);
-          if (!$field) {
+          $field_info = field_info_field_by_id($field_id);
+          if (!$field_info) {
             continue;
           }
-          $field_name = $field['field_name'];
+          $field_name = $field_info['field_name'];
           $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
           // API function
           $downloaders = array();
-          $this->downloaders += tripal_get_field_field_formatters($field, $instance);
+          $this->downloaders += tripal_get_field_field_formatters($field_info, $instance);
         }
       }
     }
@@ -374,9 +385,9 @@ class TripalEntityCollection {
    * assigns them to $this->downloader.
    *
    */
-  public function getRemoteFieldDownloadFormats($site_vocab, $bundle_name, $field, $site_id) {
+  public function getRemoteFieldDownloadFormats($site_doc, $bundle_name, $field, $site_id) {
     // Now find the tripal formatters in the json data returned.
-    foreach ($site_vocab as $item) {
+    foreach ($site_doc as $item) {
       if (is_array($item)) {
         foreach ($item as $vocab_term) {
           /* The returned $vocab item should look like this:
@@ -676,138 +687,7 @@ class TripalEntityCollection {
 
     $outfile = $this->getOutfile($formatter);
 
-    // Filter out fields that aren't supported by the formatter.
-    $supported_fields = array();
-    foreach ($this->fields as $field_group) {
-      foreach ($field_group as $field_id) {
-        // Check is $field_id is an array because if it is that means we
-        // nested arrays we need to deal with.
-        if (is_array($field_id)) {
-          foreach ($field_id as $field) {
-            // If the formatter is TripalTabDownloader or TripalCSVDownloader then
-            // we always want to support the field.
-            if ($tripal_create_collection_files == 'TripalTabDownloader' or $formatter == 'TripalCSVDownloader') {
-              if (!in_array($field, $supported_fields)) {
-                $supported_fields[] = $field;
-              }
-              continue;
-            }
-
-            // If the $field is numeric it's a field id from the local site but
-            // if it is not then it's a remote field that needs to be handled differently.
-            if (!is_numeric($field)) {
-              // Need the site ID from the tripal_collection_bundle table.
-              $collection_id = $this->collection_id;
-              // Return the bundles from the collection_bundle table.
-              $collections = db_select('tripal_collection_bundle')
-                ->fields('tripal_collection_bundle')
-                ->condition('collection_id', $collection_id, '=')
-                ->execute()
-                ->fetchAll();
-
-              // Now that we have all possible bundles we need to find the right one.
-              foreach ($collections as $collection) {
-                $fields = unserialize($collection->fields);
-                if (in_array($field, $fields)) {
-                  $site_id = $collection->site_id;
-                }
-              }
-              // Use the webservices call to pull the available downloaders list.
-              // Need to pull the vocab doc of the web services to know the download formats.
-              // Use the api call to get the vocab for the site which will need to be parsed
-              // to find the accession and the download types available.
-              if (!empty($site_id)) {
-                $cache_name = 'tripal_web_services_vocab_' . $site_id;
-                if ($cache = cache_get($cache_name)) {
-                  $site_vocab = $cache->data;
-                }
-                else {
-                  $site_vocab = tripal_web_services_vocab_request($site_id);
-                  if (!empty($site_vocab)) {
-                    cache_set('tripal_web_services_vocab', $site_vocab);
-                  }
-                }
-                foreach ($site_vocab as $item) {
-                  if (is_array($item)) {
-                    foreach ($item as $vocab_term) {
-                      if (!empty($vocab_term['supportedProperty'])) {
-                        $vocab_supported_properties = $vocab_term['supportedProperty'];
-                        if (is_array($vocab_supported_properties)) {
-                          foreach ($vocab_supported_properties as $property) {
-                            if ($property['property'] === $field) {
-                              if (in_array('tripal_formatters', $property)) {
-                                $download_types = $property['tripal_formatters'];
-                                foreach ($download_types as $download_type) {
-                                  if ($formatter == $download_type) {
-                                    $supported_fields[] = $field;
-                                    continue 5;
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        }
-                        else {
-                          if (in_array('tripal_formatters', $vocab_supported_properties)) {
-                            $download_types = $vocab_supported_properties['tripal_formatters'];
-                            foreach ($download_types as $download_type) {
-                              if ($formatter == $download_type) {
-                                $supported_fields[] = $field;
-                                continue 3;
-                              }
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-            }
-            else {
-              // Otherwise, find out if the formatter specified is supported by the
-              // field and if so then add it to our list of supported fields.
-              $field_info = field_info_field_by_id($field);
-              $field_name = $field_info['field_name'];
-              $field_type = $field_info['type'];
-              if (tripal_load_include_field_class($field_type)) {
-                $settings = $field_type::$default_instance_settings;
-                if (array_key_exists('download_formatters', $settings)) {
-                  if (in_array($formatter, $settings['download_formatters'])) {
-                    $supported_fields[] = $field;
-                  }
-                }
-              }
-              else {
-                // If the formatter is TripalTabDownloader or TripalCSVDownloader then
-                // we always want to support the field.
-                if ($formatter == 'TripalTabDownloader' or $formatter == 'TripalCSVDownloader') {
-                  if (!in_array($field_id, $supported_fields)) {
-                    $supported_fields[] = $field_id;
-                  }
-                  continue;
-                }
-
-                // Otherwise, find out if the formatter specified is supporte by the
-                // field and if so then add it to our list of supported fields.
-                $field = field_info_field_by_id($field_id);
-                $field_name = $field['field_name'];
-                $field_type = $field['type'];
-                if (tripal_load_include_field_class($field_type)) {
-                  $settings = $field_type::$default_instance_settings;
-                  if (array_key_exists('download_formatters', $settings)) {
-                    if (in_array($formatter, $settings['download_formatters'])) {
-                      $supported_fields[] = $field_id;
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-    $downloader = new $formatter($this->bundles, $this->ids, $supported_fields, $outfile, $this->user->uid, $this->collection_id);
+    $downloader = new $formatter($this->collection_id, $outfile);
     $downloader->write();
 
   }

+ 53 - 64
tripal/includes/TripalFieldDownloaders/TripalCSVDownloader.inc

@@ -18,92 +18,81 @@ class TripalCSVDownloader extends TripalFieldDownloader {
    */
   static public $default_extension = 'csv';
 
+  /**
+   * @see TripalFieldDownloader::isFieldSupported()
+   */
+  public function isFieldSupported($field, $instance) {
+    $is_supported = parent::isFieldSupported($field, $instance);
+
+    // For now all fields are supported.
+    return TRUE;
+  }
+
   /**
    * @see TripalFieldDownloader::format()
    */
   protected function formatEntity($entity) {
-    $row = array();
     $bundle_name = $entity->bundle;
-    
-    // Grab the headers to ensure we tab the fields properly.
-    $header =  $this->header_array;
-    if (!empty($header)) { 
-      // Count the number of items in the header and build an array with that many items.
-      $header_count = count($header);
-      $row = array_fill(0, $header_count, NULL);
-    }
-    // Determine if the entity is remote or local.
-    if (strpos($bundle_name, 'bio_data_') !== 0) {
-      $external = TRUE;
-    }
-    else {
-      $external = FALSE;
-    }
-
-    $bundle_collections = $this->collection_bundles;
+    $row = array();
 
-    foreach ($bundle_collections as $bundle_collection) {
-      $bundle = $bundle_collection->bundle_name;
-      if ($bundle_name == $bundle) {
-        $fields = unserialize($bundle_collection->fields);
-      }
-    }
+    // Iterate through all of the printable fields and add the value to
+    // the row.
+    foreach ($this->printable_fields as $accession => $label) {
 
-    foreach ($fields as $field) {
-      if ($external) {
-        $field_name = $entity->$field['und'][0]['label'];
-        $value = $entity->$field['und'][0]['value'];
-        $accession = $entity->$field['und'][0]['accession'];
-        if (!empty($header)) {
-          //Now find the accession in the header array and place it where it needs to go.
-          $position = array_search($accession, $header);
-          $row[$position] = $value;
-        }
+      // If this field is not present for this entity then add an empty
+      // element and move on.
+      if (!array_key_exists($accession, $this->fields2terms[$bundle_name]['by_accession'])) {
+        $row[] = '';
+        continue;
       }
-      else {
-        $field_info = field_info_field_by_id($field);
-        $field_name = $field_info['field_name'];
-        $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
-        // Build the accession incase someone renames a cord field name.
-        $accession = $instance['settings']['term_vocabulary'].$instance['settings']['term_accession'];
 
-        if (count($entity->{$field_name}['und']) == 1) {
-          $value = $entity->{$field_name}['und'][0]['value'];
+      // Get the field from the class variables.
+      $field_id = $this->fields2terms[$bundle_name]['by_accession'][$accession];
+      $field = $this->fields[$bundle_name][$field_id]['field'];
+      $instance = $this->fields[$bundle_name][$field_id]['instance'];
+      $field_name = $field['field_name'];
 
-          if (!empty($header)) { 
-            //Now find the accession in the header array and place it where it needs to go.
-            $position = array_search($accession, $header);
-            // If the single element is not an array then this is good.
-            if (!is_array($value)) {
-              $row[$position] = $value;
-            }
-            else {
-              if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
-                $row[$position] = $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']);
-              }
-              else {
-                $row[$position] = '';
-              }
-              // TODO: What to do with fields that are arrays?
-            }
+      // If we only have one item for this value then add it.
+      if (count($entity->{$field_name}['und']) == 1) {
+        $value = $entity->{$field_name}['und'][0]['value'];
 
-          } 
+        // If the single element is not an array then this is good.
+        if (!is_array($value)) {
+          if (is_numeric($value) or !$value) {
+            $row[] = $value;
+          }
+          else {
+            $row[] = '"' . $value . '"';
+          }
         }
         else {
-          $row[] = '';
-          // TODO: What to do with fields that have multiple values?
+          if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
+            $row[] = '"' . $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']) . '"';
+          }
+          else {
+            $row[] = '';
+          }
+          // TODO: What to do with fields that are arrays?
         }
       }
+      // If we have multiple items then deal with that.
+      else {
+        $row[] = '';
+        // TODO: What to do with fields that have multiple values?
+      }
     }
-    return array(implode("\t", $row));
+    return array(implode(",", $row));
   }
 
   /**
    * @see TripalFieldDownloader::getHeader()
    */
   protected function getHeader() {
-    $bundle_collections = $this->collection_bundles;
-    return $this->processTabHeader($bundle_collections);
+    $row = array();
+    foreach ($this->printable_fields as $accession => $label) {
+      $row[] = $label;
+    }
+    return array(implode(",", $row));
   }
 
 }

+ 379 - 318
tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc

@@ -21,9 +21,9 @@ abstract class TripalFieldDownloader {
   static public $default_extension = 'txt';
 
   /**
-   * The bundle name.
+   * The data collection assigned to this downloader.
    */
-  protected $bundle_name = '';
+  protected $collection = NULL;
 
   /**
    * The collection ID
@@ -31,84 +31,119 @@ abstract class TripalFieldDownloader {
   protected $collection_id = NULL;
 
   /**
-   * The collection bundle IDs
+   * An array of collection_bundle records for the content types that
+   * belong to this data collection.
    */
-  protected $collection_bundles = '';
-
-    /**
-   * The collection bundle IDs
-   */
-  protected $collection_bundle_ids = array();
+  protected $collection_bundles = NULL;
 
   /**
-   * A set of entity IDs.
+   * The output file URI.
    */
-  protected $entity_ids = array();
+  protected $outfile = '';
 
   /**
-   * The set of fields
+   * An array of printable fields.  Because fields can come from multiple
+   * bundles and those bundles can be from multiple sites, it is possible that
+   * 1) two bundles use the same field and we want to conslidate to a
+   * single printable field; and 2) that a remote site may use the same term
+   * for a field as a bundle on the local site.  The only way to sort out this
+   * mess is to use the term accession numbers.  Therefore, the array contains
+   * a unique list of printable fields using their accession numbers as keys
+   * and a field label as the value.
+   *
    */
-  protected $fields = array();
+  protected $printable_fields = array();
 
   /**
-   * The output file URI.
+   * The remote site json data returned for the entity
    */
-  protected $outfile = '';
+  protected $remote_entity = '';
 
   /**
-   * An array of the header.
+   * An array that associates a field ID with a term.  The key is the bundle
+   * name, followed by two subkeys: by_field and by_accession.  To lookup
+   * a field's term you use the 'by_field' subkey with the field_id as the next
+   * level.  To lookup the field ID for a term use the 'by_accession' subkey
+   * with the accession as the next level.  Below is an example of the structure
+   * of this array.
+   *
+   * @code
+    Array (
+      [bio_data_7] => Array(
+        [by_field] => Array(
+          [56] => data:2091,
+          [57] => OBI:0100026,
+          [17] => schema:name,
+          [58] => data:2044,
+          [34] => data:0842,
+          [67] => schema:alternateName,
+        ),
+        [by_accession] => Array (
+          [data:2091] => 56,
+          [OBI:0100026] => 57,
+          [schema:name] => 17,
+          [data:2044] => 58,
+          [data:0842] => 34,
+          [schema:alternateName] => 67,
+        ),
+      ),
+    )
+   * @endcode
    */
-  protected $header_array = array();
+  protected $fields2terms = array();
+
 
   /**
-   * The remote site json data returned for the entity
+   * A list of field and instance items, indexed first by bundle_name and
+   * then by field_id and then by the keys 'field' or 'instance' where the
+   * value is the field or instance details respectively.
    */
-  protected $remote_entity = '';
+  protected $fields = array();
+
 
   /**
    * Constructs a new instance of the TripalFieldDownloader class.
-   * @param $bundle_name
-   *   The name of the bundle to which the IDs in the $id argument belong.
-   * @param $ids
-   *   An array of entity IDs.  The order of the IDs will be the order that
-   *   output is generated.
-   * @param $fields
-   *   An array of numeric field IDs to use when constructing the download. If
-   *   no fields are provided then all fields that are appropriate for the
-   *   given type will be used.
+   *
+   * @param $collection_id
+   *   The ID for the collection.
    * @param $outfile_name
    *   The name of the output file to create. The name should not include
    *   a path.
    */
-  public function __construct($bundle_name, $ids, $fields = array(),
-      $outfile_name, $uid, $collection_id) {
+  public function __construct($collection_id, $outfile_name) {
 
-    $user = user_load($uid);
-    if (!$user) {
-      throw new Exception(t("The provided user ID does not reference a real user: '@uid'.", array('@uid' => $uid)));
-    }
     if (!$outfile_name) {
       throw new Exception("Please provide an outputfilename");
     }
 
+    // Get the collection record and store it.
+    $this->collection = db_select('tripal_collection', 'tc')
+      ->fields('tc')
+      ->condition('collection_id', $collection_id, '=')
+      ->execute()
+      ->fetchObject();
+
+    // Make sure the user directory exists
+    $user = user_load($this->collection->uid);
+    $user_dir = 'public://tripal/users/' . $user->uid;
+
+    // Set the collection ID of the collection that this downloader will use.
+    $this->collection_id = $collection_id;
+    $this->outfile = $user_dir . '/' . $outfile_name;
+    $this->selected_fields = $selected_fields;
+
+    // A data collection may have multiple bundles.  We'll need to get
+    // them all and store them.
     $this->collection_bundles = db_select('tripal_collection_bundle')
       ->fields('tripal_collection_bundle')
       ->condition('collection_id', $collection_id, '=')
       ->execute()
       ->fetchAll();
-
     foreach ($this->collection_bundles as $collection_bundle) {
-      $collection_id = $collection_bundle->collection_bundle_id;
-      $this->collection_bundle_ids[] = $collection_id;
-      $this->bundle_name_ . $collection_id = $collection_bundle->bundle_name;
-      $this->entity_ids_ . $collection_id = unserialize($collection_bundle->ids);
-      $this->fields_ . $collection_id = unserialize($collection_bundle->fields);
-      $this->site_id_ . $collection_id = $collection_bundle->site_id;
+      $collection_bundle->ids = unserialize($collection_bundle->ids);
+      $collection_bundle->fields = unserialize($collection_bundle->fields);
+      $this->collection_bundles[] = $collection_bundle;
     }
-    $this->collection_id = $collection_id;
-
-    // Make sure the user directory exists
-    $user_dir = 'public://tripal/users/' . $user->uid;
 
     if (!file_prepare_directory($user_dir, FILE_CREATE_DIRECTORY)) {
       $message = 'Could not access the directory on the server for storing this file.';
@@ -121,7 +156,36 @@ abstract class TripalFieldDownloader {
       return;
     }
 
-    $this->outfile = $user_dir . '/' . $outfile_name;
+    // Map the fields to their term accessions.
+    $this->setFields();
+    $this->setFields2Terms();
+    $this->setPrintableFields();
+  }
+
+  /**
+   * Inidcates if a given field is supported by this Downloader class.
+   *
+   * @param $field
+   *   A field info array.
+   */
+  public function isFieldSupported($field, $instance) {
+    $field_name = $field['field_name'];
+    $field_type = $field['type'];
+
+    // If a field is a TripalField then check its supported downloaders.
+    if (tripal_load_include_field_class($field_type)) {
+      $formatters = $field_type::$download_formatters;
+      if (in_array($formatter, $settings['download_formatters'])) {
+        return TRUE;
+      }
+    }
+
+    $is_remote = $field['field']['storage']['type'] == 'tripal_remote_field' ? TRUE : FALSE;
+    if ($is_remote) {
+      if (in_array($formatter, $instance['formatters'])) {
+        return TRUE;
+      }
+    }
   }
 
   /**
@@ -151,7 +215,8 @@ abstract class TripalFieldDownloader {
    * Creates the downloadable file.
    */
   public function write() {
-    global $user;
+    $user = user_load($this->collection->uid);
+
     $fh = fopen(drupal_realpath($this->outfile), "w");
 
     if (!$fh) {
@@ -169,40 +234,33 @@ abstract class TripalFieldDownloader {
     foreach ($bundle_collections as $bundle_collection) {
       $collection_bundle_id = $bundle_collection->collection_bundle_id;
       $bundle_name = $bundle_collection->bundle_name;
-      $fields = unserialize($bundle_collection->fields);
-      $entity_ids = unserialize($bundle_collection->ids);
+      $entity_ids = $bundle_collection->ids;
+      $fields = $bundle_collection->fields;
       $site_id = $bundle_collection->site_id;
 
-      // Determine if the entity is remote or local.
-      if (strpos($bundle_name, 'bio_data_') !== 0) {
-        $external = TRUE;
-      }
-      else {
-        $external = FALSE;
-      }
+      foreach ($entity_ids as $entity_id) {
+
 
-      // Now handle the external bundles.
-      if ($external) {
-        // Now we have the remote site info we need to check against the
-        // passed field and entity ids.
-        foreach ($entity_ids as $remote_id) {
-          $fake_tripal_entity = $this->getRemoteEntity($remote_id, $site_id, $fields, $bundle_name);
-          $lines = $this->formatEntity($fake_tripal_entity);
-          foreach ($lines as $line) {
-            fwrite($fh, $line . "\r\n");
+        // if we have a site_id then we need to get the entity from the
+        // remote service. Otherwise create the entity from the local system.
+        if ($site_id) {
+          $entity = $this->loadRemoteEntity($entity_id, $site_id, $bundle_name);
+          if (!$entity) {
+            continue;
           }
         }
-      }
-      else {
-        // Now handle the local bundles.
-        foreach ($entity_ids as $entity) {
-          // If the field is from a remote entity then we need to load the info through web services.
-          $result = tripal_load_entity('TripalEntity', array($entity), FALSE, $fields);
-          $entity_info = $result[$entity];
-          $lines = $this->formatEntity($entity_info);
-          foreach ($lines as $line) {
-            fwrite($fh, $line . "\r\n");
-          }
+        else {
+          $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $fields);
+          $entity = $result[$entity_id];
+        }
+
+        if (!$entity) {
+          continue;
+        }
+
+        $lines = $this->formatEntity($entity);
+        foreach ($lines as $line) {
+          fwrite($fh, $line . "\r\n");
         }
       }
     }
@@ -214,12 +272,34 @@ abstract class TripalFieldDownloader {
     $file->filemime = file_get_mimetype($this->outfile);
     $file->uid = $user->uid;
     $file->status = FILE_STATUS_PERMANENT;
-    $file = file_save($file);
-    $fid = $file->fid;
-    $file = file_load($fid);
+
+    // Check if this file already exists. If it does then just update
+    // the stats.
+    $fid = db_select('file_managed', 'fm')
+      ->fields('fm', array('fid'))
+      ->condition('uri', $this->outfile)
+      ->execute()
+      ->fetchField();
+    if ($fid) {
+      $file->fid = $fid;
+      $file = file_save($file);
+    }
+    else {
+      $file = file_save($file);
+      $fid = $file->fid;
+      $file = file_load($fid);
+    }
+
+
     // We use the fid for the last argument because these files
-    // aren't really associated with any entity, but we need a value.
-    file_usage_add($file, 'tripal', 'data-collection', $fid);
+    // aren't really associated with any entity, but we need a value./
+    // But, only add the usage if it doens't already exists.
+    $usage = file_usage_list($file);
+    if (array_key_exists('tripal', $usage)) {
+      if (!array_key_exists('data-collection', $usage['tripal'])) {
+        file_usage_add($file, 'tripal', 'data-collection', $fid);
+      }
+    }
   }
 
   /**
@@ -229,286 +309,267 @@ abstract class TripalFieldDownloader {
 
   }
 
-  /**
-   * Recursive function to walk down the array looking for the passed value.
-   *
-   * @param $haystack
-   *   The array of vocab returned from the remote site.
-   * @param $needle
-   *   The field which is trying to be matched to.
-   * @param $current_array
-   *   Holder for the last array identified.
-   *
-   * @return
-   *   The array of the field with title included.
-   */
-  public function vocabSearch($haystack, $needle, &$current_array) {
-    static $_return_array = array();
-    // If the $current_array has a value and the $item is not an array
-    // then we are at the single item section of the nested vocab array
-    // and we can start to check for the $needle.
-    if ($current_array['property'] == $needle) {
-      $_return_array = $current_array;
-      return $_return_array;
-    }
-    elseif (is_array($haystack) && count($haystack) > 0) {
-      foreach ($haystack as $item) {
-        if (is_array($item)) {
-          // Clear out the previous array.
-          $current_array = [];
-          $current_array = $item;
-          $this->vocabSearch($item, $needle, $current_array);
-        }
-      }
-    }
-    return $_return_array;
-  }
-
   /**
    * Build and return a fake entity from a remote site using
    * tripal web services calls.
    *
    * @param $remote_ids
    *   Array of the remote ids.
-   * 
+   *
    * @param $site_id
    *   Unique site id assigned in the tripal_sites table when
    *   a new site is created via the web services interface.
-   * 
+   *
    * @param $remote_fields
    *   Array of the remote fields.
-   * 
+   *
    * @param $bundle_name
-   *   Bundle name of the remote field, in this instance it will be 
+   *   Bundle name of the remote field, in this instance it will be
    *   the accession of the field.
    *
    * @return $fake_tripal_entity
    *    This is a fake entity structured to allow the format
    *    entity function to process and return the info.
    */
-  public function getRemoteEntity($remote_ids, $site_id, $remote_fields, $bundle_name) {
-    // Before a request can be made we need to get the entity type
-    // '@id' contains the bundle_name, so look for that.
-    if (!empty($site_id)) {
-      $cache_name = 'tripal_web_services_vocab_' . $site_id;
-      if ($cache = cache_get($cache_name)) {
-        $site_vocab = $cache->data;
+  protected function loadRemoteEntity($remote_id, $site_id, $bundle_name) {
+
+    // Get the site documentation
+    $site_doc = $this->retrieveRemoteAPIDoc($site_id);
+
+    // Get the remote entity and create the fake entity.
+    $query = $bundle_name . '/' . $remote_id;
+    $remote_entity = tripal_query_remote_site($site_id, $query);
+    if (!$remote_entity) {
+      return FALSE;
+    }
+
+    // Start building the fake id.
+    $entity = new stdClass();
+    $entity->entityType = 'TripalEntity';
+    $entity->entityInfo = [];
+    $entity->id = $remote_id;
+    $entity->type = 'TripalEntity';
+    $entity->bundle = $bundle_name;
+
+    // Get the context JSON for this remote entity, we'll use it to map
+    // the properties to the correct fields.
+    $context = drupal_http_request($remote_entity['@context']);
+    $context = drupal_json_decode($context->data);
+    $context = $context['@context'];
+
+    // Iterate through the fields that are printable and get those values
+    // from the results.
+    foreach ($this->printable_fields as $accession => $label) {
+      $field_id = $this->fields2terms[$bundle_name]['by_accession'][$accession];
+
+      // If the field isn't part of this bundle then skip it.
+      if (!$field_id) {
+        continue;
       }
-      else {
-        $site_vocab = tripal_web_services_vocab_request($site_id);
-        if (!empty($site_vocab)) {
-          cache_set('tripal_web_services_vocab', $site_vocab);
+
+      $field = $this->fields[$bundle_name][$field_id]['field'];
+      $instance = $this->fields[$bundle_name][$field_id]['instance'];
+      $field_name = $field['field_name'];
+      $accession = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+
+      // Get the key for this field from the context.
+      $field_key = $accession;
+      foreach ($context as $k => $v) {
+        if (!is_array($v)) {
         }
-      }
-      // Now we have the vocab we can look for the bundle_name in the @id field.
-      foreach ($site_vocab as $item) {
-        if (is_array($item)) {
-          foreach ($item as $vocab_term) {
-            if (!empty($vocab_term['@id'])) {
-              if (strpos($vocab_term['@id'], $bundle_name) !== FALSE) {
-                $entity_type = $vocab_term['hydra:title'];
-              }
-            }
-          }
+        if (!is_array($v) and $v == $accession) {
+          $field_key = $k;
         }
       }
-      if (is_array($remote_ids)) {
-        foreach ($remote_ids as $remote_id) {
-          // This entity needs to be pulled down and data grabbed.
-          $query = $entity_type . '/' . $remote_id;
-          $this->remote_entity = tripal_web_services_remote_request($site_id, $query);
-        }
+
+      // If the field is not in this remote bundle then add an empty value for
+      // it.
+      if (!$field_key) {
+        $entity->{$field_name}['und'][0]['value'] = '';
+        continue;
       }
-      else {
-        $query = $entity_type . '/' . $remote_ids;
-        $this->remote_entity = tripal_web_services_remote_request($site_id, $query);
+
+      // If the key is for a field that is not "auto attached' then we need
+      // to get that field through a separate call.
+      $needs_query = FALSE;
+      if (array_key_exists($field_name, $context) and is_array($context[$field_name]) and
+          array_key_exists('@type', $context[$field_name]) and $context[$field_name]['@type'] == '@id'){
+        $needs_query = TRUE;
       }
-      /**
-       * remote_entity looks like this:
-       *  [@context] => http://demo.tripal.info/3.x/sites/default/files/tripal/ws/context/content.v0_1.gene.296.json
-       *  [@id] => http://demo.tripal.info/3.x/web-services/content/v0.1/Gene/296
-       *  [@type] => gene
-       *  [label] => td01_000348m.g
-       *  [ItemPage] => http://demo.tripal.info/3.x/bio_data/296
-       *  [type] => Gene
-       *  [organism] => Array
-       *     (
-       *         [label] => <i>Tripalus databasica</i>
-       *          [genus] => Tripalus
-       *          [species] => databasica
-       *      )
-       *
-       *  [name] => td01_000348m.g
-       *  [identifier] => td01_000348m.g
-       *  [sequence_checksum] => d41d8cd98f00b204e9800998ecf8427e
-       *  [time_accessioned] => 2011-06-30 17:00:58.050856
-       *  [time_last_modified] => 2011-06-30 17:00:58.050856
-       *  [sequence_coordinates] => http://demo.tripal.info/3.x/web-services/content/v0.1/Gene/296/Sequence+coordinates
-       *  [relationship] => http://demo.tripal.info/3.x/web-services/content/v0.1/Gene/296/relationship
-       */
-
-      // Build the fields for the fake entity.
-      /**
-       * $site_vocab will look like this:
-       * [3] => Array(
-       *  [property] => data:0842
-       *  [hydra:title] => Identifier
-       *  [hydra:description] =>
-       *  [required] => 1
-       *  [readable] =>
-       *  [writeable] => 1
-       *  [tripal_formatters] => Array
-       *    (
-       *      [0] => TripalTabDownloader
-       *      [1] => TripalCSVDownloader
-       *    )
-       *  )
-       */
-      // If only one field was passed then it needs to be handled differently.
-      if (!is_array($remote_fields)) {
-        $field_entity = array();
-        $returned_array = $this->vocabSearch($site_vocab, $remote_fields, $field_entity);
-        if ($returned_array['hydra:title']) {
-          $fields[$remote_fields]['field_name'] = $returned_array['hydra:title'];
-          // Turn the hydra:title into the machine name to get the label.
-          $machine_name = (str_replace(' ', '-', strtolower($fields[$remote_fields]['field_name'])));
-          $fields[$remote_fields]['label'] = $machine_name;
-          // Get the value from the previously created entity.
-          $fields[$remote_fields]['value'] = $this->remote_entity[$machine_name];
-          // Add the accession information.
-          $fields[$remote_fields]['accession'] = $remote_fields;
-        }
+
+      $value = '';
+      if (!$needs_query) {
+        $value = $remote_entity[$field_key];
       }
-      else {
-        foreach ($remote_fields as $field) {
-          $field_entity = array();
-          $returned_array = $this->vocabSearch($site_vocab, $field, $field_entity);
-          if (!empty($returned_array)) {
-            if ($returned_array['hydra:title']) {
-              $fields[$field]['field_name'] = $returned_array['hydra:title'];
-              // Turn the hydra:title into the machine name to get the label.
-              $machine_name = (str_replace(' ', '-', strtolower($fields[$field]['field_name'])));
-              $fields[$field]['label'] = $machine_name;
-              // Get the value from the previously created entity.
-              $fields[$field]['value'] = $this->remote_entity[$machine_name];
-              // Add the accession information.
-              $fields[$field]['accession'] = $field;
-            }
-          }
-        }
+      $entity->{$field_name}['und'][0]['value'] = $value;
+    }
+
+    return $entity;
+  }
+
+  /**
+   * Retrieves the vocabulary for a remote Tripal web service.
+   *
+   * @return
+   *   The vocabulary of a remote Tripal web service.
+   */
+  protected function retrieveRemoteAPIDoc($site_id) {
+    $cache_name = 'tripal_web_services_doc_' . $site_id;
+    if ($cache = cache_get($cache_name)) {
+      $site_doc = $cache->data;
+    }
+    else {
+      $site_doc = tripal_get_remote_site_doc($site_id);
+      if (!$site_doc) {
+        cache_set('tripal_web_services_doc_'  . $site_id, $site_doc);
       }
-      // Because this is a remote field we need to construct a fake entity.
-      $fake_tripal_entity = new stdClass();
-      $fake_tripal_entity->entityType = 'TripalEntity';
-      $fake_tripal_entity->entityInfo = [];
-      $fake_tripal_entity->id = $entity_id;
-      $fake_tripal_entity->type = 'TripalEntity';
-      $fake_tripal_entity->bundle = $bundle_name;
-      if (is_array($fields)) {
-        foreach ($fields as $name => $field) {
-          $fake_tripal_entity->$name = [
-            'und' => [
-              '0' => [
-                'field_name' => $field['field_name'],
-                'label' => $field['label'],
-                'value' => $field['value'],
-                'accession' => $field['accession'],
-              ],
-            ],
-          ];
-        };
+    }
+    return $site_doc;
+  }
+
+  /**
+   * A helper function for the setFields() function.
+   *
+   * Adds local fields to the list of fields.
+   */
+  private function setLocalFields() {
+    foreach ($this->collection_bundles as $collection_bundle) {
+      $bundle_name = $collection_bundle->bundle_name;
+      // Skip remote fields.
+      if ($collection_bundle->site_id) {
+        continue;
       }
-      else {
-        $fake_tripal_entity->$name = [
-          'und' => [
-            '0' => [
-              'field_name' => $fields[$remote_fields]['field_name'],
-              'label' => $fields[$remote_fields]['label'],
-              'value' => $fields[$remote_fields]['value'],
-              'accession' => $fields[$remote_fields]['accession'],
-            ],
-          ],
-        ];
+      foreach ($collection_bundle->fields as $field_id) {
+        $field = field_info_field_by_id($field_id);
+        $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
+        $this->fields[$bundle_name][$field_id]['field'] = $field;
+        $this->fields[$bundle_name][$field_id]['instance'] = $instance;
       }
     }
-    return $fake_tripal_entity;
   }
 
   /**
-   * Process and return a header for tab delimited file formats.
-   *
-   * @param $bundle_collections
-   *   The accession which is stored as the field id.
+   * A helper function for the setFields() function.
    *
-   * @return array
-   *    The impoded array for the header.
+   * Adds remote fields to the list of fields.
    */
-  public function processTabHeader($bundle_collections) {
-    $row = array();
-    $accession_ids = array();
+  private function setRemoteFields() {
+    foreach ($this->collection_bundles as $collection_bundle) {
+      $bundle_name = $collection_bundle->bundle_name;
+      // Skip local fields.
+      if (!$collection_bundle->site_id) {
+        continue;
+      }
 
-    foreach ($bundle_collections as $bundle_collection) {
-      $collection_id = $bundle_collection->collection_bundle_id;
-      $bundle_name = $bundle_collection->bundle_name;
-      $fields = unserialize($bundle_collection->fields);
-      $entity_ids = unserialize($bundle_collection->ids);
-      $site_id = $bundle_collection->site_id;
+      $site_doc = $this->retrieveRemoteAPIDoc($collection_bundle->site_id);
+
+      // Get the class that matches this bundle.
+      $class = $this->getRemoteClass($bundle_name, $site_doc);
+
+      // Iterate through the fields of this collection and get the
+      // info for each one from the class.  We will create "fake" field and
+      // instance info arrays.
+      foreach ($collection_bundle->fields as $field_id) {
+
+        // Get the property from the document for this field.
+        $property = $this->getRemoteClassProperty($class, $field_id, $site_doc);
+
+        // Now create the fake field and instance.
+        list($vocab, $accession) = explode(':', $field_id);
+        $field_name = 'tripal_remote_site_' . $collection_bundle->site_id . '_' . $field_id;
+        $field = array(
+          'field_name' => $field_name,
+          'type' => $field_name,
+          'storage' => array(
+            'type' => 'tripal_remote_site'
+          ),
+        );
+        $instance = array(
+          'label' => $property['hydra:title'],
+          'description' => $property['hydra:description'],
+          'formatters' => $property['tripal_formatters'],
+          'settings' => array(
+            'term_vocabulary' => $vocab,
+            'term_accession' => $accession
+          ),
+          'field_name' => $field_name,
+          'entity_type' => 'TripalEntity',
+          'bundle_name' => $bundle_name,
+        );
+        $this->fields[$bundle_name][$field_id]['field'] = $field;
+        $this->fields[$bundle_name][$field_id]['instance'] = $instance;
+      }
+    }
+  }
+
+  private function getRemoteClass($class_id, $site_doc){
+    // Get the class that matches this bundle.
+    $classes = $site_doc['supportedClass'];
+    $class = NULL;
+    foreach ($classes as $item) {
+      if ($item['@id'] == $class_id) {
+        $class = $item;
+      }
+    }
+    return $class;
+  }
 
-      // Determine if the entity is remote or local.
-      if (strpos($bundle_name, 'bio_data_') !== 0) {
-        $external = TRUE;
+  private function getRemoteClassProperty($class, $prop_id, $site_doc){
+    $properties = $class['supportedProperty'];
+    foreach ($properties as $item) {
+      if ($item['property'] == $prop_id) {
+         return $item;
       }
-      else {
-        $external = FALSE;
+    }
+  }
+
+  /**
+   * Sets the fields array
+   */
+  protected function setFields() {
+    $this->setLocalFields();
+    $this->setRemoteFields();
+  }
+
+  /**
+   * Sets the fields2term array.
+   *
+   * The fields2term array provides an easy lookup for mapping a term
+   * to it's accession number.
+   **/
+  protected function setFields2Terms() {
+    foreach ($this->fields as $bundle_name => $bundle_fields) {
+      foreach ($bundle_fields as $field_id => $info) {
+        $instance = $info['instance'];
+        $accession = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+        $this->fields2terms[$bundle_name]['by_field'][$field_id] = $accession;
+        $this->fields2terms[$bundle_name]['by_accession'][$accession] = $field_id;
       }
+    }
+  }
 
-      foreach ($fields as $field) {
-        if ($external) {
-          $fake_tripal_entity = $this->getRemoteEntity($entity_ids, $site_id, $field, $bundle_name);
-          $field_name = $fake_tripal_entity->$field['und'][0]['field_name'];
-          $accession = $fake_tripal_entity->$field['und'][0]['accession'];
-          if (!empty($accession_ids) && !empty($row)) {
-            // If the accession is already in the accession_ids list skip.
-            if (array_search($accession, $accession_ids)) {
-              continue;
-            }
-            else {
-              $row[] = $field_name;
-              $accession_ids[] = $accession;
-            }
-          } 
-          else {
-            $row[] = $field_name;
-            $accession_ids[] = $accession;
-          }
-        }
-        else {
-          $field_info = field_info_field_by_id($field);
-          //$field_name is the accession info.
-          $field_name = $field_info['field_name'];
-          $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
-
-          // Build the accession incase someone renames a cord field name.
-          $accession = $instance['settings']['term_vocabulary'] . $instance['settings']['term_accession'];
-          if (!empty($accession_ids) && !empty($row)) {
-            // If the accession is already in the accession_ids list skip.
-            if (array_search($accession, $accession_ids)) {
-              continue;
-            }
-            else {
-              $accession_ids[] = $accession;
-              $row[] = $instance['label'];
-            }
-          } 
-          else {
-            $accession_ids[] = $accession;
-            $row[] = $instance['label'];
+  /**
+   * Conslidates all the fields into a single list of accession numbers.
+   *
+   * The array of printable fields will contain an array containing the
+   * accession number and the label.  The title used is from the first
+   * occurance of an accession.
+   */
+  protected function setPrintableFields() {
+
+    foreach ($this->fields as $bundle_name => $bundle_fields) {
+      foreach ($bundle_fields as $field_id => $info) {
+        $instance = $info['instance'];
+        $accession = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+        if (!array_key_exists($accession, $this->printable_fields)) {
+          // Only include fields that support this downloader type in
+          // or list of printable fields.
+          if ($this->isFieldSupported($field, $instance)) {
+            $this->printable_fields[$accession] = $instance['label'];
           }
         }
       }
     }
-    $this->header_array = $accession_ids;
-    return array(implode("\t", $row));
   }
 
   /**

+ 1 - 0
tripal/includes/TripalFieldDownloaders/TripalNucFASTADownloader.inc

@@ -34,6 +34,7 @@ class TripalNucFASTADownloader extends TripalFieldDownloader {
       }
       $available_fields[$instance['field_name']] = $instance;
     }
+return array();
 
     foreach ($this->fields as $field_id) {
       $field = field_info_field_by_id($field_id);

+ 53 - 71
tripal/includes/TripalFieldDownloaders/TripalTabDownloader.inc

@@ -19,82 +19,62 @@ class TripalTabDownloader extends TripalFieldDownloader {
   static public $default_extension = 'txt';
 
   /**
-   * @see TripalFieldDownloader::format()
+   * @see TripalFieldDownloader::isFieldSupported()
    */
- protected function formatEntity($entity) {
-    $row = array();
-    $bundle_name = $entity->bundle;
-    
-    // Grab the headers to ensure we tab the fields properly.
-    $header =  $this->header_array;
-    if (!empty($header)) { 
-      // Count the number of items in the header and build an array with that many items.
-      $header_count = count($header);
-      $row = array_fill(0, $header_count, NULL);
-    }
-    // Determine if the entity is remote or local.
-    if (strpos($bundle_name, 'bio_data_') !== 0) {
-      $external = TRUE;
-    }
-    else {
-      $external = FALSE;
-    }
+  public function isFieldSupported($field, $instance) {
+    $is_supported = parent::isFieldSupported($field, $instance);
 
-    $bundle_collections = $this->collection_bundles;
+    // For now all fields are supported.
+    return TRUE;
+  }
 
-    foreach ($bundle_collections as $bundle_collection) {
-      $bundle = $bundle_collection->bundle_name;
-      if ($bundle_name == $bundle) {
-        $fields = unserialize($bundle_collection->fields);
-      }
-    }
+  /**
+   * @see TripalFieldDownloader::formatEntity()
+   */
+   protected function formatEntity($entity) {
+     $bundle_name = $entity->bundle;
+     $row = array();
 
-    foreach ($fields as $field) {
-      if ($external) {
-        $field_name = $entity->$field['und'][0]['label'];
-        $value = $entity->$field['und'][0]['value'];
-        $accession = $entity->$field['und'][0]['accession'];
-        if (!empty($header)) {
-          //Now find the accession in the header array and place it where it needs to go.
-          $position = array_search($accession, $header);
-          $row[$position] = $value;
-        }
-      }
-      else {
-        $field_info = field_info_field_by_id($field);
-        $field_name = $field_info['field_name'];
-        $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
-        // Build the accession incase someone renames a cord field name.
-        $accession = $instance['settings']['term_vocabulary'] . $instance['settings']['term_accession'];
+     // Iterate through all of the printable fields and add the value to
+     // the row.
+     foreach ($this->printable_fields as $accession => $label) {
 
-        if (count($entity->{$field_name}['und']) == 1) {
-          $value = $entity->{$field_name}['und'][0]['value'];
+       // If this field is not present for this entity then add an empty
+       // element and move on.
+       if (!array_key_exists($accession, $this->fields2terms[$bundle_name]['by_accession'])) {
+         $row[] = '';
+         continue;
+       }
 
-          if (!empty($header)) { 
-            //Now find the accession in the header array and place it where it needs to go.
-            $position = array_search($accession, $header);
+       // Get the field from the class variables.
+       $field_id = $this->fields2terms[$bundle_name]['by_accession'][$accession];
+       $field = $this->fields[$bundle_name][$field_id]['field'];
+       $instance = $this->fields[$bundle_name][$field_id]['instance'];
+       $field_name = $field['field_name'];
 
-            // If the single element is not an array then this is good.
-            if (!is_array($value)) {
-              $row[$position] = $value;
-            }
-            else {
-              if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
-                $row[$position] = $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']);
-              }
-              else {
-                $row[$position] = '';
-              }
-              // TODO: What to do with fields that are arrays?
-            }
+       // If we only have one item for this value then add it.
+       if (count($entity->{$field_name}['und']) == 1) {
+         $value = $entity->{$field_name}['und'][0]['value'];
 
-          } 
-        }
-        else {
-          $row[] = '';
-          // TODO: What to do with fields that have multiple values?
-        }
-      }
+         // If the single element is not an array then this is good.
+         if (!is_array($value)) {
+           $row[] = $value;
+         }
+         else {
+           if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
+             $row[] = $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']);
+           }
+           else {
+             $row[] = '';
+           }
+           // TODO: What to do with fields that are arrays?
+         }
+       }
+       // If we have multiple items then deal with that.
+       else {
+         $row[] = '';
+         // TODO: What to do with fields that have multiple values?
+       }
     }
     return array(implode("\t", $row));
   }
@@ -103,8 +83,10 @@ class TripalTabDownloader extends TripalFieldDownloader {
    * @see TripalFieldDownloader::getHeader()
    */
   protected function getHeader() {
-    $bundle_collections = $this->collection_bundles;
-    return $this->processTabHeader($bundle_collections);
+    $row = array();
+    foreach ($this->printable_fields as $accession => $label) {
+      $row[] = $label;
+    }
+    return array(implode("\t", $row));
   }
-
 }

+ 1 - 1
tripal/includes/TripalTerm.inc

@@ -40,7 +40,7 @@ class TripalTerm extends Entity {
     return $this->name;
   }
   public function getAccession() {
-    return $this->name;
+    return $this->vocab->vocabulary . ':' . $this->accession;
   }
   public function getDefinition() {
     return $this->definition;

+ 14 - 9
tripal_ws/api/tripal_ws.api.inc

@@ -114,9 +114,9 @@ function tripal_load_include_web_service_class($class) {
  * @param $version
  *   Version of the API being used. default to 1
  * @param $description
- *    A description of the site and any additional info that 
+ *    A description of the site and any additional info that
  *    would be helpful for admins.
- * 
+ *
  * @return
  *   TRUE if the site is successfully added, FALSE otherwise.
  */
@@ -185,13 +185,14 @@ function tripal_remove_site($record_id) {
   return FALSE;
 }
 
-/** 
- * When passed a site_id from the tripal_sites table the full
- * site's vocab service is returned in json format
- *
+/**
+ * Retreives the full Hydra documentattion for the remote Tripal site.
  *
+ * When passed a site_id from the tripal_sites table the full
+ * site's document service is returned in json format
  */
-function tripal_web_services_vocab_request($site_id) {
+function tripal_get_remote_site_doc($site_id) {
+
   // Get the site url from the tripal_sites table.
   if ($site_id) {
     $remote_site = db_select('tripal_sites', 'ts')
@@ -200,11 +201,12 @@ function tripal_web_services_vocab_request($site_id) {
       ->execute()
       ->fetchObject();
   }
+
   // Build the URL to the remote web services.
   $ws_version = $remote_site->version;
   $ws_url = $remote_site->url;
   $ws_url = trim($ws_url, '/');
-  $ws_url .= '/web-services/vocab/' . $ws_version;
+  $ws_url .= '/web-services/doc/' . $ws_version;
 
   // Build and make the request.
   $options = [];
@@ -246,8 +248,11 @@ function tripal_web_services_vocab_request($site_id) {
  * @param $query
  *   The query string. This string is added to the URL for the remote
  *   website.
+ *
+ * @return
+ *   The JSON response formatted in a PHP array or FALSE if a problem occured.
  */
-function tripal_web_services_remote_request($site_id, $query) {
+function tripal_query_remote_site($site_id, $query) {
   $ctype = $query;
   $qdata = '';
   if (preg_match('/\?/', $query)) {

+ 7 - 5
tripal_ws/includes/TripalWebService.inc

@@ -83,7 +83,7 @@ class TripalWebService {
 
     $this->addDocClass(array(
       "id" => "http://www.w3.org/ns/hydra/core#Resource",
-      "name" => 'resource',
+      "term" => 'hydra:Resource',
       "title" => "Resource",
       'subClassOf' => NULL,
     ));
@@ -345,9 +345,7 @@ class TripalWebService {
    *    An array of key/value pairs providing the details for the class. Valid
    *    keys include:
    *      - id: The unique IRI for this class.
-   *      - name: a computer-readable name for this class (i.e. no spaces,
-   *        no special characters).  This name is used to construct
-   *        type identifiers for operations.
+   *      - term: the accession for the term for this class.
    *      - title:  The title for the resource that this Class represents.
    *      - description: (Optional). A description of the resource.
    *      - subClassOf: (Optional). If this class is a subclass of another
@@ -416,7 +414,11 @@ class TripalWebService {
 
     // Set the Class ID.
     $class_id = $details['id'];
-    $supported_class->setID($class_id);
+    $accession = $details['term'];
+    if ($accession != $class_id) {
+      $supported_class->addContextItem($accession, $class_id);
+    }
+    $supported_class->setID($accession);
 
     // Set the class Type.
     if (array_key_exists('type', $details)) {

+ 52 - 19
tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc

@@ -53,8 +53,32 @@ class TripalContentService_v0_1 extends TripalWebService {
 
     // is this a valid content type?
     if ($ctype) {
-      $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-      if (!$bundle) {
+      // Get the list of published terms (these are the bundle IDs)
+      $bquery = db_select('tripal_bundle', 'tb');
+      $bquery->join('tripal_term', 'tt', 'tt.id = tb.term_id');
+      $bquery->join('tripal_vocab', 'tv', 'tv.id = tt.vocab_id');
+      $bquery->fields('tb', array('label'));
+      $bquery->fields('tt', array('accession'));
+      $bquery->fields('tv', array('vocabulary'));
+      $bquery->orderBy('tb.label', 'ASC');
+      $bundles = $bquery->execute();
+
+      // Iterate through the terms convert the santized name to the real label.
+      $i = 0;
+      $ctype_lookup = array();
+      $found = FALSE;
+      while ($bundle = $bundles->fetchObject()) {
+        if ($ctype == preg_replace('/[^\w]/', '_', $bundle->label)) {
+          $ctype = $bundle->label;
+          $found = TRUE;
+        }
+        if ($ctype == $bundle->vocabulary . ':' . $bundle->accession) {
+          $ctype = $bundle->label;
+          $found = TRUE;
+        }
+      }
+
+      if (!$found) {
         throw new Exception('Invalid content type: ' . $ctype);
       }
     }
@@ -272,7 +296,8 @@ class TripalContentService_v0_1 extends TripalWebService {
         // that information.
         $items = field_get_items('TripalEntity', $entity, $field_name);
         $term_key = $this->getContextTerm($term, array('lowercase', 'spacing'));
-        $this->resource->addContextItem($term_key, array(
+        $this->resource->addContextItem($term_key, $vocabulary . ':' . $accession);
+        $this->resource->addContextItem($vocabulary . ':' . $accession, array(
           '@id' => $term['url'],
           '@type' => '@id'
         ));
@@ -780,7 +805,7 @@ class TripalContentService_v0_1 extends TripalWebService {
    * Creates a collection of resources for a given type.
    */
   private function doContentTypeList($ctype) {
-    $service_path = $this->getServicePath() . '/' . urlencode($ctype);
+    $service_path = $this->getServicePath() . '/' . preg_replace('/[^\w]/', '_', $ctype);
     $this->resource = new TripalWebServiceCollection($service_path, $this->params);
 
     // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
@@ -789,10 +814,11 @@ class TripalContentService_v0_1 extends TripalWebService {
     $term = reset($term);
 
     // The type of collection is provided by our API vocabulary service.
-    $vocab_service = new TripalVocabService_v0_1($this->base_path);
+    $vocab_service = new TripalDocService_v0_1($this->base_path);
     $this->resource->addContextItem('vocab', $vocab_service->getServicePath() . '#');
-    $this->resource->addContextItem(urlencode($bundle->label) . 'Collection', 'vocab:' . urlencode($bundle->label) . 'Collection');
-    $this->resource->setType(urlencode($bundle->label) . 'Collection');
+    $accession = preg_replace('/[^\w]/', '_', $bundle->label . ' Collection');
+    $this->resource->addContextItem($accession, 'vocab:' . $accession);
+    $this->resource->setType($accession);
 
     // Convert term to a simple array
     $term = tripal_get_term_details($term->vocab->vocabulary, $term->accession);
@@ -893,11 +919,11 @@ class TripalContentService_v0_1 extends TripalWebService {
    */
   private function doAllTypesList() {
     $service_path = $this->getServicePath();
-    $service_vocab = new TripalVocabService_v0_1($this->base_path);
+    $service_vocab = new TripalDocService_v0_1($this->base_path);
     $this->resource = new TripalWebServiceCollection($service_path, $this->params);
     $this->resource->addContextItem('vocab', $service_vocab->getServicePath());
-    $this->resource->addContextItem('ContentCollection', $service_vocab->getServicePath() . '#ContentCollection');
-    $this->resource->setType('ContentCollection');
+    $this->resource->addContextItem('Content_Collection', $service_vocab->getServicePath() . '#Content_Collection');
+    $this->resource->setType('Content_Collection');
 
     $label = tripal_get_term_details('rdfs', 'label');
     $this->addResourceProperty($this->resource, $label, 'Content Types');
@@ -919,12 +945,13 @@ class TripalContentService_v0_1 extends TripalWebService {
       $term = tripal_get_term_details($term->vocab->vocabulary, $term->accession);
 
       $member = new TripalWebServiceResource($service_path);
-      $member->setID(urlencode($bundle->label));
+      $member->setID(preg_replace('/[^\w]/', '_', $bundle->label));
 
-      $vocab_service = new TripalVocabService_v0_1($this->base_path);
+      $vocab_service = new TripalDocService_v0_1($this->base_path);
       $member->addContextItem('vocab', $vocab_service->getServicePath() . '#');
-      $member->addContextItem(urlencode($bundle->label) . 'Collection', 'vocab:' . urlencode($bundle->label) . 'Collection');
-      $member->setType(urlencode($bundle->label) . 'Collection');
+      $accession = preg_replace('/[^\w]/', '_', $bundle->label . ' Collection');
+      $member->addContextItem($accession, 'vocab:' . $accession);
+      $member->setType($accession);
 
       // Make sure the term has a URL.
       $url = $term['url'];
@@ -954,7 +981,8 @@ class TripalContentService_v0_1 extends TripalWebService {
    */
   private function addDocContentCollectionClass() {
     $details = array(
-      'id' => 'vocab:ContentCollection',
+      'id' => 'vocab:Content_Collection',
+      'term' => 'vocab:Content_Collection',
       'title' => 'Content Collection',
     );
     $vocab = tripal_get_vocabulary_details('hydra');
@@ -1017,14 +1045,15 @@ class TripalContentService_v0_1 extends TripalWebService {
       // use the term definition
       $description = tripal_get_bundle_variable('description', $bundle->id);
       if (!$description) {
-        $description = $term->definition;
+        $description = $term->getDefinition();
       }
 
       // Create the details array for the class.
       $class_id = $this->getServicePath() . '/' . urlencode($bundle->label);
       $details = array(
-        'id' => $term->url,
-        'title' => $bundle->label,
+        'id' => $term->getURL(),
+        'term' => $term->getAccession(),
+        'title' => preg_replace('/[^\w]/', '_', $bundle->label),
         'description' => $description,
       );
 
@@ -1187,8 +1216,12 @@ class TripalContentService_v0_1 extends TripalWebService {
    * Every content type (bundle) needs a collection class in the documentation.
    */
   private function addDocBundleCollectionClass($bundle, $term) {
+
+    $accession = preg_replace('/[^\w]/', '_', $bundle->label . ' Collection');
+
     $details = array(
-      'id' => 'vocab:' . urlencode($bundle->label) . 'Collection',
+      'id' => 'vocab:' . $accession,
+      'term' => 'vocab:' . $accession,
       'title' => $bundle->label . ' Collection',
       'subClassOf' => 'hydra:Collection',
       'description' => 'A collection (or list) of ' . $bundle->label . ' resources.',

+ 8 - 7
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc → tripal_ws/includes/TripalWebService/TripalDocService_v0_1.inc

@@ -1,21 +1,21 @@
 <?php
 
-class TripalVocabService_v0_1 extends TripalWebService {
+class TripalDocService_v0_1 extends TripalWebService {
 
   /**
    * The human-readable label for this web service.
    */
-  public static $label = 'Vocabulary';
+  public static $label = 'API Documentation';
   /**
    * A bit of text to describe what this service provides.
    */
-  public static $description = 'Provides access to vocabulary terms that are in use by this site.';
+  public static $description = 'Provides Hydra style documenation to make this RESTful webservice discoverable.';
   /**
    * A machine-readable type for this service. This name must be unique
    * among all Tripal web services and is used to form the URL to access
    * this service.
    */
-  public static $type = 'vocab';
+  public static $type = 'doc';
 
   /**
    * The list of web services.
@@ -24,7 +24,7 @@ class TripalVocabService_v0_1 extends TripalWebService {
 
 
   /**
-   * Constructor for the TripalVocabService_v0_1 class.
+   * Constructor for the TripalDocService_v0_1 class.
    */
   public function __construct($base_path) {
     parent::__construct($base_path);
@@ -52,7 +52,7 @@ class TripalVocabService_v0_1 extends TripalWebService {
     $this->resource->addContextItem('apiDocumentation', 'hydra:apiDocumentation');
     $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
     $this->resource->setType('apiDocumentation');
-    $this->resource->setID('vocab/' . $this->getVersion());
+    $this->resource->setID('doc/' . $this->getVersion());
 
     // Add the EntryPoint class.
     $this->addEntryPointClass();
@@ -81,6 +81,7 @@ class TripalVocabService_v0_1 extends TripalWebService {
     $service_path = $this->getServicePath();
     $details = array(
       'id' => $service_path . '#EntryPoint',
+      'term' => 'vocab:EntryPoint',
       'title' => 'EntryPoint',
       'description' => 'The main entry point or homepage of the API',
       'subClassOf' => NULL,
@@ -134,7 +135,7 @@ class TripalVocabService_v0_1 extends TripalWebService {
       $op->addProperty('label', 'Retrieves the ' . $service_class::$label . ' resource.');
       $op->addProperty('description', NULL);
       $op->addProperty('expects', NULL);
-      $op->addProperty('returns', 'vocab:EntryPoint/' . $service::$type);
+      $op->addProperty('returns', 'local:EntryPoint/' . $service::$type);
       $op->addProperty('statusCodes', array());
       $ops[] = $op;
       $link->addContextItem('supportedOperation', 'hydra:supportedOperation');

+ 8 - 9
tripal_ws/tripal_ws.module

@@ -39,8 +39,7 @@ require_once "includes/TripalFields/WebServicesFieldFormatter.inc";
 function tripal_ws_init() {
   global $base_url;
 
-  $version = 'v0.1';
-  $api_url = $base_url . '/ws/' . $version;
+  $api_url = $base_url . '/web-sevices/';
 
   $vocab = tripal_get_vocabulary_details('hydra');
 
@@ -49,7 +48,7 @@ function tripal_ws_init() {
   // This allows a hydra-enabled client to discover the API and use it.
   $attributes = array(
     'rel' => $vocab['sw_url'] . 'apiDocumentation',
-    'href' => $api_url . '/ws-doc/',
+    'href' => $api_url . '/doc/v0.1',
   );
   drupal_add_html_head_link($attributes, $header = FALSE);
 }
@@ -138,8 +137,8 @@ function tripal_ws_get_services() {
 
   // Add a link header for the vocabulary service so that clients
   // know where to find the docs.
-  tripal_load_include_web_service_class('TripalVocabService_v0_1');
-  $service = new TripalVocabService_v0_1($service_path);
+  tripal_load_include_web_service_class('TripalDocService_v0_1');
+  $service = new TripalDocService_v0_1($service_path);
   $vocab = tripal_get_vocabulary_details('hydra');
   drupal_add_http_header('Link', '<' . $service->getServicePath() . '>; rel="' . $vocab['sw_url'] . 'apiDocumentation"');
   drupal_add_http_header('Cache-Control', "no-cache");
@@ -176,7 +175,7 @@ function tripal_ws_get_services() {
     }
     // If the URL doesn't match then return not found.
     else {
-      throw new Exception("Unsupported service URL.  Web service URLs must be of the following format:  ");
+      throw new Exception("Unsupported service URL: '" . $ws_path[1] . "'");
     }
 
     // Get the service that matches the service_name
@@ -235,8 +234,8 @@ function tripal_ws_list_services() {
   $resource = new TripalWebServiceResource($base_path);
 
   // Add the vocabulary to the context.
-  tripal_load_include_web_service_class('TripalVocabService_v0_1');
-  $service = new TripalVocabService_v0_1($base_path);
+  tripal_load_include_web_service_class('TripalDocService_v0_1');
+  $service = new TripalDocService_v0_1($base_path);
   $resource->addContextItem('vocab', $service->getServicePath() . '#');
   $resource->addContextItem('EntryPoint', 'vocab:EntryPoint');
   $resource->setType('EntryPoint');
@@ -244,7 +243,7 @@ function tripal_ws_list_services() {
   // Now add the services as properties.
   foreach ($services as $service_class) {
     tripal_load_include_web_service_class($service_class);
-    if ($service_class == 'TripalVocabService_v0_1') {
+    if ($service_class == 'TripalDocService_v0_1') {
       continue;
     }
     $service = new $service_class($base_path);