Browse Source

Downloader classes Tab and CSV working

Stephen Ficklin 7 years ago
parent
commit
7125cfad7f

+ 51 - 0
tripal/api/tripal.collections.api.inc

@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Creates a collection of entities for a given user.
+ *
+ * @param  $details
+ *   An association array containing the details for a collection. The
+ *   details must include the following key/value pairs:
+ *   - uid:  The ID of the user that owns the collection
+ *   - collection_name:  The name of the collection
+ *   - bundle_name:  The name of the TripalEntity content type.
+ *   - ids:  An array of the entity IDs that form the collection.
+ *   - fields: An array of the field IDs that the collection is limited to.
+ *   - description:  A user supplied description for the collection.
+ * @return TripalEntityCollection
+ *   An instance of a TripalEntityCollection class or FALSE on failure.
+ */
+function tripal_create_collection($details) {
+  try {
+    $collection = new TripalEntityCollection();
+    $collection_id = $collection->create($details);
+    drupal_set_message(t("Collection '%name' created.", array('%name' => $details['collection_name'])));
+  }
+  catch (Exception $e) {
+    drupal_set_message(t("Failed to create the collection '%name': " . $e->getMessage(), array('%name' =>  $details['collection_name'])), 'error');
+    return FALSE;
+  }
+  return $collection;
+}
+
+/**
+ * Retrieve a collection using the collection ID
+ *
+ * @param $collection_id
+ *   The unique identifier for the collection.
+ *
+ * @return
+ *  An instance of a TripalEntityCollection class or FALSE on failure.
+ *
+ */
+function tripal_get_collection($collection_id) {
+  try {
+    $collection = new TripalEntityCollection();
+    $collection->load($collection_id);
+    return $collection;
+  }
+  catch (Exception $e) {
+    drupal_set_message(t("Failed to load the collection with id '%id': " . $e->getMessage(), array('%id' =>  $collection_id)), 'error');
+    return FALSE;
+  }
+}

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

@@ -18,7 +18,7 @@
  * @param $reset: Whether to reset the internal cache for the requested entity
  *   type. Defaults to FALSE.
  * @param $field_ids
- *   A list of numeric feild IDs that should be loaded.  The
+ *   A list of numeric field IDs that should be loaded.  The
  *   TripalField named 'content_type' is always automatically added.
  *
  * @return

+ 39 - 0
tripal/api/tripal.fields.api.inc

@@ -162,6 +162,7 @@ function tripal_get_field_widgets() {
   }
   return $widgets;
 }
+
 /**
  * Retrieves a list of TripalFieldFormatters.
  *
@@ -259,6 +260,44 @@ function tripal_load_include_field_class($class) {
   return FALSE;
 }
 
+/**
+ * Loads the TripalEntityDownloader file into scope.
+ *
+ * @param $class
+ *   The name of the class to include.
+ *
+ * @return
+ *   TRUE if the downloader class file was found, FALSE otherwise.
+ */
+function tripal_load_include_downloader_class($class) {
+
+  $modules = module_list(TRUE);
+  foreach ($modules as $module) {
+    $file_path = realpath(".") . '/' . drupal_get_path('module', $module) . '/includes/TripalFieldDownloaders/' . $class . '.inc';
+    if (file_exists($file_path)) {
+      module_load_include('inc', $module, 'includes/TripalFieldDownloaders/' . $class);
+      if (class_exists($class)) {
+        return TRUE;
+      }
+    }
+  }
+
+  // If the libraries module is enabled then we want to look for a
+  // TripalFieldDownloader library folder and see if our field exist there.
+  if (module_exists('libraries')) {
+    $library_path = libraries_get_path('TripalFieldDownloaders');
+    $file_path = realpath(".") . '/' . $library_path .'/' . $class . '.inc';
+    if (file_exists($file_path)) {
+      require_once($file_path);
+      if (class_exists($class)) {
+        return TRUE;
+      }
+    }
+  }
+
+  return FALSE;
+}
+
 /**
  * More easily get the value of a single item from a field's items array.
  *

+ 299 - 0
tripal/includes/TripalEntityCollection.inc

@@ -0,0 +1,299 @@
+<?php
+
+class TripalEntityCollection {
+
+  /**
+   * The name of the bundle (i.e. content type) to which the entities belong.
+   */
+  protected $bundle_name = '';
+  /**
+   * The name of this collection.
+   */
+  protected $collection_name = '';
+  /**
+   * An array of numeric entities IDs.
+   */
+  protected $ids = array();
+  /**
+   * An array of field IDs.
+   */
+  protected $fields = array();
+  /**
+   * The user object of the user that owns the collection.
+   */
+  protected $user = array();
+  /**
+   * The date that the collection was created.
+   */
+  protected $create_date = '';
+
+  /**
+   * The list of downloaders available for this bundle.
+   */
+  protected $downloaders = array();
+
+  /**
+   * Constructs a new instance of the TripalEntityCollection class.
+   */
+  public function __construct() {
+
+  }
+
+  /**
+   * Loads an existing collection using a collection ID.
+   *
+   * @param $collection_id
+   *   The ID of the collection to load.
+   *
+   * @throws Exception
+   */
+  public function load($collection_id) {
+    // Make sure we have a numeric job_id.
+    if (!$collection_id or !is_numeric($collection_id)) {
+      throw new Exception("You must provide the collection_id to load the collection.");
+    }
+
+    $collection = db_select('tripal_collection', 'tc')
+      ->fields('tc')
+      ->condition('collection_id', $collection_id)
+      ->execute()
+      ->fetchObject();
+
+    if (!$collection) {
+      throw new Exception("Cannot find a collection with the ID provided.");
+    }
+
+    // Fix the date/time fields.
+    $this->bundle_name = $collection->bundle_name;
+    $this->collection_name = $collection->collection_name;
+    $this->create_date = $collection->create_date;
+    $this->user = user_load($collection->uid);
+    $this->ids = unserialize($collection->ids);
+    $this->fields = unserialize($collection->fields);
+
+    // Iterate through the fields and find out what download formats are
+    // supported for this basket.
+    foreach ($this->fields as $field_id) {
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $field_type = $field['type'];
+      $field_module = $field['module'];
+      $instance = field_info_instance('TripalEntity', $field_name, $this->bundle_name);
+      $settings = $instance['settings'];
+      $downloaders = array();
+      if (array_key_exists('download_formatters', $settings)) {
+        foreach ($settings['download_formatters'] as $class_name) {
+          if (!in_array($class_name, $settings['download_formatters'])) {
+            tripal_load_include_downloader_class($class_name);
+            $this->downloaders[$class_name] = $class_name::$label;
+          }
+          // For backwards compatibility for fields that don't have
+          // the download_formatters we'll set tab-delimeted and CSV
+          // downloaders.
+          else {
+            tripal_load_include_downloader_class('TripalTabDownloader');
+            $this->downloaders[$class_name] = $class_name::$label;
+            tripal_load_include_downloader_class('TripalCSVDownloader');
+            $this->downloaders[$class_name] = $class_name::$label;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Creates a new collection.
+   *
+   * @param  $details
+   *   An association array containing the details for a collection. The
+   *   details must include the following key/value pairs:
+   *   - uid:  The ID of the user that owns the collection
+   *   - collection_name:  The name of the collection
+   *   - bundle_name:  The name of the TripalEntity content type.
+   *   - ids:  An array of the entity IDs that form the collection.
+   *   - fields: An array of the field IDs that the collection is limited to.
+   *   - description:  A user supplied description for the collection.
+   *
+   * @throws Exception
+   */
+  public function create($details) {
+    if (!$details['uid']) {
+      throw new Exception("Must provide a 'uid' key to TripalEntityCollection::create().");
+    }
+    if (!$details['collection_name']) {
+      throw new Exception("Must provide a 'collection_name' key to TripalEntityCollection::create().");
+    }
+    if (!$details['bundle_name']) {
+      throw new Exception("Must provide a 'bundle_name' key to TripalEntityCollection::create().");
+    }
+    if (!$details['ids']) {
+      throw new Exception("Must provide a 'ids' key to TripalEntityCollection::create().");
+    }
+    if (!$details['fields']) {
+      throw new Exception("Must provide a 'fields' key to TripalEntityCollection::create().");
+    }
+
+    if (!is_array($details['ids'])) {
+      throw new Exception("The 'ids' key must be an array key to TripalEntityCollection::create().");
+    }
+    if (!is_array($details['fields'])) {
+      throw new Exception("The 'ids' key must be an array key to TripalEntityCollection::create().");
+    }
+
+    // Before inserting the new collection make sure we don't violote the unique
+    // constraint that a user can only have one collection of the give name.
+    $has_match = db_select('tripal_collection', 'tc')
+      ->fields('tc', array('collection_id'))
+      ->condition('uid', $details['uid'])
+      ->condition('collection_name', $details['collection_name'])
+      ->execute()
+      ->fetchField();
+    if ($has_match) {
+      throw new Exception('Cannot create the collection. One with this name already exists');
+    }
+
+    try {
+      $collection_id = db_insert('tripal_collection')
+        ->fields(array(
+          'collection_name' => $details['collection_name'],
+          'bundle_name' => $details['bundle_name'],
+          'ids' => serialize($details['ids']),
+          'fields' => serialize($details['fields']),
+          'create_date' => time(),
+          'uid' => $details['uid'],
+          'description' => array_key_exists('description', $details) ? $details['description'] : '',
+        ))
+        ->execute();
+      // Now load the job into this object.
+      $this->load($collection_id);
+    }
+    catch (Exception $e) {
+      throw new Exception('Cannot create collection: ' .  $e->getMessage());
+    }
+  }
+
+  /**
+   * Retrieves the list of appropriate download formatters for the basket.
+   *
+   * @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 getDownloadFormatters() {
+     return $this->downloaders;
+  }
+
+  /**
+   * Retrieves the list of entity IDs.
+   *
+   * @return
+   *   An array of numeric enity IDs.
+   */
+  public function getEntityIDs(){
+    return $this->ids;
+  }
+
+  /**
+   * Retrieves the list of fields in the basket.
+   *
+   * @return
+   *   An array of numeric field IDs.
+   */
+  public function getFields() {
+    return $this->fields;
+  }
+
+  /**
+   * Retrieves the date that the basket was created.
+   *
+   * @param $formatted
+   *   If TRUE then the date time will be formatted for human readability.
+   * @return
+   *   A UNIX time stamp string containing the date or a human-readable
+   *   string if $formatted = TRUE.
+   */
+  public function getCreateDate($formatted = TRUE) {
+    if ($formatted) {
+      return format_date($this->create_date);
+    }
+    return $this->create_date;
+  }
+
+  /**
+   * Retreives the name of the collection.
+   *
+   * @return
+   *   A string containing the name of the collection.
+   */
+  public function getName() {
+    return $this->collection_name;
+  }
+
+  /**
+   * Retrieves the user object of the user that owns the collection
+   *
+   * @return
+   *   A Drupal user object.
+   */
+  public function getUser() {
+    return $this->user;
+  }
+
+  /**
+   * Retrieves the ID of the user that owns the collection
+   *
+   * @return
+   *   The numeric User ID.
+   */
+  public function getUserID() {
+    if ($this->user) {
+      return $this->user->uid;
+    }
+    return NULL;
+  }
+
+  /**
+   * Writes the collection to a file.
+   *
+   * @param formatter
+   *   The name of the formatter class to use (e.g. TripalTabDownloader).
+   */
+  public function write($formatter) {
+
+    if (!tripal_load_include_downloader_class($formatter)) {
+      throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
+    }
+
+    $extension = $formatter::$default_extension;
+    $create_date = $this->getCreateDate(FALSE);
+    $outfile = preg_replace('/[^\w]/', '_', ucwords($this->collection_name)) . '_collection' . '_' . $create_date . '.' . $extension;
+
+    // Filter out fields that aren't supported by the formatter.
+    $supported_fields = array();
+    foreach ($this->fields as $field_id) {
+      // 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'];
+      $instance = field_info_instance('TripalEntity', $field_name, $this->bundle_name);
+      if (array_key_exists('download_formatters', $instance['settings'])) {
+        if (in_array($formatter, $instance['settings']['download_formatters'])) {
+          $supported_fields[] = $field_id;
+        }
+      }
+    }
+
+    $downloader = new $formatter($this->bundle_name, $this->ids, $supported_fields, $outfile, $this->user->uid);
+    $downloader->write();
+
+  }
+}

+ 0 - 5
tripal/includes/TripalFieldDownloader/TripalFASTADownloader.inc

@@ -1,5 +0,0 @@
-<?php
-
-class TripalFASTADownloader extends TripalFieldDownloader {
-
-}

+ 0 - 5
tripal/includes/TripalFieldDownloader/TripalGFFDownloader.inc

@@ -1,5 +0,0 @@
-<?php
-
-class TripalGFFDownloader extends TripalFieldDownloader {
-
-}

+ 0 - 5
tripal/includes/TripalFieldDownloader/TripalTabCSVDownloader.inc

@@ -1,5 +0,0 @@
-<?php
-
-class TripalCSVDownloader extends TripalFieldDownloader {
-
-}

+ 0 - 5
tripal/includes/TripalFieldDownloader/TripalTabDownloader.inc

@@ -1,5 +0,0 @@
-<?php
-
-class TripalTabDownloader extends TripalFieldDownloader {
-
-}

+ 56 - 0
tripal/includes/TripalFieldDownloaders/TripalCSVDownloader.inc

@@ -0,0 +1,56 @@
+<?php
+
+class TripalCSVDownloader extends TripalFieldDownloader {
+  /**
+   * Sets the label shown to the user describing this formatter.
+   */
+  static public $label = 'CSV (comma separated)';
+
+  /**
+   * Indicates the default extension for the outputfile.
+   */
+  static public $default_extension = 'csv';
+
+  /**
+   * @see TripalFieldDownloader::format()
+   */
+  protected function formatEntity($entity) {
+    $row = array();
+    foreach ($this->fields as $field_id) {
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $value = $entity->{$field_name}['und'][0]['value'];
+      // If we only have one element then this is good.
+      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)) {
+          $row[] = '"' . $value . '"';
+        }
+        else {
+          $row[] = '';
+          // TODO: What to do with fields that are arrays?
+        }
+      }
+      else {
+        $row[] = '';
+        // TODO: What to do with fields that have multiple values?
+      }
+    }
+    return array(implode(',', $row));
+  }
+
+  /**
+   * @see TripalFieldDownloader::getHeader()
+   */
+  protected function getHeader() {
+    $row = array();
+    foreach ($this->fields as $field_id) {
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $instance = field_info_instance('TripalEntity', $field_name, $this->bundle_name);
+      $row[] = '"' . $instance['label'] . '"';
+    }
+    return array(implode(',', $row));
+  }
+}

+ 28 - 0
tripal/includes/TripalFieldDownloaders/TripalFASTADownloader.inc

@@ -0,0 +1,28 @@
+<?php
+
+class TripalFASTADownloader extends TripalFieldDownloader {
+
+  /**
+   * Sets the label shown to the user describing this formatter.
+   */
+  static public $label = 'FASTA';
+
+  /**
+   * Indicates the default extension for the outputfile.
+   */
+  static public $default_extension = 'fasta';
+
+  /**
+   * @see TripalFieldDownloader::format()
+   */
+  protected function formatEntity($entity) {
+
+  }
+
+  /**
+   * @see TripalFieldDownloader::getHeader()
+   */
+  protected function getHeader() {
+
+  }
+}

+ 54 - 22
tripal/includes/TripalFieldDownloader/TripalFieldDownloader.inc → tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc

@@ -1,11 +1,20 @@
 <?php
 
 
-class TripalFieldDownloader {
+abstract class TripalFieldDownloader {
+
+  /**
+   * Sets the label shown to the user describing this formatter.
+   */
+  static public $label = 'Generic';
+
+  /**
+   * Indicates the default extension for the outputfile.
+   */
+  static public $default_extension = 'txt';
 
   /**
    * The bundle name.
-   * @var string
    */
   protected $bundle_name = '';
 
@@ -29,20 +38,25 @@ class TripalFieldDownloader {
    * @param $bundle_name
    *   The name of the bundle to which the IDs in the $id argument belong.
    * @param $ids
-   *   An array of entity 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 $outfile_name
-   *   The name of the output file to create (minus any extension).
-   * @param $extension
-   *   The extension to add to the end of the output file.
+   *   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 = '', $extension = 'txt') {
-    global $user;
+      $outfile_name = '', $uid) {
+
+    $user = user_load($uid);
+    if (!$user) {
+      throw new ErrorException(t("The provided user ID does not reference a real user: '@uid'.", array('@uid' => $uid)));
+    }
 
+    $this->bundle_name = $bundle_name;
     $this->entity_ids = $ids;
     $this->fields = $fields;
 
@@ -63,7 +77,7 @@ class TripalFieldDownloader {
       $outfile_name = unqiueid();
     }
 
-    $this->outfile = $user_dir. '/' . $outfile_name . '.' . $outfile_ext;
+    $this->outfile = $user_dir. '/' . $outfile_name;
   }
 
   /**
@@ -74,14 +88,27 @@ class TripalFieldDownloader {
   }
 
   /**
-   * Creates the download able file.
+   * Creates the downloadable file.
    */
-  public function create() {
-    $fh = fopen($this->outfile, "w");
+  public function write() {
+    $fh = fopen(drupal_realpath($this->outfile), "w");
+    if (!$fh) {
+      throw new Exception("Cannout open collection file: " . $this->outfile);
+    }
+
+    $headers = $this->getHeader();
+    foreach ($headers as $line) {
+      fwrite($fh, $line . "\r\n");
+    }
+
     foreach ($this->entity_ids as $entity_id) {
-      $entity = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $this->fields);
-      $content = $this->format($entity, $this->fields);
-      fwrite($fh, $content);
+      $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $this->fields);
+      reset($result);
+      $entity = $result[$entity_id];
+      $lines = $this->formatEntity($entity);
+      foreach ($lines as $line) {
+        fwrite($fh, $line . "\r\n");
+      }
     }
     fclose($fh);
   }
@@ -94,21 +121,26 @@ class TripalFieldDownloader {
   }
 
   /**
-   * Formats the output for a given entity.
+   * Formats the entity and the specified fields for utput.
    *
    * This function should be implemented by a child class. It should iterate
-   * over the fields for the entity and return the appropriate format.
+   * over the fields for the entity and return the appropriate format. It may
+   * return multiple lines of output if necessary.
    *
    * @param $entity
    *   The entity object.  The fields that should be formatted are already
    *   loaded.
-   * @param $fields
-   *   A list of field names that should be formatted.
    *
    * @return
-   *   A string containing the formatted output.
+   *   An array of strings (one per line of output.
    */
-  protected function format($entity, $fields) {
+  abstract protected function formatEntity($entity);
 
-  }
+  /**
+   *  Retreives header lines
+   *
+   *  This function should be implemented by a child class.  It should return
+   *  the header lines for an output file.
+   */
+  abstract protected function getHeader();
 }

+ 27 - 0
tripal/includes/TripalFieldDownloaders/TripalGFF3Downloader.inc

@@ -0,0 +1,27 @@
+<?php
+
+class TripalGFF3Downloader extends TripalFieldDownloader {
+  /**
+   * Sets the label shown to the user describing this formatter.
+   */
+  static public $label = 'GFF3';
+
+  /**
+   * Indicates the default extension for the outputfile.
+   */
+  static public $default_extension = 'gff3';
+
+  /**
+   * @see TripalFieldDownloader::format()
+   */
+  protected function formatEntity($entity) {
+
+  }
+
+  /**
+   * @see TripalFieldDownloader::getHeader()
+   */
+  protected function getHeader() {
+
+  }
+}

+ 56 - 0
tripal/includes/TripalFieldDownloaders/TripalTabDownloader.inc

@@ -0,0 +1,56 @@
+<?php
+
+class TripalTabDownloader extends TripalFieldDownloader {
+  /**
+   * Sets the label shown to the user describing this formatter.
+   */
+  static public $label = 'Tab delimeted';
+
+  /**
+   * Indicates the default extension for the outputfile.
+   */
+  static public $default_extension = 'txt';
+
+  /**
+   * @see TripalFieldDownloader::format()
+   */
+  protected function formatEntity($entity) {
+    $row = array();
+    foreach ($this->fields as $field_id) {
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $value = $entity->{$field_name}['und'][0]['value'];
+      // If we only have one element then this is good.
+      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)) {
+          $row[] = $value;
+        }
+        else {
+          $row[] = '';
+          // TODO: What to do with fields that are arrays?
+        }
+      }
+      else {
+        $row[] = '';
+        // TODO: What to do with fields that have multiple values?
+      }
+    }
+    return array(implode("\t", $row));
+  }
+
+  /**
+   * @see TripalFieldDownloader::getHeader()
+   */
+  protected function getHeader() {
+    $row = array();
+    foreach ($this->fields as $field_id) {
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $instance = field_info_instance('TripalEntity', $field_name, $this->bundle_name);
+      $row[] = $instance['label'];
+    }
+    return array(implode("\t", $row));
+  }
+}

+ 7 - 5
tripal/includes/TripalFields/TripalField.inc

@@ -50,11 +50,12 @@ class TripalField {
     // Set to TRUE if the site admin is not allowed to change the term
     // type, otherwise the admin can change the term mapped to a field.
     'term_fixed' => FALSE,
-    // Inidates if this field should be automatically attached to display
-    // or web services or if this field should be loaded separately. This
-    // is convenient for speed.  Fields that are slow should for loading
-    // should ahve auto_attach set to FALSE so tha their values can be
-    // attached asyncronously.
+    // Indicates the download formats for this field.  The list must be the
+    // name of a child class of the TripalFieldDownloader.
+    'download_formatters' => array(
+      'TripalTabDownloader',
+      'TripalCSVDownloader',
+    ),
   );
 
   // The default widget for this field.
@@ -78,6 +79,7 @@ class TripalField {
   // the user but otherwise provides no data.
   public static $no_data = FALSE;
 
+
   // --------------------------------------------------------------------------
   //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
   // --------------------------------------------------------------------------

+ 0 - 5
tripal/includes/TripalFields/TripalFieldFormatter.inc

@@ -16,11 +16,6 @@ class TripalFieldFormatter {
    */
   public static $default_settings = array();
 
-  /**
-   * An array of formatter classes supported by the field.
-   */
-  public static $download_formats = array();
-
 
   /**
    * Instantiates a new TripalFieldFormatter object.

+ 12 - 3
tripal/includes/tripal.fields.inc

@@ -34,10 +34,10 @@ function tripal_field_info() {
  * Implements hook_info_alter().
  */
 function tripal_field_info_alter(&$info) {
-  // Make sure all fields have a term setting so we can map
-  // all fields to a vocabulary term for the semantic web.
   foreach ($info as $field_name => $details) {
-    if(array_key_exists('instance_settings', $details)) {
+    // Make sure all fields have a term setting so we can map
+    // all fields to a vocabulary term for the semantic web.
+    if (array_key_exists('instance_settings', $details)) {
       if (!array_key_exists('term_vocabulary', $details['instance_settings'])) {
         $info[$field_name]['instance_settings']['term_vocabulary'] = '';
       }
@@ -61,6 +61,15 @@ function tripal_field_info_alter(&$info) {
       $info[$field_name]['instance_settings']['term_fixed'] = FALSE;
       $info[$field_name]['instance_settings']['auto_attach'] = TRUE;
     }
+
+    // Make sure all fields have some sort of downloader support. The Tab
+    // delimited and CSV downloaders should support all types of fields.
+    if (!array_key_exists('download_formatters', $details)) {
+      $info[$field_name]['instance_settings']['download_formatters'] = array(
+        'TripalTabDownloader',
+        'TripalCSVDownloader',
+      );
+    }
   }
 }
 

+ 96 - 20
tripal/tripal.install

@@ -16,7 +16,7 @@ function tripal_install() {
     db_add_field('tripal_jobs', 'includes', array(
       'type' => 'text',
       'description' => 'A serialized array of file paths that should be included prior to executing the job.',
-      'not NULL' => FALSE,
+      'not null' => FALSE,
     ));
   }
 }
@@ -193,6 +193,7 @@ function tripal_schema() {
   $schema['tripal_entity'] = tripal_tripal_entity_schema();
   $schema['tripal_bundle'] = tripal_tripal_bundle_schema();
   $schema['tripal_import'] = tripal_tripal_import_schema();
+  $schema['tripal_collection'] = tripal_tripal_collection_schema();
 
   // Adds a table for additional information related to bundles.
   $schema['tripal_bundle_variables'] = tripal_tripal_bundle_variables_schema();
@@ -209,96 +210,96 @@ function tripal_tripal_jobs_schema() {
       'job_id' => array(
         'type' => 'serial',
         'unsigned' => TRUE,
-        'not NULL' => TRUE
+        'not null' => TRUE
       ),
       'uid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
-        'not NULL' => TRUE,
+        'not null' => TRUE,
         'description' => 'The Drupal userid of the submitee'
       ),
       'job_name' => array(
         'type' => 'varchar',
         'length' => 255,
-        'not NULL' => TRUE
+        'not null' => TRUE
       ),
       'modulename' => array(
         'type' => 'varchar',
         'length' => 50,
-        'not NULL' => TRUE,
+        'not null' => TRUE,
         'description' => 'The module name that provides the callback for this job'
       ),
       'callback' => array(
         'type' => 'varchar',
         'length' => 255,
-        'not NULL' => TRUE
+        'not null' => TRUE
       ),
       'arguments' => array(
         'type' => 'text',
         'size' => 'normal',
-        'not NULL' => FALSE
+        'not null' => FALSE
       ),
       'progress' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'default' => 0,
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'a value from 0 to 100 indicating percent complete'
       ),
       'status' => array(
         'type' => 'varchar',
         'length' => 50,
-        'not NULL' => TRUE
+        'not null' => TRUE
       ),
       'submit_date' => array(
         'type' => 'int',
-        'not NULL' => TRUE,
+        'not null' => TRUE,
         'description' => 'UNIX integer submit time'
       ),
       'start_time' => array(
         'type' => 'int',
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'UNIX integer start time'
       ),
       'end_time' => array(
         'type' => 'int',
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'UNIX integer end time'
       ),
       'error_msg' => array(
         'type' => 'text',
         'size' => 'normal',
-        'not NULL' => FALSE
+        'not null' => FALSE
       ),
       'pid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'The process id for the job'
       ),
       'priority' => array(
         'type' => 'int',
         'unsigned' => TRUE,
-        'not NULL' => TRUE,
+        'not null' => TRUE,
         'default' => '0',
         'description' => 'The job priority'
       ),
       'mlock' => array(
         'type' => 'int',
         'unsigned' => TRUE,
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'If set to 1 then all jobs for the module are held until this one finishes'
       ),
       'lock' => array(
         'type' => 'int',
         'unsigned' => TRUE,
-        'not NULL' => FALSE,
+        'not null' => FALSE,
         'description' => 'If set to 1 then all jobs are held until this one finishes'
       ),
       'includes' => array(
         'type' => 'text',
         'description' => 'A serialized array of file paths that should be included prior to executing the job.',
-        'not NULL' => FALSE,
+        'not null' => FALSE,
       )
     ),
     'indexes' => array(
@@ -308,6 +309,64 @@ function tripal_tripal_jobs_schema() {
     'primary key' => array('job_id'),
   );
 }
+/**
+ * Returns the Drupal Schema API array for the tripal_jobs table.
+ */
+function tripal_tripal_collection_schema() {
+  return array(
+    'fields' => array(
+      'collection_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE
+      ),
+      'collection_name' => array(
+        'type' => 'varchar',
+        'length' => 1024,
+        'not null' => TRUE
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => FALSE
+      ),
+      'bundle_name' => array(
+        'type' => 'varchar',
+        'length' => 1024,
+        'not null' => TRUE
+      ),
+      'ids' => array(
+        'type' => 'text',
+        'size' => 'normal',
+        'not null' => TRUE,
+        'description' => 'An array of entity IDs.'
+      ),
+      'fields' => array(
+        'type' => 'text',
+        'size' => normal,
+        'not null' => TRUE,
+        'description' => 'An array of numeric field IDs.'
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'The user Id of the person who created the collection.'
+      ),
+      'create_date' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'UNIX integer start time'
+      ),
+    ),
+    'indexes' => array(
+      'bundle_name' => array('bundle_name'),
+      'uid' => array('uid')
+    ),
+    'unique keys' => array(
+      'user_collection' => array('uid', 'collection_name'),
+    ),
+    'primary key' => array('collection_id'),
+  );
+}
 /**
  * Returns the Drupal Schema API array for the tripal_jobs table.
  */
@@ -914,7 +973,7 @@ function tripal_update_7304() {
   catch (\PDOException $e) {
     $transaction->rollback();
     $error = $e->getMessage();
-    throw new DrupalUpdateException('Could not perform update: '. $error);
+    throw new DrupalUpdateException('Could not perform update: ' . $error);
   }
 }
 
@@ -933,6 +992,23 @@ function tripal_update_7305() {
   catch (\PDOException $e) {
     $transaction->rollback();
     $error = $e->getMessage();
-    throw new DrupalUpdateException('Could not perform update: '. $error);
+    throw new DrupalUpdateException('Could not perform update: ' . $error);
+  }
+}
+
+/**
+ * Adds the tripal_collection table.
+ */
+function tripal_update_7306() {
+  $transaction = db_transaction();
+  try {
+    $schema = array();
+    $schema['tripal_collection'] = tripal_tripal_collection_schema();
+    db_create_table('tripal_collection', $schema['tripal_collection']);
+  }
+  catch (\PDOException $e) {
+    $transaction->rollback();
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not add the tripal_collection table:' . $error);
   }
 }

+ 3 - 0
tripal/tripal.module

@@ -31,6 +31,8 @@ require_once "includes/TripalFields/TripalFieldFormatter.inc";
 require_once "includes/TripalFieldQuery.inc";
 require_once "includes/TripalJob.inc";
 require_once "includes/TripalImporter.inc";
+require_once "includes/TripalEntityCollection.inc";
+require_once "includes/TripalFieldDownloaders/TripalFieldDownloader.inc";
 
 /**
  * @defgroup tripal Tripal Core Module
@@ -626,6 +628,7 @@ function tripal_import_api() {
   module_load_include('inc', 'tripal', 'api/tripal.notice.api');
   module_load_include('inc', 'tripal', 'api/tripal.variables.api');
   module_load_include('inc', 'tripal', 'api/tripal.upload.api');
+  module_load_include('inc', 'tripal', 'api/tripal.collections.api');
   module_load_include('inc', 'tripal', 'api/tripal.DEPRECATED.api');
 }
 

+ 1 - 1
tripal_bulk_loader/tripal_bulk_loader.module

@@ -66,7 +66,7 @@ function tripal_bulk_loader_menu() {
 
   // Admin pages -----------------
   $items['admin/tripal/loaders/bulk'] = array(
-    'title' => 'Bulk Loader',
+    'title' => 'Chado Bulk Loader',
     'description' => 'Templates for loading tab-delimited data',
     'page callback' => 'tripal_bulk_loader_admin_jobs_listing',
     'access arguments' => array('administer tripal_bulk_loader'),

+ 6 - 0
tripal_chado/includes/TripalFields/ChadoField.inc

@@ -41,6 +41,12 @@ class ChadoField extends TripalField {
     'chado_column' => '',
     // The base table.
     'base_table' => '',
+    // Indicates the download formats for this field.  The list must be the
+    // name of a child class of the TripalFieldDownloader.
+    'download_formatters' => array(
+      'TripalTabDownloader',
+      'TripalCSVDownloader',
+    ),
   );
 
   // The module that manages this field.